Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
Looking for a mailing list server with a MySQL backend
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Networking & Security
View previous topic :: View next topic  
Author Message
joemc91
n00b
n00b


Joined: 04 Feb 2004
Posts: 38

PostPosted: Mon Jun 14, 2004 12:34 am    Post subject: Looking for a mailing list server with a MySQL backend Reply with quote

I'm trying to setup a mailing list server and have made no headway. I've tried mailman integrated with postfix but it didn't do anything I wanted it to. I'd like to use an MTA that has a MySQL backend for aliases as well as a mailing list that uses a MySQL backend. I have been looking at qmail for the MTA because of the extensive documentation on it (minus the SQL part). The only mailing list manager I have found is ezmlm-idx-mysql. This has very little documentation on it so I'm sort of worried about using it. My only other option (AFIK) is to hack together my own mailing list manager with an SQL backend in some scripting language, since I really don't have to time to go learning a new language right now. Any recommendations?
Back to top
View user's profile Send private message
joemc91
n00b
n00b


Joined: 04 Feb 2004
Posts: 38

PostPosted: Tue Jun 22, 2004 3:09 am    Post subject: Reply with quote

I couldn't find any mailing list managers with a database backend that I like, so I rolled my own simple one. Not very powerful (doesn't have to be), but it already supports threaded archiving, user lists, multiple domains, and joining requests by email. I know it's a small feature list, but not too bad IMO for a short PHP script.

