Perl upgrade broke majordomo
#81 Henry, Wednesday, 02 March 2011 2:06 PM (Category: Email)
(Tags: majordomo)

About five years ago I needed mailing list software for two clubs that I work with. Their needs were simple. One club wanted broadcast email and the other club needed a simple emailing system between members. The volume isn't large. The first club does one email per month broadcast to 300 people, and the second one does four emails per month between 30 people. This is not heavy traffic. I didn't need complete controls by email or by web. I needed something simple that worked for unsophisticated users. And something I could make work easily with sendmail, my MTA of choice.

I looked around for mailing software and it came down to two choices - majordomo and mailman. GNU mailman looked good, but the GNU guys seem hostile to sendmail and the instructions were not there. Majordomo looked a bit old, and it was coded in Perl 4, but there were good instructions for integrating with sendmail, and it did what I wanted for my users. So I installed majordomo, and it's been running quite well ever since. My users use it without thinking, and all is well. It's been running neatly for years.

As the years went by, I integrated the majordomo mailing list into my users' databases and websites. They can easily manage the list based on their club rosters, and it works nicely for them.

Last month, "something happened". It always works that way. Something's been running well for years then "something happens" and I have an emergency. I keep my Slackware servers upgraded to current. Recently, Perl upgraded to 5.12.2. And when it did, majordomo stopped working. I didn't notice it at first, and probably several weeks went by without anyone noticing it. But when my users needed to send email, they sure noticed and it became an emergency for me. It had to work, and it better be soon.

I looked in the majordomo logs. Oh, that's right, I remember now, I could not get the logging to work so I have an empty file. That's no use. I looked in the sendmail logs. I can see an email come in, I can see it hit the aliases and get transferred to the majordomo resend program and that's the last you see of it.

So the problem is in majordomo. I ran resend manually and found some warnings about old features deprecated. Uh oh. How old is majordomo? I went looking. Last upgrade was 2000. Ouch. It's old. I see that there is a majordomo2 project, but I don't know what the status is. They are rewriting it in Python, leaving old Perl 4 behind. That's nice, but doesn't help my immediate problem. So majordomo is in Perl 4, and Perl 5 just got upgraded and something broke.

I fiddled with it a bit, but couldn't make any sense of the problem. So I ignored it for a few days while other things happened, and then my users got on my back again and it needed to be fixed ASAP. I tried a few different things first.

I looked at the new majordomo2 and couldn't quite see how to integrate in into the databases and websites that I have already in place. They don't use a flat file of email addresses, they use a database of their own. I looked at mailman again, and still couldn't see how to get it to work with sendmail. I noticed that both these programs have their own website controls and appear to be very sophisticated. This makes it a lot harder to integrate into my existing systems.

My users got desperate and angry, and emails had to be sent out that night. I had hours to find a solution. I looked at wilder quick solutions. I tried writing a Perl program to read the list of email addresses and push them out. It failed because sendmail did not like something about them, and stuffed them into the clientmqueue directory and forgot about them. I tried implementing a simple mailing list in sendmail's aliases file with the :include: directive. That failed and I don't know why. I wrote a PHP web-based page so they could fill in the email contents and have it mail it out. Success. It worked. Until they asked how to put in their multipart text + HTML document, with a PDF attachment. Fail. I couldn't get PHP to do MIME in the time I had left.

So finally, after exhausting all my mediocre skills at system administration, and it was about 2am, I decided to go back to what I am good at - programming and debugging. I started looking at majordomo again. I switched in a dummy list of emails to test against.

I worked out how to run it at the command line, and saw lots of logs sent to stderr. I noticed that it was checking to see if the sender was in the list of allowable sender and then it stopped there. I started digging into the Perl in the resend program where it did this comparison.

It was comparing the email address from the email:

Sending User <>

against the email address in the file of allowable senders:

and failing. And when this failed, it meant that the sender wasn't on the list of allowables, so majordomo would stop there and not send any emails out. This looked promising.

It called ParseAddrs on both addresses and then compared them. ParseAddrs looked like this:

sub main'ParseAddrs {
    local($_) = shift;
    1 while s/([^()]*)//g;          # strip comments
    1 while s/"[^"]*"s//g;             # strip comments"
    split(/,/);                         # split into parts
    foreach (@_) {
        1 while s/.*<(.*)>.*/$1/;

I stared at this for a while, and the WTF level went to 3. WTF is that Perl 4 main' construct? WTF are those "1 while" statements? I worked out what those two were about. And then finally "WTF is it using @ like that for?" That didn't make sense. @ is the magic variable array for all the parameters passed into a function. But they were using it as the default magic variable array produced from a split. Obviously this worked in earlier versions of Perl (like a month ago) but the latest version must have cleaned up old ambiguities and so it stopped working.

I went for simplicity and cleaned it up like this:

sub main'ParseAddrs
  local($_) = shift;
  local (@pa);

  1 while s/([^()]*)//g;            # strip comments
  1 while s/"[^"]*"s//g;               # strip comments"

  @pa = split(/,/);                     # split into parts
  foreach (@pa)
    1 while s/.*<(.*)>.*/$1/;

Very simple. Stop using @_ and use a local array @pa.

I re-ran my tests and my test email, and this time I was recognised as a valid sender and my little test email was sent to the recipients of my little test list. Success. I ran some other tests, and they worked. I stripped out my debug statements, and tested again and it still worked. I got my irritated user to send her big multipart email with PDF invitation, and watched the sendmail logs and it worked and sent that email out to her 300 recipients.

I was greatly relieved.

Now it's working, the pressure is off me, and I can take my time to find a permanent solution. Majordomo is too old, and I need to get off it. I could try majordomo2, or I could try and learn mailman, or I could do something much more drastic. I think I will do something more drastic. I will rewrite majordomo in Python, strip out the stuff I don't need, and allow for closer integration into my websites. This will give me something I can control better, and it will give me more knowledge.