org/sonews/mlgw/Dispatcher.java
author cli
Thu, 20 Aug 2009 14:31:19 +0200
changeset 12 bb6990c0dd1a
parent 3 2fdc9cc89502
child 14 efce4ec25564
permissions -rw-r--r--
Merging fixes from sonews/1.0.3
     1 /*
     2  *   SONEWS News Server
     3  *   see AUTHORS for the list of contributors
     4  *
     5  *   This program is free software: you can redistribute it and/or modify
     6  *   it under the terms of the GNU General Public License as published by
     7  *   the Free Software Foundation, either version 3 of the License, or
     8  *   (at your option) any later version.
     9  *
    10  *   This program is distributed in the hope that it will be useful,
    11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  *   GNU General Public License for more details.
    14  *
    15  *   You should have received a copy of the GNU General Public License
    16  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  */
    18 
    19 package org.sonews.mlgw;
    20 
    21 import java.io.IOException;
    22 import java.util.ArrayList;
    23 import java.util.List;
    24 import java.util.regex.Matcher;
    25 import java.util.regex.Pattern;
    26 import javax.mail.Address;
    27 import javax.mail.Authenticator;
    28 import javax.mail.Message;
    29 import javax.mail.MessagingException;
    30 import javax.mail.PasswordAuthentication;
    31 import javax.mail.internet.InternetAddress;
    32 import org.sonews.config.Config;
    33 import org.sonews.storage.Article;
    34 import org.sonews.storage.Group;
    35 import org.sonews.storage.Headers;
    36 import org.sonews.storage.StorageBackendException;
    37 import org.sonews.storage.StorageManager;
    38 import org.sonews.util.Log;
    39 import org.sonews.util.Stats;
    40 
    41 /**
    42  * Dispatches messages from mailing list to newsserver or vice versa.
    43  * @author Christian Lins
    44  * @since sonews/0.5.0
    45  */
    46 public class Dispatcher 
    47 {
    48 
    49   static class PasswordAuthenticator extends Authenticator
    50   {
    51     
    52     @Override
    53     public PasswordAuthentication getPasswordAuthentication()
    54     {
    55       final String username = 
    56         Config.inst().get(Config.MLSEND_USER, "user");
    57       final String password = 
    58         Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
    59 
    60       return new PasswordAuthentication(username, password);
    61     }
    62     
    63   }
    64 
    65   /**
    66    * Chunks out the email address of the full List-Post header field.
    67    * @param listPostValue
    68    * @return The matching email address or null
    69    */
    70   private static String chunkListPost(String listPostValue)
    71   {
    72     // listPostValue is of form "<mailto:dev@openoffice.org>"
    73     Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
    74     Matcher mailMatcher = mailPattern.matcher(listPostValue);
    75     if(mailMatcher.find())
    76     {
    77       return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
    78     }
    79     else
    80     {
    81       return null;
    82     }
    83   }
    84 
    85   /**
    86    * This method inspects the header of the given message, trying
    87    * to find the most appropriate recipient.
    88    * @param msg
    89    * @param fallback If this is false only List-Post and X-List-Post headers
    90    *                 are examined.
    91    * @return null or fitting group name for the given message.
    92    */
    93   private static List<String> getGroupFor(final Message msg, final boolean fallback)
    94     throws MessagingException, StorageBackendException
    95   {
    96     List<String> groups = null;
    97 
    98     // Is there a List-Post header?
    99     String[]        listPost = msg.getHeader(Headers.LIST_POST);
   100     InternetAddress listPostAddr;
   101 
   102     if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
   103     {
   104       // Is there a X-List-Post header?
   105       listPost = msg.getHeader(Headers.X_LIST_POST);
   106     }
   107 
   108     if(listPost != null && listPost.length > 0 
   109       && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
   110     {
   111       // listPost[0] is of form "<mailto:dev@openoffice.org>"
   112       listPost[0]  = chunkListPost(listPost[0]);
   113       listPostAddr = new InternetAddress(listPost[0], false);
   114       groups = StorageManager.current().getGroupsForList(listPostAddr);
   115     }
   116     else if(fallback)
   117     {
   118       Log.msg("Using fallback recipient discovery for: " + msg.getSubject(), true);
   119       groups = new ArrayList<String>();
   120       // Fallback to TO/CC/BCC addresses
   121       Address[] to = msg.getAllRecipients();
   122       for(Address toa : to) // Address can have '<' '>' around
   123       {
   124         if(toa instanceof InternetAddress)
   125         {
   126           List<String> g = StorageManager.current().getGroupsForList((InternetAddress) toa);
   127           groups.addAll(g);
   128         }
   129       }
   130     }
   131     
   132     return groups;
   133   }
   134   
   135   /**
   136    * Posts a message that was received from a mailing list to the 
   137    * appropriate newsgroup.
   138    * If the message already exists in the storage, this message checks
   139    * if it must be posted in an additional group. This can happen for
   140    * crosspostings in different mailing lists.
   141    * @param msg
   142    */
   143   public static boolean toGroup(final Message msg)
   144   {
   145     try
   146     {
   147       // Create new Article object
   148       Article article = new Article(msg);
   149       boolean posted  = false;
   150 
   151       // Check if this mail is already existing the storage
   152       boolean updateReq = 
   153         StorageManager.current().isArticleExisting(article.getMessageID());
   154 
   155       List<String> newsgroups = getGroupFor(msg, !updateReq);
   156       List<String> oldgroups  = new ArrayList<String>();
   157       if(updateReq)
   158       {
   159         // Check for duplicate entries of the same group
   160         Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
   161         List<Group> oldGroups = oldArticle.getGroups();
   162         for(Group oldGroup : oldGroups)
   163         {
   164           if(!newsgroups.contains(oldGroup.getName()))
   165           {
   166             oldgroups.add(oldGroup.getName());
   167           }
   168         }
   169       }
   170 
   171       if(newsgroups.size() > 0)
   172       {
   173         newsgroups.addAll(oldgroups);
   174         StringBuilder groups = new StringBuilder();
   175         for(int n = 0; n < newsgroups.size(); n++)
   176         {
   177           groups.append(newsgroups.get(n));
   178           if (n + 1 != newsgroups.size())
   179           {
   180             groups.append(',');
   181           }
   182         }
   183         Log.msg("Posting to group " + groups.toString(), true);
   184 
   185         article.setGroup(groups.toString());
   186         //article.removeHeader(Headers.REPLY_TO);
   187         //article.removeHeader(Headers.TO);
   188 
   189         // Write article to database
   190         if(updateReq)
   191         {
   192           Log.msg("Updating " + article.getMessageID() + " with additional groups", true);
   193           StorageManager.current().delete(article.getMessageID());
   194           StorageManager.current().addArticle(article);
   195         }
   196         else
   197         {
   198           Log.msg("Gatewaying " + article.getMessageID() + " to "
   199             + article.getHeader(Headers.NEWSGROUPS)[0], true);
   200           StorageManager.current().addArticle(article);
   201           Stats.getInstance().mailGatewayed(
   202             article.getHeader(Headers.NEWSGROUPS)[0]);
   203         }
   204         posted = true;
   205       }
   206       else
   207       {
   208         StringBuilder buf = new StringBuilder();
   209         for (Address toa : msg.getAllRecipients())
   210         {
   211           buf.append(' ');
   212           buf.append(toa.toString());
   213         }
   214         buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
   215         Log.msg("No group for" + buf.toString(), false);
   216       }
   217       return posted;
   218     }
   219     catch(Exception ex)
   220     {
   221       ex.printStackTrace();
   222       return false;
   223     }
   224   }
   225   
   226   /**
   227    * Mails a message received through NNTP to the appropriate mailing list.
   228    * This method MAY be called several times by PostCommand for the same
   229    * article.
   230    */
   231   public static void toList(Article article, String group)
   232     throws IOException, MessagingException, StorageBackendException
   233   {
   234     // Get mailing lists for the group of this article
   235     List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
   236 
   237     if(rcptAddresses == null || rcptAddresses.size() == 0)
   238     {
   239       Log.msg("No ML-address for " + group + " found.", false);
   240       return;
   241     }
   242 
   243     for(String rcptAddress : rcptAddresses)
   244     {
   245       // Compose message and send it via given SMTP-Host
   246       String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
   247       int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
   248       String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
   249       String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
   250       String smtpFrom = Config.inst().get(
   251         Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
   252 
   253       // TODO: Make Article cloneable()
   254       article.getMessageID(); // Make sure an ID is existing
   255       article.removeHeader(Headers.NEWSGROUPS);
   256       article.removeHeader(Headers.PATH);
   257       article.removeHeader(Headers.LINES);
   258       article.removeHeader(Headers.BYTES);
   259 
   260       article.setHeader("To", rcptAddress);
   261       //article.setHeader("Reply-To", listAddress);
   262 
   263       if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
   264       {
   265         rewriteSenderAddress(article); // Set the SENDER address
   266       }
   267 
   268       SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
   269       smtpTransport.send(article, smtpFrom, rcptAddress);
   270       smtpTransport.close();
   271 
   272       Stats.getInstance().mailGatewayed(group);
   273       Log.msg("MLGateway: Mail " + article.getHeader("Subject")[0]
   274         + " was delivered to " + rcptAddress + ".", true);
   275     }
   276   }
   277   
   278   /**
   279    * Sets the SENDER header of the given MimeMessage. This might be necessary
   280    * for moderated groups that does not allow the "normal" FROM sender.
   281    * @param msg
   282    * @throws javax.mail.MessagingException
   283    */
   284   private static void rewriteSenderAddress(Article msg)
   285     throws MessagingException
   286   {
   287     String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
   288 
   289     if(mlAddress != null)
   290     {
   291       msg.setHeader(Headers.SENDER, mlAddress);
   292     }
   293     else
   294     {
   295       throw new MessagingException("Cannot rewrite SENDER header!");
   296     }
   297   }
   298   
   299 }