Hi, James Developers,

I wrote a first draft of a "Mailman Bridge Mailet" and have attached it. I would really appreciate feedback on the use of the Mailet API, as well as any comments/criticism on style, format, or Java usage.

The MailmanBridge Mailet "pipes" mail to Mailman. The intent is for James to be used as the MTA in (former) Postfix and Sendmail environments. Each line of what was once an /etc/aliases alias mapping can now be represented as a mailet block in James' config.xml. What was formerly:

list-announce: "|/var/mailman/mail/mailman post list-announce"

can now be expressed as:

<mailet match="[EMAIL PROTECTED]" class="MailmanBridge">
<debug>false</debug>
<passThrough>false</passThrough>
<runAs>mail</runAs>
<wrapperPath>/var/mailman/mail/mailman</wrapperPath>
<program>post</program>
<argument>list-announce</argument>
</mailet>


I currently have it running on two personal lists and so far so good.

As for enhancements, I plan on reworking the config, so the XML is less verbose and, hopefully, to reduce the sheer number of Mailets, since it currently operates as one Mailet per Mailman alias.

I built it against the latest from CVS, which identifies as "James Mail Server 3.0a1." This Mailet drops into org.apache.james.transport.mailets and I call 'ant' to build it right into the distribution. I am running it on Fedora Core 1 with mailman-2.1.4 and j2sdk1.4.2_03.

-enrique
/***********************************************************************
 * Copyright (c) 2004 The Apache Software Foundation.             *
 * All rights reserved.                                                *
 * ------------------------------------------------------------------- *
 * Licensed under the Apache License, Version 2.0 (the "License"); you *
 * may not use this file except in compliance with the License. You    *
 * may obtain a copy of the License at:                                *
 *                                                                     *
 *     http://www.apache.org/licenses/LICENSE-2.0                      *
 *                                                                     *
 * Unless required by applicable law or agreed to in writing, software *
 * distributed under the License is distributed on an "AS IS" BASIS,   *
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or     *
 * implied.  See the License for the specific language governing       *
 * permissions and limitations under the License.                      *
 ***********************************************************************/

package org.apache.james.transport.mailets;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Enumeration;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.apache.mailet.GenericMailet;
import org.apache.mailet.Mail;

/**
 * The MailmanBridge Mailet "pipes" mail to Mailman.  The intent is for James to be used
 * as the MTA in (former) Postfix and Sendmail environments.  Each line of what was
 * once an /etc/aliases alias mapping can now be represented as a mailet block in
 * James' config.xml.  What was formerly:
 * 
 * list-announce:         "|/var/mailman/mail/mailman post list-announce"
 *
 * can now be expressed as:
 *
 * <mailet match="[EMAIL PROTECTED]" class="MailmanBridge">
 *     <debug>false</debug>
 *     <passThrough>false</passThrough>
 *     <runAs>mail</runAs>
 *     <wrapperPath>/var/mailman/mail/mailman</wrapperPath>
 *     <program>post</program>
 *     <argument>list-announce</argument>
 * </mailet>
 *
 * @version 0.0.1, 6/03/2004
 *
 * @version This is $Revision: 1.0 $
 */
public class MailmanBridge extends GenericMailet {

    /**
     * The message to be forwarded to Mailman.
     */
    private MimeMessage message;
    /**
     * The headers, a separate entity in the MimeMessage than the message contents.
     */
    private String headers;
    /**
     * The subject of the message.  More useful for debugging than the mail name.
     */
    private String subject;
    /**
     * The exit code, as returned by the Mailman wrapper.
     */
    private int exitValue;
    /**
     * Controls certain log messages.
     */
    private boolean isDebug = false;
    /**
     * Whether this mailet should allow mails to be processed by additional mailets
     * or mark it as finished.
     */
    private boolean passThrough = false;
    /**
     * Optional user to execute Mailman commands as, using sudo -u.
     * Mailman comes configured expecting a specific account, such as nobody or apache.
     * Default is whatever James is running as.
     */
    private String runAs;
    /**
     * Optional path to the Mailman wrapper.
     * Old versions:  /var/mailman/mail/wrapper
     * Newer versions, default:  /var/mailman/mail/mailman
     */
    private String wrapperPath;
    /**
     * Mandatory.  Program to execute.  Something like post, mailowner, mailcmd ...
     */
    private String program;
    /**
     * Mandatory.  Argument for program.  Almost always the mailing list name.
     */
    private String argument;
    /**
     * The full command line this Mailet will execute.
     */
    private String command;
        
