How-to merge Mailman mailing-lists

Let’s say I have an old inactive mailing list (which ID is old-ml) for which I want to merge its archives to another one (called active-ml).

To do so, I have to merge the two mbox files holding all mails since the creation of these mailing-lists. I first tried to use cat to concatenate the two mbox files be it didn’t work.

Luckily, I found a Python script to merge 2 mbox files while sorting all mails by date. Here is how I uses it:

$ cd /var/lib/mailman/archives/private
$ wget http://mail.python.org/pipermail/mailman-users/attachments/20080322/80455064/attachment.txt --output-document=mbmerge.py
$ python ./mbmerge.py ./old-ml.mbox/old-ml.mbox ./active-ml.mbox/active-ml.mbox > ./active-ml.mbox/active-ml.mbox.new

Then I switched the current mbox with the one generated above and asked mailman to regenerate the static HTML archives:

$ cd /var/lib/mailman/archives/private/active-ml.mbox/
$ mv active-ml.mbox active-ml.mbox.backup
$ mv active-ml.mbox.new active-ml.mbox
$ chown list:list active-ml.mbox*
$ /usr/lib/mailman/bin/arch --wipe active-ml

Of course this will only merge mail archives. You still have to merge your old mailing lists parameters (including membership) manually.

At last, when everything is clean to you, you can safely remove your old mailing-list:

$ rmlist -a old-ml
$ /var/lib/mailman/bin/genaliases

How-to use GMail to send mails from Debian Squeeze

Here is quick guide on how I configured Exim 4 to let a Debian Squeeze server send mails through a GMail account. This article is just a rip-off of a tutorial I found on the web, which is itself an updated version of a Debian’s Wiki page.

Debian come with Exim (v4.72) pre-installed: it’s the default MTA on this distribution. There is absolutely no need to install extra packages. Let’s start right away by calling Exim’s configuration wizard:

$ dpkg-reconfigure exim4-config

Here are the options I choose in each step of the wizard:

  1. Choose Mail sent by smarthost; received via SMTP or fetchmail.
  2. System mail name: server.deldycke.com.
  3. IP adresses to listen on for incoming SMTP connections: 127.0.0.1 ; ::1 (which is the default proposed value).
  4. Other destinations for which mail is accepted: leave blank.
  5. Machines to relay mail for: leave blank.
  6. Machine handling outgoing mail for this host (smarthost): smtp.gmail.com::587.
  7. Hide local mail name in outgoing mail: No.
  8. Keep number of DNS-queries minimal (Dial-on-Demand): No.
  9. Mailboxes format: mbox.
  10. Split configuration into small files: No.

All these parameters you just answered are saved in the /etc/exim4/update-exim4.conf.conf:

# /etc/exim4/update-exim4.conf.conf
#
# Edit this file and /etc/mailname by hand and execute update-exim4.conf
# yourself or use 'dpkg-reconfigure exim4-config'
#
# Please note that this is _not_ a dpkg-conffile and that automatic changes
# to this file might happen. The code handling this will honor your local
# changes, so this is usually fine, but will break local schemes that mess
# around with multiple versions of the file.
#
# update-exim4.conf uses this file to determine variable values to generate
# exim configuration macros for the configuration file.
#
# Most settings found in here do have corresponding questions in the
# Debconf configuration, but not all of them.
#
# This is a Debian specific file

dc_eximconfig_configtype='smarthost'
dc_other_hostnames=''
dc_local_interfaces='127.0.0.1 ; ::1'
dc_readhost=''
dc_relay_domains=''
dc_minimaldns='false'
dc_relay_nets=''
dc_smarthost='smtp.gmail.com:587'
CFILEMODE='644'
dc_use_split_config='false'
dc_hide_mailname='false'
dc_mailname_in_oh='true'
dc_localdelivery='mail_spool'

Then I updated the /etc/exim4/exim4.conf.template to add proper handling of GMail SMTP server. Here are the differences between the untouched original exim4.conf.template file and my version:

