diff -r 9f0b95aafaa3 -r ed84c8bdd87b src/org/sonews/mlgw/Dispatcher.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/sonews/mlgw/Dispatcher.java Sun Aug 29 17:28:58 2010 +0200
@@ -0,0 +1,301 @@
+/*
+ * SONEWS News Server
+ * see AUTHORS for the list of contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package org.sonews.mlgw;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import javax.mail.Address;
+import javax.mail.Authenticator;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.PasswordAuthentication;
+import javax.mail.internet.InternetAddress;
+import org.sonews.config.Config;
+import org.sonews.storage.Article;
+import org.sonews.storage.Group;
+import org.sonews.storage.Headers;
+import org.sonews.storage.StorageBackendException;
+import org.sonews.storage.StorageManager;
+import org.sonews.util.Log;
+import org.sonews.util.Stats;
+
+/**
+ * Dispatches messages from mailing list to newsserver or vice versa.
+ * @author Christian Lins
+ * @since sonews/0.5.0
+ */
+public class Dispatcher
+{
+
+ static class PasswordAuthenticator extends Authenticator
+ {
+
+ @Override
+ public PasswordAuthentication getPasswordAuthentication()
+ {
+ final String username =
+ Config.inst().get(Config.MLSEND_USER, "user");
+ final String password =
+ Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
+
+ return new PasswordAuthentication(username, password);
+ }
+
+ }
+
+ /**
+ * Chunks out the email address of the full List-Post header field.
+ * @param listPostValue
+ * @return The matching email address or null
+ */
+ private static String chunkListPost(String listPostValue)
+ {
+ // listPostValue is of form ""
+ Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
+ Matcher mailMatcher = mailPattern.matcher(listPostValue);
+ if(mailMatcher.find())
+ {
+ return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * This method inspects the header of the given message, trying
+ * to find the most appropriate recipient.
+ * @param msg
+ * @param fallback If this is false only List-Post and X-List-Post headers
+ * are examined.
+ * @return null or fitting group name for the given message.
+ */
+ private static List getGroupFor(final Message msg, final boolean fallback)
+ throws MessagingException, StorageBackendException
+ {
+ List groups = null;
+
+ // Is there a List-Post header?
+ String[] listPost = msg.getHeader(Headers.LIST_POST);
+ InternetAddress listPostAddr;
+
+ if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
+ {
+ // Is there a X-List-Post header?
+ listPost = msg.getHeader(Headers.X_LIST_POST);
+ }
+
+ if(listPost != null && listPost.length > 0
+ && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
+ {
+ // listPost[0] is of form ""
+ listPost[0] = chunkListPost(listPost[0]);
+ listPostAddr = new InternetAddress(listPost[0], false);
+ groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
+ }
+ else if(fallback)
+ {
+ Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
+ groups = new ArrayList();
+ // Fallback to TO/CC/BCC addresses
+ Address[] to = msg.getAllRecipients();
+ for(Address toa : to) // Address can have '<' '>' around
+ {
+ if(toa instanceof InternetAddress)
+ {
+ List g = StorageManager.current()
+ .getGroupsForList(((InternetAddress)toa).getAddress());
+ groups.addAll(g);
+ }
+ }
+ }
+
+ return groups;
+ }
+
+ /**
+ * Posts a message that was received from a mailing list to the
+ * appropriate newsgroup.
+ * If the message already exists in the storage, this message checks
+ * if it must be posted in an additional group. This can happen for
+ * crosspostings in different mailing lists.
+ * @param msg
+ */
+ public static boolean toGroup(final Message msg)
+ {
+ try
+ {
+ // Create new Article object
+ Article article = new Article(msg);
+ boolean posted = false;
+
+ // Check if this mail is already existing the storage
+ boolean updateReq =
+ StorageManager.current().isArticleExisting(article.getMessageID());
+
+ List newsgroups = getGroupFor(msg, !updateReq);
+ List oldgroups = new ArrayList();
+ if(updateReq)
+ {
+ // Check for duplicate entries of the same group
+ Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
+ List oldGroups = oldArticle.getGroups();
+ for(Group oldGroup : oldGroups)
+ {
+ if(!newsgroups.contains(oldGroup.getName()))
+ {
+ oldgroups.add(oldGroup.getName());
+ }
+ }
+ }
+
+ if(newsgroups.size() > 0)
+ {
+ newsgroups.addAll(oldgroups);
+ StringBuilder groups = new StringBuilder();
+ for(int n = 0; n < newsgroups.size(); n++)
+ {
+ groups.append(newsgroups.get(n));
+ if (n + 1 != newsgroups.size())
+ {
+ groups.append(',');
+ }
+ }
+ Log.get().info("Posting to group " + groups.toString());
+
+ article.setGroup(groups.toString());
+ //article.removeHeader(Headers.REPLY_TO);
+ //article.removeHeader(Headers.TO);
+
+ // Write article to database
+ if(updateReq)
+ {
+ Log.get().info("Updating " + article.getMessageID()
+ + " with additional groups");
+ StorageManager.current().delete(article.getMessageID());
+ StorageManager.current().addArticle(article);
+ }
+ else
+ {
+ Log.get().info("Gatewaying " + article.getMessageID() + " to "
+ + article.getHeader(Headers.NEWSGROUPS)[0]);
+ StorageManager.current().addArticle(article);
+ Stats.getInstance().mailGatewayed(
+ article.getHeader(Headers.NEWSGROUPS)[0]);
+ }
+ posted = true;
+ }
+ else
+ {
+ StringBuilder buf = new StringBuilder();
+ for (Address toa : msg.getAllRecipients())
+ {
+ buf.append(' ');
+ buf.append(toa.toString());
+ }
+ buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
+ Log.get().warning("No group for" + buf.toString());
+ }
+ return posted;
+ }
+ catch(Exception ex)
+ {
+ ex.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * Mails a message received through NNTP to the appropriate mailing list.
+ * This method MAY be called several times by PostCommand for the same
+ * article.
+ */
+ public static void toList(Article article, String group)
+ throws IOException, MessagingException, StorageBackendException
+ {
+ // Get mailing lists for the group of this article
+ List rcptAddresses = StorageManager.current().getListsForGroup(group);
+
+ if(rcptAddresses == null || rcptAddresses.size() == 0)
+ {
+ Log.get().warning("No ML-address for " + group + " found.");
+ return;
+ }
+
+ for(String rcptAddress : rcptAddresses)
+ {
+ // Compose message and send it via given SMTP-Host
+ String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
+ int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
+ String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
+ String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
+ String smtpFrom = Config.inst().get(
+ Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
+
+ // TODO: Make Article cloneable()
+ article.getMessageID(); // Make sure an ID is existing
+ article.removeHeader(Headers.NEWSGROUPS);
+ article.removeHeader(Headers.PATH);
+ article.removeHeader(Headers.LINES);
+ article.removeHeader(Headers.BYTES);
+
+ article.setHeader("To", rcptAddress);
+ //article.setHeader("Reply-To", listAddress);
+
+ if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
+ {
+ rewriteSenderAddress(article); // Set the SENDER address
+ }
+
+ SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
+ smtpTransport.send(article, smtpFrom, rcptAddress);
+ smtpTransport.close();
+
+ Stats.getInstance().mailGatewayed(group);
+ Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
+ + " was delivered to " + rcptAddress + ".");
+ }
+ }
+
+ /**
+ * Sets the SENDER header of the given MimeMessage. This might be necessary
+ * for moderated groups that does not allow the "normal" FROM sender.
+ * @param msg
+ * @throws javax.mail.MessagingException
+ */
+ private static void rewriteSenderAddress(Article msg)
+ throws MessagingException
+ {
+ String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
+
+ if(mlAddress != null)
+ {
+ msg.setHeader(Headers.SENDER, mlAddress);
+ }
+ else
+ {
+ throw new MessagingException("Cannot rewrite SENDER header!");
+ }
+ }
+
+}