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