--- /etc/exim4/exim4.conf.template-orig  2011-05-03 10:49:43.207938577 +0200
+++ /etc/exim4/exim4.conf.template       2011-05-03 10:52:26.235438776 +0200
@@ -1077,15 +1077,11 @@
 # domains, you'll need to copy the dnslookup_relay_to_domains router
 # here so that mail to relay_domains is handled separately.

-smarthost:
-  debug_print = "R: smarthost for $local_part@$domain"
-  driver = manualroute
-  domains = ! +local_domains
-  transport = remote_smtp_smarthost
-  route_list = * DCsmarthost byname
-  host_find_failed = defer
-  same_domain_copy_routing = yes
-  no_more
+send_via_gmail:
+       driver = manualroute
+       domains = ! +local_domains
+       transport = gmail_smtp
+       route_list = * smtp.gmail.com

 .endif

@@ -1632,6 +1628,12 @@
 # to a smarthost. The local host tries to authenticate.
 # This transport is used for smarthost and satellite configurations.

+gmail_smtp:
+       driver = smtp
+       port = 587
+       hosts_require_auth = $host_address
+       hosts_require_tls = $host_address
+
 remote_smtp_smarthost:
   debug_print = "T: remote_smtp_smarthost for $local_part@$domain"
   driver = smtp
@@ -1759,6 +1761,11 @@

 begin authenticators

+gmail_login:
+       driver = plaintext
+       public_name = LOGIN
+       client_send = : system@deldycke.com : XXXXXXXXX
+

 #####################################################
 ### end auth/00_exim4-config_header
@@ -1999,27 +2006,27 @@
                    ^${sg{PASSWDLINE}{\\N([^:]+:)(.*)\\N}{\\$2}}"
 .endif

-login:
-  driver = plaintext
-  public_name = LOGIN
-.ifndef AUTH_CLIENT_ALLOW_NOTLS_PASSWORDS
-  # Return empty string if not non-TLS AND looking up $host in passwd-file
-  # yields a non-empty string; fail otherwise.
-  client_send = "<; ${if and{\
-                          {!eq{$tls_cipher}{}}\
-                          {!eq{PASSWDLINE}{}}\
-                         }\
-                      {}fail}\
-                 ; ${extract{1}{::}{PASSWDLINE}}\
-                ; ${sg{PASSWDLINE}{\\N([^:]+:)(.*)\\N}{\\$2}}"
-.else
-  # Return empty string if looking up $host in passwd-file yields a
-  # non-empty string; fail otherwise.
-  client_send = "<; ${if !eq{PASSWDLINE}{}\
-                      {}fail}\
-                 ; ${extract{1}{::}{PASSWDLINE}}\
-                ; ${sg{PASSWDLINE}{\\N([^:]+:)(.*)\\N}{\\$2}}"
-.endif
+#login:
+#  driver = plaintext
+#  public_name = LOGIN
+#.ifndef AUTH_CLIENT_ALLOW_NOTLS_PASSWORDS
+#  # Return empty string if not non-TLS AND looking up $host in passwd-file
+#  # yields a non-empty string; fail otherwise.
+#  client_send = "<; ${if and{\
+#                          {!eq{$tls_cipher}{}}\
+#                          {!eq{PASSWDLINE}{}}\
+#                         }\
+#                      {}fail}\
+#                 ; ${extract{1}{::}{PASSWDLINE}}\
+#               ; ${sg{PASSWDLINE}{\\N([^:]+:)(.*)\\N}{\\$2}}"
+#.else
+#  # Return empty string if looking up $host in passwd-file yields a
+#  # non-empty string; fail otherwise.
+#  client_send = "<; ${if !eq{PASSWDLINE}{}\
+#                      {}fail}\
+#                 ; ${extract{1}{::}{PASSWDLINE}}\
+#               ; ${sg{PASSWDLINE}{\\N([^:]+:)(.*)\\N}{\\$2}}"
+#.endif
 #####################################################
 ### end auth/30_exim4-config_examples
 #####################################################

Now all we have to do is to regenerate Exim’s configuration and restart the mail server:

$ update-exim4.conf
$ /etc/init.d/exim4 restart

You can then send a dummy email to test your mail system:

