marian gunkel
career
                                                

 

Syncmail.php - Synchronising a mailing list and a web based discussion forum

Author: Marian Gunkel
Contact: gunkel@student.hu-berlin.de
Date: 25th January 2002
Last change: 26th January 2002

Synchronisation?
It doesn't matter what medium someone uses: a posting via a mailing list will show up in a web based forum, too. A posting in a web based forum will be sent to the mailing list as well. Discussion threads are possible.

What and Why

This document describes how to setup the synchronisation of a web based discussion forum (Phorum version 3.2.11) and an ordinary majordomo mailinglist (ML).
I mainly deal with problems that can and will occur in the process as well as solutions.

For a long time I have been obsessed with an all-round solution for computer-based communications. Users need to decide themselves what medium they are using to communicate. According to their work and life style, users want to choose if they are using email or web based discussion groups. With the solutions described, it is easier to communicate. However, I stick to asynchronous media for the time being (one reason is: I hate chats :-)).

Currently, the solution works in a test environment with only little bugs. In a production environment, however, more bugs will show up. I'll do my best to debug the system and report all changes here and at the Phorum homepage.

I assume you are familiar with both the phorum software (available at http://www.phorum.org) and the majordomo mailing list manager (available at http://www.greatcircle.com/majordomo/). While you should have installed phorum and have it running, you can use an ordinary majordomo mailinglist (you need to be able to subscribe email addresses to it).

Chapters:

1 - Sending Phorum-postings via email to a mailing list
2 - Sending mail from a mailing list to Phorum
3 - Ongoing Problems
4 - The files (cm.pl, syncmail.php)
5 - Thanks to Phorum, Ralph Hoehn, Jan-Klaas Kollhof

 

1 - Sending Phorum-postings via email to a mailing list

Phorum has already built in the possibility to send postings to an email address. This can be the address of a mailing list. You can also specify the "return address" which will show up as REPLY-TO: header line in an email. And you can specify the ML tag (which is usually added to the subject by the ML software, i.e. [foldingboats]).
The ML tag will be stripped off the subject when posting a ML email.

Problem No. 1:

The FROM-field in an email sent by Phorum contains the email-address of the poster (who is not likely to be subscribed to the ML). Majordomo is not accepting emails by non-subscribers. Those emails will bounce (be rejected and sent to the list-owner).

Solution - overview:

Phorum sends all posts as email to a "neutral email account" (dummy_address@gmx.net). It also sets the FROM: header to dummy_address@gmx.net.
The "neutral email account" dummy_address@gmx.net is filtering all emails and forwards the right emails to majordomo (dummy_address@gmx.net is subscribed to majordomo).
In my case, I am using gmx.de, a German freemail provider. You need header-filtering and instant forwarding to other email-addresses.

Solution - what to do:

1. Phorum: change file include/post.php
add one line in the range of line 380:
$ebody = '';
if ($id) {
$ebody.="This message was sent from: $ForumName.\n";
$ebody.="Original author: $plain_author <$from_email>\n"; // added so people know who's the author
$ebody.="<$forum_url/$read_page.$ext?f=$num&i=$id&t=$thread> \n";
$ebody.="----------------------------------------------------------------\n\n";

In the range of line 390, change the line
"\nFrom: $plain_author <$from_email>" .
to
"\nFrom: $plain_author <dummy_address@gmx.net>" .

2. Phorum admin area - Forum Properties
mailing list address: dummy_address@gmx.net
return address: dummy_address@gmx.net
[Problem: if a poster has checked email replies to his/her postings and answers those emails, they will be sent to dummy_address@gmx.net and then forwarded to the ML but the email will bounce - needs to be solved]
mailing list tag: foldingboats [just an example; enter your mailing list tag if you have one]

3. setup dummy_address@gmx.net (not this address, of course ;-))
- set up email account
- install filters:

  • delete all emails with a typical majordomo-header line
    (in my case, I delete all emails with "Sender: owner-[name of ML]@domain.com" in the header)
  • delete all emails with "TO: dummy_address@gmx.net" AND "TO:ML@domain.com"
    (this is to avoid double postings when someone hits "reply all" in the email program)
  • forward all emails with *no* ML-header to the ML

4. set up phorum@domain.com [I'll talk about this one later. It is the main adress for receiving emails that are to be posted into phorum]
5. subscribe phorum@domain.com and dummy_address@gmx.net to the ML.
As a list-owner, you should have the necessary password. Send an email to majordomo@domain.com with the following two lines:
approve [password] subscribe [ML name] phorum@domain.com
approve [password] subscribe [ML name] dummy_address@gmx.net

Now we can send all phorum postings to a ML (and have installed some features that are needed in the second step: sending emails to phorum).

 

2 - Sending Phorum-postings via email to a mailing list

This is the trickier part. Something where most web-based discussion forums just give up. Not phorum. A script called phorummail.php (in the scripts folder) is doing all the work if you got a well designed webserver and mailserver.
Problem: mine isn't well designed. So I "invented" a procedure to make it work.

Problem No. 1:

- My .procmailrc is not allowed to execute a script.
Phorummail needs to have an email "piped" (sent as standard input) to it, together with some info where the phorum is, where phorummail is and what forum the email should go to.
I work with procmail, a UNIX software that processes and filters mail. The filter information is stored in a special file called .procmailrc [note the leading dot]. Unfortunately, my procmail is not allowed to execute a script (if you "pipe" an email to a script, you are technically executing that script).

Solution:

So, I decided to put emails for a certain phorum into a special folder. As I mentioned, phorum@domain.com is subscribed to the ML and all emails are being sent to it.

Here is my .procmailrc recipe:

LOGFILE =$HOME/Procmail/procmail.log
LOGABSTRACT = "all"
VERBOSE = "on"

:0
* ^TO_phorum@domain.com
* ^Subject:.*foldingboats
www/phorum/mail/forum1/

:0
* ^TO_phorum2@domain.com
* ^Subject:.*foldingboatbuilders
www/phorum/mail/forum2/

This will log everything in the file Procmail/procmail.log [you can delete those first three lines after everything runs smoothly]. It will store emails to phorum@domain.com with the word foldingboats in the folder www/phorum/mail/forum1/ [note the relative path]. Inside the folder forum1, the first email will make three folders new, cur and temp. In the folder new you will find the emails.
How do we avoid mail loops (Phorum-posting -> dummy_address@gmx.net -> ML -> phorum@domain.com -> Phorum-posting -> dummy_address@gmx.net -> ML -> phorum@domain.com -> Phorum-posting .....)?
Emails with phorum-headers in them [i.e. sent from phorum to dummy_address@gmx.net] will be both deleted via .procmailrc filters [I have not written them yet] and via phorummail/syncmail. All other ML-messages are forwarded to the folder mail/forum1.

Problem No. 2:

How do we get the emails into phorummail?

Solution:

A script called syncmail.php is doing all the work phorummail does as well as some extra things. It is basically phorummail with a couple of extensions. Syncmail.php is included in the files phorum/list.php and phorum/index.php.
list.php, lines 26 and 27:
26 //sync mails
27 $s=file("http://www.domain.com/phorum/scripts/syncmail.php?f=$num");

index.php, lines 20-22:
20 //sync all the mails by calling the syncmail.php
21 $s=file("http://www.domain.com/phorum/scripts/syncmail.php?f=1");
22 $s=file("http://www.domain.com/phorum/scripts/syncmail.php?f=2");

So, if someone is accessing the phorum via a browser, the following steps are executed:

  • syncmail will execute a perl-script cm.pl that chmods the new mail messages in the folder mail/forum1 to 644.
  • it will write each email into a new temporary file to avoid messing up with new incoming emails
  • it will do the same as phorummail does with each email (checking emails, stripping certain things and posting them into the DB.
  • above the normal phorummail functions, syncmail is also stripping off the ML footer as well as decoding MIME-encoded email subjects (which would otherwise look messy on the phorum).
  • it will delete the temporary email.

This works better the more hits your phorum gets (the more times index.php and/or list.php are called). As soon as someone hits the phorum, all new emails will be included into the DB and show up as new postings.

 

3 - Ongoing Problems

Currently, there is one scenario where I have found no solution:

- a poster has asked for email-notification and is replying to an answer via email. Should this go into the ML (via REPLY-TO: gmx.de) and then into the Phorum or should it go directly to phorum@mydomain.com, posted into the DB and then sent to the ML?
-> At the moment, he/she will reply to dummy_address@gmx.net (which is both FROM and REPLY-TO). This will forward the email to the ML but the ML will bounce this message (since there is FROM: poster_email_address).

I am also still finetuning the filters in gmx.de and the .procmailrc file. I would like to do all the filtering via these instances in order to avoid phorum-confusion with dublicate messages and the like.

Postings via email are sometimes a bit messy, I need to work on stripping off the MIME-parts of those emails.

4 - The Scripts:


-- cm.pl:

#!/usr/bin/perl
$mailPath = "/home/domain/www/phorum/mail/forum";
@phorumNums = ('1','2');

print"Content-type: text/html\n\n";

foreach $n (@phorumNums)
{
#reading in the current filenames
opendir(dir, "$mailPath$n/new");
while($fle=readdir(dir))
{
if (!(-d $fle))
{
chmod 0644, "$mailPath$n/new/$fle";
}
}
closedir(dir);
}
print "cm OK";

//cm.pl ends here .......................


-- syncmail.php:

<?PHP
//make sure script won't be stopped
ignore_user_abort();

// some vars
$PhorumMail=true; // Do not touch.
$phorum_path="/home/domain/www/phorum"; // this the path to the main Phorum dir.
$admin_email="phorum@domain.com"; // This should match the default email for Phorum.

$plChmodPath="http://www.domain.com/phorum/scripts/cm.pl"; //perl script to chmod the mails
$s=file($plChmodPath);

//$verbose=false;
$num="" + join("", $argv); //forum-number

@chdir($phorum_path);
// check for all we need.
include "common.php";
include "include/post.php";
$phorummailcode = strtolower($PhorumMailCode);
$email=$DefaultEmail;

$postMailPath="$phorum_path/mail/forum$num/new"; //path to the folder mails are stored in

$tmpPath="$postMailPath/" . microtime();
mkdir($tmpPath, 0777);
$handle=opendir($postMailPath);
do
{
$fName=readdir($handle);
if (is_file("$postMailPath/$fName"))
{
//move all files into the new directory
rename("$postMailPath/$fName", "$tmpPath/$fName");
}
}while($fName != "");
closedir($handle);

//parse tmp-folder and post the mails
//when finished delete the folder
$handle=opendir($tmpPath);
do
{
$fName=readdir($handle);
//read and post all files but the clear-file
if (is_file("$tmpPath/$fName"))
{
//save name in .clear file for deleting it later
// Get input
$stdin=file("$tmpPath/$fName");
$message=implode("", $stdin);
if($message=="" || empty($num))
{
if(empty($num))
{
$error ="PhorumMail could not run for the following reason(s):\n\n";
if(empty($num))
$error.=" The forum id was not specified.\n";

$error.="\n";
$error.="An example of a correct PhorumMail command line is:\n\n";
$error.=" /usr/local/bin/phorummail forum=5 path=/usr/home/www/phorum\n\n";
$error.="A copy of the message is included below.\n\n";
$error.="====================================================================\n\n";
$error.="$message";
mail($email, "PhorumMail failure", $error, "From: PhorumMail <$email>\nReturn-Path: Phorummail <$email>");
}
}else
{
// read in headers
reset($stdin);
// Would be nice to use array_shift() here, but that's PHP4 only.
while (list($linenum,$line) = each($stdin))
{
$line = trim($line);
$parts=explode(": ", $line);
$type=$parts[0];
unset($parts[0]);
$value=trim(implode(": ", $parts));
// Use strtolower to avoid case problems
$eHeaders[strtolower($type)]=$value;
unset($stdin[$linenum]);
if (empty($line))
{
break;
}
}

// read in the body

## We do this in the post function now.
$body=str_replace("\r\n", "\n", trim(implode("", $stdin)));

//strip off subscriptions lines
$body=str_replace("#########################################################", "", $body);
$body=str_replace("Foldingboats Mailing List - All postings copyright the author and not to be\n", "", $body);
$body=str_replace("reproduced outside Foldingboats or Foldingboats archives without author's permission\n", "", $body);
$body=str_replace("Submissions: foldingboats@pouchboats.com\n", "", $body);
$body=str_replace("Subscriptions: majordomo@pouchboats.com\n", "", $body);

print "<pre>\n"; print_r(get_defined_vars()); print "</pre>\n";

$body.="------------------------------------------\n";
$body.="Posted to Phorum via PhorumMail\n";


if (empty($eHeaders["date"]))
{
$datestamp = date("Y-m-d H:i:s");
} else
{
$datestamp = date("Y-m-d H:i:s");
// $datestamp = date("Y-m-d H:i:s", strtotime($eHeaders["date"], time()));
// changed because posted emails had a silly date (like 5 days ago)
}

if(@ereg("([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)", $eHeaders["received"], $regs))
{
$ip=$regs[0];
} else
{
$ip="PhorumMail";
}
$host = @gethostbyaddr($ip);

if (empty($eHeaders["from"]))
{
$author='';
$email='';
} else
{
if(ereg('"([^"]+)" <([^>]+)>', $eHeaders["from"], $regs))
{
$author=$regs[1];
$email=$regs[2];
} else if(ereg('([^<]+)<([^>]+)>', $eHeaders["from"], $regs))
{
$author=trim($regs[1]);
$email=$regs[2];
} else if(substr($eHeaders["from"],0,1)=="<")
{
$author=substr($eHeaders["from"], 1, -1);
$email=$author;
} else
{
$author=$eHeaders["from"];
$email=$eHeaders["from"];
}
}

$thread = 0;
$parent = 0;


if (empty($eHeaders["subject"]))
{
$subject = '';
} else
{
$subject = trim($eHeaders["subject"]);
// Strip out [$ForumEmailTag] if it exists.
// Use eregi, because some software seems to tamper with the case of the tag!
if (eregi("^([^[]*)\\[$ForumEmailTag\\](.*)$", $subject, $regs))
{
$subject = trim($regs[1]) . ' ' . trim($regs[2]);
$subject = trim($subject);
}
// Look for "[forum:thread:parent]" at the end of the subject
// We aren't actually using these anymore, but their presence would
// screw up the threading.
if (ereg('^(.+) +\[([0-9]+):([0-9]+):([0-9]+)\]$', $subject, $regs))
{
$subject = $regs[1];
// $forum=$regs[2];
// $thread=$regs[3];
// $parent=$regs[4];
}
}

// Decode characters like German Umlaute in subject
$subject = imap_mime_header_decode($subject);
$subject = $subject[0]->text;


$forum = $num;
if (!empty($eHeaders["x-phorum-$phorummailcode-forum"])&& (strcmp($eHeaders["x-phorum-$phorummailcode-forum"],$ForumName) == 0))
{
if (!empty($eHeaders["x-phorum-$phorummailcode-thread"]))
{
$thread = $eHeaders["x-phorum-$phorummailcode-thread"];
}
if (!empty($eHeaders["x-phorum-$phorummailcode-parent"]))
{
$parent = $eHeaders["x-phorum-$phorummailcode-parent"];
}
}

$action="post";
$toaddress = empty($eHeaders["to"]) ? '' : $eHeaders["to"];

$msgid = empty($eHeaders["message-id"]) ? '' : $eHeaders["message-id"];

// The email this is a reply to should be in In-Reply-To, but some
// mailers seem to use References instead.
// Both fields can have multiple message ids in them, so just grab the first.
$inreplyto = '';
if (@ereg('^(<[^>]+>)', $eHeaders["in-reply-to"], $regs))
{
$inreplyto = $regs[1];
} else if (@ereg('^(<[^>]+>)', $eHeaders["references"], $regs))
{
$inreplyto = $regs[1];
}

$IsError = check_data($host, $author, $subject, $body, $email);
if (!empty($IsError))
{
violation();
}

$author=trim($author);
$subject=trim($subject);
$email=trim($email);
$body=chop($body);
list($author, $subject, $email, $body) = censor($author, $subject, $email, $body);

$author = addslashes($author);
$email = addslashes($email);
$subject = addslashes($subject);
$body = addslashes($body);

$plain_author=stripslashes($author);
$plain_subject=stripslashes(ereg_replace("<[^>]+>", "", $subject));
$plain_body=stripslashes(ereg_replace("<[^>]+>", "", $body));

$author = htmlspecialchars($author);
$email = htmlspecialchars($email);
$subject = htmlspecialchars($subject);

$org_attachment = '';

if(($email==$ForumModPass && $ForumModPass!="") || ($email==$Password && $Password!=""))
{
$ForumModeration='';
$email=$ForumModEmail;
$author = "<b>$author</b>";
$subject = "<b>$subject</b>";
$body="$body";
$host="<b>$ForumStaffHost</b>";
}
else
{
$body=eregi_replace("</*HTML>", "", $body);
if($ForumAllowHTML=="Y")
{
$body="$body";
}
}

// If there are no Phorum headers, put it in the database.
if(empty($eHeaders["x-phorum-$phorummailcode-version"]))
{
post_to_database();
}

// Notify people who wanted replies sent to them
post_to_email();
}
//delete the posted file
unlink("$tmpPath/$fName");
}
}while($fName != "");
closedir($handle);
//delete the tmp-folder
rmdir($tmpPath);
?>

5 - thanks to Phorum, Ralph Hoehn, Jan-Klaas Kollhof

I want to thank the Phorum development team who have been designing a marvellous piece of software. Ralph Hoehn of www.PouchBoats.com gave the necessary funds and structure to develop the synchronisation. Jan-Klaas Kollhof has been planning and coding syncmail.php and cm.pl as my PHP and Perl knowledge is veeery small ;-).

 

 

    gunkel@student.hu-berlin.de
26th January 2002