    /**
     * Initialize the mailet, loading configuration information.
     */
    public void init() {
        isDebug = (getInitParameter("debug") == null) ? false : new Boolean(getInitParameter("debug")).booleanValue();
        passThrough = (getInitParameter("passThrough") == null) ? false : new Boolean(getInitParameter("passThrough")).booleanValue();

        runAs = getInitParameter("runAs");
        wrapperPath = getInitParameter("wrapperPath");
        program = getInitParameter("program");
        argument = getInitParameter("argument");
        
        if (wrapperPath == null)
            wrapperPath = "/var/mailman/mail/mailman";
            
        if (program == null)
            log("Invalid program:  " + program);
            
        if (argument == null)
            log("Invalid argument:  " + argument);
        
        command = getCommand();
        if (isDebug)
            log("Mailman command initialized to:  " + command);
    }

    /**
     * "Pipe" the mail message to the Mailman wrapper.
     *
     * @param mail the mail to process
     */
    public void service(Mail mail) {

        try {
            // Get the message parts we want to work with.
            message = mail.getMessage();
            headers = getMessageHeaders(message);
            subject = message.getSubject();

        } catch (MessagingException me) {
            log("Caught MessagingException:  " + me.getMessage());
        }
        
        if (isDebug) {
            log("Passing mail to Mailman with subject:  " + subject);
            log("Mailman wrapper executing with command:  " + command);
        }

        Runtime rt = Runtime.getRuntime();
        
        try {
            // Call the command-line.
            Process pr = rt.exec(command);
            // Open the stream that is "piped" to the Mailman wrapper.
            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(pr.getOutputStream()));
            // Open the reader for the message body.  Note this doesn't include the headers.
            BufferedReader in = new BufferedReader(new InputStreamReader(message.getInputStream()));
            
            // Write the headers.
            out.write(headers);
            // Write the message body.
            int b = -1;
            while ((b = in.read()) != -1) {
                out.write(b);
            }
            
            in.close();
            out.flush();
            out.close();
            
            // Wait for command to complete.
            pr.waitFor();

            // Get program's exit code.  Will be zero (0) if successful. 
            exitValue = pr.exitValue();

        } catch (Exception ex) {
            log("Caught exception:  " + ex.getMessage());
        }

        if (isDebug)
            log("Mailman wrapper exit value is:  " + String.valueOf(exitValue));
            
        if (!passThrough && exitValue==0) {
            if (isDebug)
                log("Ghosting message with subject:  " + subject);
            mail.setState(Mail.GHOST);
        }
    }

    /**
     * Utility method for converting configuration options into the command line.
     * 
     * @return a string of the command line to be executed.
     */
    private String getCommand() {
        StringBuffer command = new StringBuffer(256);
        if (runAs != null)
            command.append("sudo -u " + runAs + " ");
        command.append(wrapperPath + " "); 
        command.append(program + " ");
        command.append(argument);
        return command.toString();
    }
    
    /**
     * Utility method for converting a message's header lines to a single string.
     * 
     * @return a string containing all message headers.
     */
    private String getMessageHeaders(MimeMessage message) throws MessagingException {
        Enumeration heads = message.getAllHeaderLines();
        StringBuffer headBuffer = new StringBuffer(1024);
        while(heads.hasMoreElements()) {
            headBuffer.append(heads.nextElement().toString()).append("\r\n");
        }
        return headBuffer.toString();
    }

    /**
     * Return a string describing this mailet.
     * 
     * @return a string describing this mailet
     */
    public String getMailetInfo() {
        return "Mailman Bridge Mailet";
    }
}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to