$ mail kevin@deldycke.com
Subject: This is an exim test
.
Cc:
Null message body; hope that's ok

And check in the log that everything’s fine:

$ tail -F /var/log/exim4/mainlog
2011-05-03 10:56:32 1QHBPE-0000ne-CW <= root@server.deldycke.com U=root P=local S=362
2011-05-03 10:56:36 1QHBPE-0000ne-CW => kevin@deldycke.com R=send_via_gmail T=gmail_smtp H=gmail-smtp-msa.l.google.com [209.85.227.109] X=TLS1.0:RSA_ARCFOUR_SHA1:16 DN="C=US,ST=California,L=Mountain View,O=Google Inc,CN=smtp.gmail.com"
2011-05-03 10:56:36 1QHBPE-0000ne-CW Completed

Comment supprimer des comptes mails secondaires @free.fr

Ça fait maintenant 10 ans que je suis client chez Free. Durant mes premières années de fréquentation du web, j’avais décidé de créer autant de comptes mails @free.fr que de variations possibles avec mon nom et prénom. Ne riez pas. Je l’ai vraiment fait. J’avais même poussé le vice jusqu’à doubler les variations avec le préfixe www. Pourquoi ? Pour avoir des espaces d’hébergement associés (Pages Perso) avec une jolie URL du type http://www.kevin.deldycke.free.fr.

Résultat: cela fait maintenant 10 ans que je traîne plus d’une vingtaine de comptes mails chez Free. J’ai eu mal le jour ou j’ai changé de crémerie pour aller chez Google. Pour continuer à recevoir tous les mails envoyés chez Free, j’ai mis en place une arborescence de comptes Gmail. C’était la seule solution pour contourner les limites de l’outil de récupération des messages, qui est limité à 5 comptes externes.

Il est temps de mettre fin à cette folie furieuse et de faire le ménage.

La procédure de suppression de ces comptes mails chez Free n’est pas compliqué. La première chose à faire, si ce n’est pas déjà fait, consiste à rattacher ses comptes mails à son compte principal. Facile, ça se fait dans l’interface d’administration du compte:

Ensuite, il faut se rendre sur la page d’assistance par mail. Là, il suffit d’écrire un message poli et précis, en choisissant bien Gestion des comptes de messagerie comme destinataire:

Et pour les paresseux, voici le modèle à copier/coller:

Bonjour,

Je possède de nombreux comptes mails secondaires. La grande majorité de ceux ci sont obsolètes et abandonnés. Aussi je souhaiterais les supprimer, ainsi que toutes les données qui leurs sont associées (listes de diffusions, comptes mails, pages personnelles, bases de données SQL, statistiques, livres d’or, …).

Voici la liste des comptes mails secondaires:
* compte1@free.fr
* compte2@free.fr
* compte3@free.fr
* compte4@free.fr
* compte5@free.fr

Pour information, tous ces comptes mails secondaires sont attachés à mon compte principal (ADSL) dont l’identifiant est “0123456789″ et le mot de passe “XXXXXXXX”.

Deux choses importante à noter:

  • Il faut préciser son mot de passe. Ben oui, c’est comme ça. Ça fait partie de la procédure de vérification.
  • Les opérateurs prenant en charge votre demande ne peuvent pas supprimer plus de 5 comptes mails à la fois. Donc si comme moi vous en avez beaucoup, il faudra faire plusieurs demandes. Et comme on ne peut pas faire plusieurs demandes en parallèle, il faudra s’armer de patience.

Une fois sur deux, votre demande va être immédiatement prise en charge, et vous recevrez la confirmation suivante:

Madame, Monsieur,

Nous prenons connaissance de votre eMail.

Tout d’abord permettez moi de vous remercier, pour avoir respecté la procédure concernant la suppression des comptes.

Celle-ci vient d’être en effet, traitée à l’instant.

Ces comptes seront réellement effacés de notre serveur principal dans 48 heures, temps qu’il faudra à tous les autres serveurs pour synchroniser leurs bases.

Nous vous remercions, de votre confiance.

En espérant que notre réponse vous apportera une entière satisfaction, nous restons à votre disposition pour tout éventuel complément d’information.