The database has 7 tables: admins (administrative users, will eventually be used for managing purposes), archive (stores all archived messages), domains (all virtual domains hosted by the list), local_aliases (the two aliases that the list uses "sendmail.php" and "request.php"), mailing_list (the mailing lists matching addresses to the sendmail and request scripts), master_list (list of all addresses hosted), subscriptions (email addresses subscribing to mailing lists. Used to authorize emails to pass through the list.

Here is the database script, contains only classes used to access the database (no commenting yet, bad habit):
Code:
database.php
<?php

/*The purpose behind the database class is to provide easily changed functions for all databases on the website
  this allows for an easy change over in the future to another database such as mysql*/

# This file only provides the functionality to perform actions on the mailing list database.

class Database
{
        var $dataResource;

        function Database()     #initialize the database connection and all variables
        {
                $this->dataResource = mysql_connect("localhost",user,password);
        }

        function closeDatabase()        #close the database connection.  The reason for calling this function instead of the mysql function directly is to be able to change the database type with minimal effort
        {
                mysql_close($this->dataResource);
        }

        function queryDatabase($query)  #query the database, parameter should be in the form of and SQL query, such as SELECT * FROM tablename
        {
                return mysql_db_query("postfix",$query,$this->dataResource);
        }

        function fetchRow($result)      #objects are returned because they are easier to deal with than arrays
        {
                return mysql_fetch_object($result);
        }

        function freeResult($result)    #frees up a result resource identifier, if you need to
        {
                mysql_free_result($result);
        }

        function numRows($result)       #get number of rows in a result
        {
                return mysql_num_rows($result);
        }

        function valueExists($query)
        {
                $result = $this->queryDatabase($query);
                if ($this->numRows($result) == 0)
                { return false; }
                else
                { return true; }
        }

        function max($table,$field)     #returns the max value of a field name from a table
        {
                $result = $this->queryDatabase("SELECT MAX($field) FROM $table");
                $maxarray = mysql_fetch_row($result);
                return $maxarray[0];
        }

        function toMySQLString($text)
        {
                return mysql_escape_string($text);
        }
}                                       #end class database

class MailingList extends Database
{
        function MailingList()
        {
                $this->Database();
        }

        function acceptMailer($email,$listname)
        {
                return $this->valueExists("SELECT address FROM subscriptions WHERE address LIKE '$email' AND subscription LIKE '$listname'");
        }

        function subscribe($email,$listname)
        {
                if (!$this->acceptMailer($email,$listname) && $this->valueExists("SELECT address FROM master_list WHERE address LIKE '$listname'"))
                { $this->queryDatabase("INSERT INTO subscriptions (address, subscription) VALUES ('$email', '$listname')"); }
        }

        function unsubscribe($email,$listname)
        {
                if ($this->acceptMailer($email,$listname))
                { $this->queryDatabase("DELETE FROM subscriptions WHERE address LIKE '$email' AND subscription LIKE '$listname'"); }
        }

        function update($oldemail,$newemail,$listname)
        {
                if ($this->acceptMailer($oldemail,$listname))
                { $this->queryDatabase("UPDATE subscriptions SET address = '$newemail' WHERE address LIKE '$oldemail' AND subscription LIKE '$listname'"); }
                else
                { $this->subscribe($newemail,$listname); }
        }

        function getEmailsInList($listname)
        {
                $result = $this->queryDatabase("SELECT address FROM subscriptions WHERE subscription LIKE '$listname'");
                $emails = array();
                while ($row = $this->fetchRow($result))
                {
                        $emails[] = $row->address;
                }
                return $emails;
        }
}

class Archive extends Database
{
        function Archive()
        {
                $this->Database();
        }

        function archiveMessage($listname,$from,$subject,$message_id,$parent_id,$body)
        {
                $body = $this->toMySQLString($body);
                $subject = $this->toMySQLString($subject);
                if ($parent_id == "") { $parent_id = "top"; }
                $query = "INSERT INTO archive (mailing_list, from_address, subject, message_id, parent_id, body) VALUES ('$listname','$from','$subject','$message_id','$parent_id','$body')";
                $this->queryDatabase($query);
                return mysql_error();
        }
}

?>


This is the sendmail script. All mail passing through the lists is processed by this (again no commenting, bad habits):

Code:
sendmail.php
#!/usr/bin/php
<?
        function getTextBodyOnly($part)
        {
                if (strcasecmp($part->ctype_primary,"multipart") == 0)
                { return getTextBodyOnly($part->parts[0]); }
                else { return $part->body; }
        }

        function encodePart($email)
        {
                $content['content_type'] = $email->ctype_primary . "/" . $email->ctype_secondary;
                $mainpart = new Mail_mimePart('',$content);
                $subparts = array();
                foreach ($email->parts as $part)
                {
                        $params = array();
                        if (strcasecmp($part->ctype_primary,"multipart") == 0)
                        {
                                $newpart = encodePart($part);
                                $params['content_type'] = $newpart['headers']['Content-Type'];
                                $body = $newpart['body'];
                        }
                        else
                        {
                                $params['content_type'] = $part->ctype_primary . "/" . $part->ctype_secondary;
                                $params['encoding'] = $part->headers['content-transfer-encoding'];
                                if ($part->disposition != "")
                                {
                                        $params['disposition'] = $part->disposition;
                                        $params['dfilename'] = join("",$part->d_parameters);
                                }
                                $body = $part->body;
                        }
                        $subparts[] = $mainpart->addSubPart($body,$params);
                }
                return $mainpart->encode();
        }

        include "Mail/mimeDecode.php";
        include "Mail/smtp.php";
        include "Mail/mimePart.php";
        include "database.php";

        $mailing_list = new MailingList();

        $emailarray = file("php://stdin");
        $email = join("",$emailarray);

        $decoder = new Mail_mimeDecode($email);
        $emailparts = $decoder->decode('include_bodies');

        $from = $emailparts->headers['from'];
        preg_match("<[\d\w_-]+@[\d\w-_]+\.[\w]+>",$from,$matches);
        $from = $matches[0];

        if ($mailing_list->acceptMailer($from, $emailparts->headers['x-original-to']))
        {

                $headers = array();
                $headers['To'] = $emailparts->headers['to'];
                $headers['From'] = $emailparts->headers['from'];
                $headers['Reply-To'] = $emailparts->headers['x-original-to'];
                $headers['Subject'] = $emailparts->headers['subject'];
                $headers['Message-ID'] = $emailparts->headers['message-id'];
                $headers['Content-Type'] = $emailparts->ctype_primary . "/" . $emailparts->ctype_secondary;

                $archive = new Archive();
                $textonlybody = getTextBodyOnly($emailparts);
                $archive->archiveMessage($headers['Reply-To'],$headers['From'],
                        $headers['Subject'],$headers['Message-ID'],$emailparts->headers['in-reply-to'],$textonlybody);

                if (strcasecmp($emailparts->ctype_primary,"text") == 0)
                {
                        $body = $emailparts->body;
                }
                if (strcasecmp($emailparts->ctype_primary,"multipart") == 0)
                {
                        $newemail = encodePart($emailparts);

                        $body = $newemail[body];
                        $newemail['headers']['Mime-Version'] = '1.0';
                        $headers = array_merge($headers,$newemail[headers]);
                }

                $emailsinlist = $mailing_list->getEmailsInList($emailparts->headers['x-original-to']);
                $recipients = join(", ",$emailsinlist);

                $mail_object =& Mail::factory('smtp');
                $mail_object->send($recipients,$headers,$body);
        }
        else
        {
                $request = str_replace("@","-request@",$emailparts->headers['x-original-to']);
                mail($emailparts->headers['from'],"Permission denied to " . $emailparts->headers['x-original-to'],
                        "Your email address does not have permission to send mail to this mailing list, you must be a member to send mail to this address.\n\n" .
                        "To become a member, send a message to $request with a subject of \"subscribe\"\n\n" .
                        "This message automatically generated by PHP Mailing List\n\n" .
                        "From: " . $emailparts->headers['x-original-to']);
        }
?>


Here is the request script, any mail sent to a list-REQUEST (emphasis mine) is processed by this (no comments, see above):

Code:
request.php
#!/usr/bin/php
<?
        include "Mail/mimeDecode.php";
        include "database.php";

        $emailarray = file("php://stdin");

        $mailing_list = new MailingList();
        $email = join("",$emailarray);

        $decoder = new Mail_mimeDecode($email);
        $emailparts = $decoder->decode('include_bodies');

        $from = $emailparts->headers['from'];
        preg_match("<[\d\w_-]+@[\d\w-_]+\.[\w]+>",$from,$matches);
        $from = $matches[0];

        $subject = $emailparts->headers['subject'];
        $list = $emailparts->headers['x-original-to'];
        $list = str_replace("-request@","@",$list);

        if (strcasecmp($subject,"subscribe") == 0)
        {
                $mailing_list->subscribe($from,$list);
                mail ($from,"You have succesfully joined the $list mailing list","This is an automatically generated message","From: " . str_replace("@","-request@",$list));
        } else
        if (strcasecmp($subject,"unsubscribe") == 0)
        {
                $mailing_list->unsubscribe($from,$list);
                mail ($from,"You have succesfully left the $list mailing list","This is an automatically generated message","From: " . str_replace("@","-request@",$list));
        }
        else
        {
                mail ($from,"Unknown request","The command requested is unknown\n\nTo perform a command, send an email to "
                      . str_replace("@","-request@",$list)
                      . " with ONLY the request in the subject (not case sensitive)\n\n"
                      . "The supported commands are:\n"
                      . "       subscribe\n     unsubscribe\n\n"
                      . "Thank you for using PHP Mail List",
                      "From: " . str_replace("@","-request@",$list));
        }

?>


In my main.cf file for postfix, I have added the following:

Code:
virtual_mailbox_domains = mysql:/etc/mail/mysql_domains.cf
virtual_alias_maps = mysql:/etc/mail/mysql_virtual.cf
alias_maps = mysql:/etc/mail/mysql_aliases.cf


I use virtual_mailbox_domains because I will eventually host mailboxes for the domains on this machine. The .cf files actually tell postfix which tables and fields to query for aliases and domains to accept email for.

I realize that this code looks hacked together (looks aren't decieving in this case), but it works pretty well so far. I'll eventually write webpages to administer the database without having to muck about in the database and keep the data consistent. Tell me what you think!
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Networking & Security All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum