org/sonews/mlgw/Dispatcher.java
author cli
Thu, 20 Aug 2009 16:57:38 +0200
changeset 14 efce4ec25564
parent 12 bb6990c0dd1a
child 16 5a4a41cfc0a3
permissions -rw-r--r--
Fix #548: API change; changed parameter type of Storage.getGroupsForList()
     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.getAddress());
   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()
   127             .getGroupsForList(((InternetAddress)toa).getAddress());
   128           groups.addAll(g);
   129         }
   130       }
   131     }
   132     
   133     return groups;
   134   }
   135   
   136   /**
   137    * Posts a message that was received from a mailing list to the 
   138    * appropriate newsgroup.
   139    * If the message already exists in the storage, this message checks
   140    * if it must be posted in an additional group. This can happen for
   141    * crosspostings in different mailing lists.
   142    * @param msg
   143    */
   144   public static boolean toGroup(final Message msg)
   145   {
   146     try
   147     {
   148       // Create new Article object
   149       Article article = new Article(msg);
   150       boolean posted  = false;
   151 
   152       // Check if this mail is already existing the storage
   153       boolean updateReq = 
   154         StorageManager.current().isArticleExisting(article.getMessageID());
   155 
   156       List<String> newsgroups = getGroupFor(msg, !updateReq);
   157       List<String> oldgroups  = new ArrayList<String>();
   158       if(updateReq)
   159       {
   160         // Check for duplicate entries of the same group
   161         Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
   162         List<Group> oldGroups = oldArticle.getGroups();
   163         for(Group oldGroup : oldGroups)
   164         {
   165           if(!newsgroups.contains(oldGroup.getName()))
   166           {
   167             oldgroups.add(oldGroup.getName());
   168           }
   169         }
   170       }
   171 
   172       if(newsgroups.size() > 0)
   173       {
   174         newsgroups.addAll(oldgroups);
   175         StringBuilder groups = new StringBuilder();
   176         for(int n = 0; n < newsgroups.size(); n++)
   177         {
   178           groups.append(newsgroups.get(n));
   179           if (n + 1 != newsgroups.size())
   180           {
   181             groups.append(',');
   182           }
   183         }
   184         Log.msg("Posting to group " + groups.toString(), true);
   185 
   186         article.setGroup(groups.toString());
   187         //article.removeHeader(Headers.REPLY_TO);
   188         //article.removeHeader(Headers.TO);
   189 
   190         // Write article to database
   191         if(updateReq)
   192         {
   193           Log.msg("Updating " + article.getMessageID() + " with additional groups", true);
   194           StorageManager.current().delete(article.getMessageID());
   195           StorageManager.current().addArticle(article);
   196         }
   197         else
   198         {
   199           Log.msg("Gatewaying " + article.getMessageID() + " to "
   200             + article.getHeader(Headers.NEWSGROUPS)[0], true);
   201           StorageManager.current().addArticle(article);
   202           Stats.getInstance().mailGatewayed(
   203             article.getHeader(Headers.NEWSGROUPS)[0]);
   204         }
   205         posted = true;
   206       }
   207       else
   208       {
   209         StringBuilder buf = new StringBuilder();
   210         for (Address toa : msg.getAllRecipients())
   211         {
   212           buf.append(' ');
   213           buf.append(toa.toString());
   214         }
   215         buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
   216         Log.msg("No group for" + buf.toString(), false);
   217       }
   218       return posted;
   219     }
   220     catch(Exception ex)
   221     {
   222       ex.printStackTrace();
   223       return false;
   224     }
   225   }
   226   
   227   /**
   228    * Mails a message received through NNTP to the appropriate mailing list.
   229    * This method MAY be called several times by PostCommand for the same
   230    * article.
   231    */
   232   public static void toList(Article article, String group)
   233     throws IOException, MessagingException, StorageBackendException
   234   {
   235     // Get mailing lists for the group of this article
   236     List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
   237 
   238     if(rcptAddresses == null || rcptAddresses.size() == 0)
   239     {
   240       Log.msg("No ML-address for " + group + " found.", false);
   241       return;
   242     }
   243 
   244     for(String rcptAddress : rcptAddresses)
   245     {
   246       // Compose message and send it via given SMTP-Host
   247       String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
   248       int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
   249       String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
   250       String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
   251       String smtpFrom = Config.inst().get(
   252         Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
   253 
   254       // TODO: Make Article cloneable()
   255       article.getMessageID(); // Make sure an ID is existing
   256       article.removeHeader(Headers.NEWSGROUPS);
   257       article.removeHeader(Headers.PATH);
   258       article.removeHeader(Headers.LINES);
   259       article.removeHeader(Headers.BYTES);
   260 
   261       article.setHeader("To", rcptAddress);
   262       //article.setHeader("Reply-To", listAddress);
   263 
   264       if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
   265       {
   266         rewriteSenderAddress(article); // Set the SENDER address
   267       }
   268 
   269       SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
   270       smtpTransport.send(article, smtpFrom, rcptAddress);
   271       smtpTransport.close();
   272 
   273       Stats.getInstance().mailGatewayed(group);
   274       Log.msg("MLGateway: Mail " + article.getHeader("Subject")[0]
   275         + " was delivered to " + rcptAddress + ".", true);
   276     }
   277   }
   278   
   279   /**
   280    * Sets the SENDER header of the given MimeMessage. This might be necessary
   281    * for moderated groups that does not allow the "normal" FROM sender.
   282    * @param msg
   283    * @throws javax.mail.MessagingException
   284    */
   285   private static void rewriteSenderAddress(Article msg)
   286     throws MessagingException
   287   {
   288     String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
   289 
   290     if(mlAddress != null)
   291     {
   292       msg.setHeader(Headers.SENDER, mlAddress);
   293     }
   294     else
   295     {
   296       throw new MessagingException("Cannot rewrite SENDER header!");
   297     }
   298   }
   299   
   300 }