Au nom de toute l’équipe Free, nous vous prions de recevoir, Madame, Monsieur, nos sincères salutations.

L’autre moitié du temps, si votre compte à été créé avec une ligne Free Haut Débit, on vous enverra une demande de confirmation:

Madame, Monsieur,

Nous prenons connaissance de votre eMail.

Je vous remercie pour votre message, vous l’avez adressé au bon service.

IMPORTANT :
———-

NE RAJOUTEZ PAS LES COMPTES SUPPLEMENTAIRES NON DESIGNES A LA SUPPRESSION.

SI CEUX-CI SONT MENTIONNES, VOTRE DEMANDE NE SERA PAS TRAITEE.

C’est un compte qui a été créé sur FREE HAUT DEBIT.

La demande du mot de passe fait partie de la procédure, de suppression des comptes, par mesure de sécurité.

Sans cette information la suppression ne peut se faire.

Lorsque vous supprimez un compte, vous n’aurez plus accès aux services liés à ce compte (connexion RTC liée, e-mail, pages persos,).

Pour la suppression de votre ou vos comptes e-mail créé sur FREE HAUT DEBIT, nous vous serions gré de bien vouloir nous communiquer :
- Votre identifiant (numéro de téléphone)
- le mot de passe qui lui est rattaché.
- le login du ou des comptes supplémentaires à supprimer UNIQUEMENT.
- POUR QU’IL N’Y AIT PAS DE CONFUSION POSSIBLE, SI LE CAS EXISTE, SIGNALEZ MOI LES COMPTES AYANT UN LOGIN APPROCHANT AVEC CEUX A SUPPRIMER EXEMPLE : “VICTOIRES” & “VICTOIRE” MERCI !

Un login appartenant à un compte secondaire qui a été supprimé, ne pourra être repris ultérieurement pour création d’un nouveau compte, et ceci définitivement.

Je vous remercie.

En espérant que notre réponse vous apportera une entière satisfaction, nous restons à votre disposition pour tout éventuel complément d’information.

Au nom de toute l’équipe Free, nous vous prions de recevoir, Madame, Monsieur, nos sincères salutations.

Dans ce dernier cas, renvoyez un message de ce type pour relancer la procédure:

Bonjour, et merci pour cette demande de confirmation.

J’ai lu attentivement vos explications et vos mises en garde. Je comprends très bien ce que ma demande implique (suppression définitive des données et impossibilité de recréation des logins).

Aussi je vous confirme bien ma demande, à savoir la suppression des comptes suivants:
* compte1@free.fr
* compte2@free.fr
* compte3@free.fr
* compte4@free.fr
* compte5@free.fr

Pour information, tous ces comptes mails secondaires sont attachés à mon compte principal (ADSL) dont l’identifiant est “01234567890″ et le mot de passe “XXXXXXXX”.

Merci pour votre prévenance.

Et voila comment, après plusieurs semaines d’échanges avec le support de Free, j’ai simplifié ma vie ! ;) Dommage que la procédure ne soit pas automatisée…

Convert Lotus Notes’ nsf files to mbox with nlconverter

There is a great piece of software called nlconverter. It’s a tool designed to convert Lotus Notes’ .nsf files to mbox. It rely on win32′s COM/DDE API so it can only be used on Windows.

If you want to extract mails out of your .nsf database, this might be the tool you’re looking for. Bonus point: it’s written in Python ! ;)

Installing nlconverter and its dependencies

Here is how I installed nlconverter on a Windows 2000 (SP4) machine:

  1. First I downloaded and installed the official Python builds for Windows (2.6.6 precisely):




  2. Then Python for Windows extensions (build 214 for Python 2.6 in my case):



  3. Finally I had to download the latest icalendar archive, then extract the \iCalendar-1.2\src\icalendar folder to C:\Python26\Lib\site-packages\:
  4. Next step is to download nlconverter itself and extract it:

nlconverter GUI

First thing you have to do is to create an export of your mails as a .nsf database. Follow the previous link to get the instructions.

Now let’s convert this nsf to a mbox. nlconverter’s FAQ tells you to run the gui.exe program to perform the conversion.

Unfortunately it didn’t work for me:

So I tried the alternative approach by using the command line.

nlconverter command line

Again, most of the things I’m writing here are based on nlconverter’s FAQ:

  1. First, we have to download the notes2mbox.py script from nlconverter’s mercurial repository, as this file is not distributed in the winnlc-alpha-1.zip archive I unzipped previously. Let’s put notes2mbox.py in C:\winnlc-alpha-1\:
  2. Now we’ll modify the notes2mbox.py script to set the password (via the notesPasswd variable) and location (notesNsfPath variable) of the .nsf file. Here are the modifications I applied:
    --- notes2mbox.py.orig	2010-09-02 13:49:58.000000000 +0200
    +++ notes2mbox.py	2010-09-02 13:51:24.000000000 +0200
    @@ -14,8 +14,8 @@
     import NlconverterLib
    
     #Constantes
    -notesPasswd = "foobar"
    -notesNsfPath = "C:\\archive.nsf"
    +notesPasswd = "XXXXXXXXXXXXX"
    +notesNsfPath = "C:\\winnlc-alpha-1\\kevin-notes-big-backup-part-1.nsf"
    
     #Connection à Notes
     db = NlconverterLib.getNotesDb(notesNsfPath, notesPasswd)
    
  3. Before running the script, we have to register a Notes DLL used by nlconverter:
    regsvr32 "C:\Program Files\Notes\nlsxbe.dll"
    


    And make the Python interpreter available system-wide:

    C:\winnlc-alpha-1>SET Path=%Path%;C:\Python26
    
  4. Now we can run the notes2mbox.py script:
    C:\winnlc-alpha-1>C:\Python26\python.exe notes2mbox.py
    

If you’re lucky, you’ll get a nice mbox at the end of the process.

But I was not and the notes2mbox.py ended up with the following error:

Traceback (most recent call last):
  File "notes2mbox.py", line 21, in <module>
    db = NlconverterLib.getNotesDb(notesNsfPath, notesPasswd)
  File "C:\winnlc-alpha-1\NlconverterLib.py", line 43, in getNotesDb
    session = win32com.client.Dispatch(r'Lotus.NotesSession')
  File "C:\Python26\lib\site-packages\win32com\client\__init__.py", line 95, in Dispatch
    dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
  File "C:\Python26\lib\site-packages\win32com\client\dynamic.py", line 104, in _GetGoodDispatchAndUserName
    return (_GetGoodDispatch(IDispatch, clsctx), userName)
  File "C:\Python26\lib\site-packages\win32com\client\dynamic.py", line 84, in _GetGoodDispatch
    IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
pywintypes.com_error: (-2147221231, 'ClassFactory ne peut pas fournir la classe demand\xe9e', None, None)

As you can see, I tried hard to make nlconverter working, without any success. But this should not stop you to try. In fact I suspect the Lotus Notes installed on my machine to be crippled or corrupted (can’t really tell). So you may be more lucky than me. In any case, feel free to report any success or failure in the comment section below !

Subversion commits and mail activity stream in iCalendar

Last week I consolidated all my code in my GitHub repository. I stumble upon an old script I haven’t publicized yet: svn2ical.py.

This is a simple hack which get commit metadata out of a Subversion repository and generate an iCalendar file containing all commits of a given author. I used it back then to visualize in a calendar my commit activity. Nowadays this script is quite useless as services like Ohloh and GitHub provides great timeline and activity streams. But this script can still be useful for private repositories.

And in the same spirit of this script, I uncovered maildir2ical.py, a script that look in a maildir folder for mails sent by a particular author, then generate an iCal file based on mail dates.

Got “unsized object” errors with Debian’s Mailman ? Try this patch !

Last week I came across a showstopper bug on Mailman 2.1.9-7, the current version of Mailman package distributed with Debian Etch.

Here is the python traceback (from /var/log/mailman/error logfile) I get each time I’ve sent a mail to my brand new mailing-list:

Dec 20 01:20:04 2008 (14275) Uncaught runner exception: len() of unsized object
Dec 20 01:20:04 2008 (14275) Traceback (most recent call last):
  File "/usr/lib/mailman/Mailman/Queue/Runner.py", line 112, in _oneloop
    self._onefile(msg, msgdata)
  File "/usr/lib/mailman/Mailman/Queue/Runner.py", line 170, in _onefile
    keepqueued = self._dispose(mlist, msg, msgdata)
  File "/usr/lib/mailman/Mailman/Queue/IncomingRunner.py", line 130, in _dispose
    more = self._dopipeline(mlist, msg, msgdata, pipeline)
  File "/usr/lib/mailman/Mailman/Queue/IncomingRunner.py", line 153, in _dopipeline
    sys.modules[modname].process(mlist, msg, msgdata)
  File "/usr/lib/mailman/Mailman/Handlers/ToDigest.py", line 81, in process
    mbox.AppendMessage(msg)
  File "/usr/lib/mailman/Mailman/Mailbox.py", line 69, in AppendMessage
    g.flatten(msg, unixfrom=True)
  File "/usr/lib/mailman/pythonlib/email/Generator.py", line 101, in flatten
    self._write(msg)
  File "/usr/lib/mailman/pythonlib/email/Generator.py", line 136, in _write
    self._write_headers(msg)
  File "/usr/lib/mailman/pythonlib/email/Generator.py", line 182, in _write_headers
    header_name=h, continuation_ws='\t').encode()
  File "/usr/lib/mailman/pythonlib/email/Header.py", line 412, in encode
    newchunks += self._split(s, charset, targetlen, splitchars)
  File "/usr/lib/mailman/pythonlib/email/Header.py", line 297, in _split
    elen = charset.encoded_header_len(encoded)
  File "/usr/lib/mailman/pythonlib/email/Charset.py", line 354, in encoded_header_len
    raise repr(s)
TypeError: len() of unsized object

Dec 20 01:20:04 2008 (14275) SHUNTING: 1229732404.1069181+dcd89a08bf7911dac2db804b76cd42d20564c71c

Here is the corresponding (anonymized) mail sent to the mailing list from a Gmail account:

Received: by 10.180.244.13 with HTTP; Fri, 19 Dec 2008 16:32:22 -0800 (PST)
Message-ID: <1f7b086f0812192632x7427c0f7u2048609ddd50673@mail.gmail.com>
Date: Sat, 20 Dec 2008 01:32:22 +0100
From: "Kevin" <kevin@my-domain.com>
To: my-ml@lists.my-domain.com
Subject: sqdfqsdfqsfd
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64
Content-Disposition: inline
Delivered-To: kevin@my-domain.com

LS0KS2V2LgogIOKAoiBiYW5kOiBodHRwOi8vY29vbGNhdmVtZW4uY29tCiAg4oCiIGJsb2c6IGh0
dHA6Ly9rZXZpbi5kZWxkeWNrZS5jb20K

And now my hackish tale. Based on a quick look at Mailman’s source code, I made an educated guess that this error is just a side effect of the wrong assumption that the s variable in the Charset.encoded_header_len() method is always a string. So I came up with the following evil patch to handle (gracefully, I hope) the case of s being None.

Here is the resulting patch of my python-fu:

--- /usr/lib/mailman/pythonlib/email/Charset.py.orig   2008-12-28 19:46:23.000000000 +0100
+++ /usr/lib/mailman/pythonlib/email/Charset.py        2008-12-20 01:42:37.000000000 +0100
@@ -351,6 +351,7 @@
             lenqp = email.quopriMIME.header_quopri_len(s)
             return min(lenb64, lenqp) + len(cset) + MISC_LEN
         else:
+            return s is not None and len(str(s)) or 0
             return len(s)

     def header_encode(self, s, convert=False):

And it do the trick ! Of course I can’t guarantee that this patch is the way to definitely fix the bug. And it may corrupt data. So use it only if you’re as crazy as me ! :D

But I know, I know… As a responsible and serious hacker (sigh), I should report this bug to the Debian or Mailman project. But I’m still not familiar with Dedian’s way of reporting bugs (and to be honest, I feel lazy these days :p ). Maybe, one day…