1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/src/org/sonews/mlgw/Dispatcher.java Sun Aug 29 17:28:58 2010 +0200
1.3 @@ -0,0 +1,301 @@
1.4 +/*
1.5 + * SONEWS News Server
1.6 + * see AUTHORS for the list of contributors
1.7 + *
1.8 + * This program is free software: you can redistribute it and/or modify
1.9 + * it under the terms of the GNU General Public License as published by
1.10 + * the Free Software Foundation, either version 3 of the License, or
1.11 + * (at your option) any later version.
1.12 + *
1.13 + * This program is distributed in the hope that it will be useful,
1.14 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.15 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.16 + * GNU General Public License for more details.
1.17 + *
1.18 + * You should have received a copy of the GNU General Public License
1.19 + * along with this program. If not, see <http://www.gnu.org/licenses/>.
1.20 + */
1.21 +
1.22 +package org.sonews.mlgw;
1.23 +
1.24 +import java.io.IOException;
1.25 +import java.util.ArrayList;
1.26 +import java.util.List;
1.27 +import java.util.regex.Matcher;
1.28 +import java.util.regex.Pattern;
1.29 +import javax.mail.Address;
1.30 +import javax.mail.Authenticator;
1.31 +import javax.mail.Message;
1.32 +import javax.mail.MessagingException;
1.33 +import javax.mail.PasswordAuthentication;
1.34 +import javax.mail.internet.InternetAddress;
1.35 +import org.sonews.config.Config;
1.36 +import org.sonews.storage.Article;
1.37 +import org.sonews.storage.Group;
1.38 +import org.sonews.storage.Headers;
1.39 +import org.sonews.storage.StorageBackendException;
1.40 +import org.sonews.storage.StorageManager;
1.41 +import org.sonews.util.Log;
1.42 +import org.sonews.util.Stats;
1.43 +
1.44 +/**
1.45 + * Dispatches messages from mailing list to newsserver or vice versa.
1.46 + * @author Christian Lins
1.47 + * @since sonews/0.5.0
1.48 + */
1.49 +public class Dispatcher
1.50 +{
1.51 +
1.52 + static class PasswordAuthenticator extends Authenticator
1.53 + {
1.54 +
1.55 + @Override
1.56 + public PasswordAuthentication getPasswordAuthentication()
1.57 + {
1.58 + final String username =
1.59 + Config.inst().get(Config.MLSEND_USER, "user");
1.60 + final String password =
1.61 + Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
1.62 +
1.63 + return new PasswordAuthentication(username, password);
1.64 + }
1.65 +
1.66 + }
1.67 +
1.68 + /**
1.69 + * Chunks out the email address of the full List-Post header field.
1.70 + * @param listPostValue
1.71 + * @return The matching email address or null
1.72 + */
1.73 + private static String chunkListPost(String listPostValue)
1.74 + {
1.75 + // listPostValue is of form "<mailto:dev@openoffice.org>"
1.76 + Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
1.77 + Matcher mailMatcher = mailPattern.matcher(listPostValue);
1.78 + if(mailMatcher.find())
1.79 + {
1.80 + return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
1.81 + }
1.82 + else
1.83 + {
1.84 + return null;
1.85 + }
1.86 + }
1.87 +
1.88 + /**
1.89 + * This method inspects the header of the given message, trying
1.90 + * to find the most appropriate recipient.
1.91 + * @param msg
1.92 + * @param fallback If this is false only List-Post and X-List-Post headers
1.93 + * are examined.
1.94 + * @return null or fitting group name for the given message.
1.95 + */
1.96 + private static List<String> getGroupFor(final Message msg, final boolean fallback)
1.97 + throws MessagingException, StorageBackendException
1.98 + {
1.99 + List<String> groups = null;
1.100 +
1.101 + // Is there a List-Post header?
1.102 + String[] listPost = msg.getHeader(Headers.LIST_POST);
1.103 + InternetAddress listPostAddr;
1.104 +
1.105 + if(listPost == null || listPost.length == 0 || "".equals(listPost[0]))
1.106 + {
1.107 + // Is there a X-List-Post header?
1.108 + listPost = msg.getHeader(Headers.X_LIST_POST);
1.109 + }
1.110 +
1.111 + if(listPost != null && listPost.length > 0
1.112 + && !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null)
1.113 + {
1.114 + // listPost[0] is of form "<mailto:dev@openoffice.org>"
1.115 + listPost[0] = chunkListPost(listPost[0]);
1.116 + listPostAddr = new InternetAddress(listPost[0], false);
1.117 + groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
1.118 + }
1.119 + else if(fallback)
1.120 + {
1.121 + Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
1.122 + groups = new ArrayList<String>();
1.123 + // Fallback to TO/CC/BCC addresses
1.124 + Address[] to = msg.getAllRecipients();
1.125 + for(Address toa : to) // Address can have '<' '>' around
1.126 + {
1.127 + if(toa instanceof InternetAddress)
1.128 + {
1.129 + List<String> g = StorageManager.current()
1.130 + .getGroupsForList(((InternetAddress)toa).getAddress());
1.131 + groups.addAll(g);
1.132 + }
1.133 + }
1.134 + }
1.135 +
1.136 + return groups;
1.137 + }
1.138 +
1.139 + /**
1.140 + * Posts a message that was received from a mailing list to the
1.141 + * appropriate newsgroup.
1.142 + * If the message already exists in the storage, this message checks
1.143 + * if it must be posted in an additional group. This can happen for
1.144 + * crosspostings in different mailing lists.
1.145 + * @param msg
1.146 + */
1.147 + public static boolean toGroup(final Message msg)
1.148 + {
1.149 + try
1.150 + {
1.151 + // Create new Article object
1.152 + Article article = new Article(msg);
1.153 + boolean posted = false;
1.154 +
1.155 + // Check if this mail is already existing the storage
1.156 + boolean updateReq =
1.157 + StorageManager.current().isArticleExisting(article.getMessageID());
1.158 +
1.159 + List<String> newsgroups = getGroupFor(msg, !updateReq);
1.160 + List<String> oldgroups = new ArrayList<String>();
1.161 + if(updateReq)
1.162 + {
1.163 + // Check for duplicate entries of the same group
1.164 + Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
1.165 + List<Group> oldGroups = oldArticle.getGroups();
1.166 + for(Group oldGroup : oldGroups)
1.167 + {
1.168 + if(!newsgroups.contains(oldGroup.getName()))
1.169 + {
1.170 + oldgroups.add(oldGroup.getName());
1.171 + }
1.172 + }
1.173 + }
1.174 +
1.175 + if(newsgroups.size() > 0)
1.176 + {
1.177 + newsgroups.addAll(oldgroups);
1.178 + StringBuilder groups = new StringBuilder();
1.179 + for(int n = 0; n < newsgroups.size(); n++)
1.180 + {
1.181 + groups.append(newsgroups.get(n));
1.182 + if (n + 1 != newsgroups.size())
1.183 + {
1.184 + groups.append(',');
1.185 + }
1.186 + }
1.187 + Log.get().info("Posting to group " + groups.toString());
1.188 +
1.189 + article.setGroup(groups.toString());
1.190 + //article.removeHeader(Headers.REPLY_TO);
1.191 + //article.removeHeader(Headers.TO);
1.192 +
1.193 + // Write article to database
1.194 + if(updateReq)
1.195 + {
1.196 + Log.get().info("Updating " + article.getMessageID()
1.197 + + " with additional groups");
1.198 + StorageManager.current().delete(article.getMessageID());
1.199 + StorageManager.current().addArticle(article);
1.200 + }
1.201 + else
1.202 + {
1.203 + Log.get().info("Gatewaying " + article.getMessageID() + " to "
1.204 + + article.getHeader(Headers.NEWSGROUPS)[0]);
1.205 + StorageManager.current().addArticle(article);
1.206 + Stats.getInstance().mailGatewayed(
1.207 + article.getHeader(Headers.NEWSGROUPS)[0]);
1.208 + }
1.209 + posted = true;
1.210 + }
1.211 + else
1.212 + {
1.213 + StringBuilder buf = new StringBuilder();
1.214 + for (Address toa : msg.getAllRecipients())
1.215 + {
1.216 + buf.append(' ');
1.217 + buf.append(toa.toString());
1.218 + }
1.219 + buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
1.220 + Log.get().warning("No group for" + buf.toString());
1.221 + }
1.222 + return posted;
1.223 + }
1.224 + catch(Exception ex)
1.225 + {
1.226 + ex.printStackTrace();
1.227 + return false;
1.228 + }
1.229 + }
1.230 +
1.231 + /**
1.232 + * Mails a message received through NNTP to the appropriate mailing list.
1.233 + * This method MAY be called several times by PostCommand for the same
1.234 + * article.
1.235 + */
1.236 + public static void toList(Article article, String group)
1.237 + throws IOException, MessagingException, StorageBackendException
1.238 + {
1.239 + // Get mailing lists for the group of this article
1.240 + List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
1.241 +
1.242 + if(rcptAddresses == null || rcptAddresses.size() == 0)
1.243 + {
1.244 + Log.get().warning("No ML-address for " + group + " found.");
1.245 + return;
1.246 + }
1.247 +
1.248 + for(String rcptAddress : rcptAddresses)
1.249 + {
1.250 + // Compose message and send it via given SMTP-Host
1.251 + String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
1.252 + int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
1.253 + String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
1.254 + String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
1.255 + String smtpFrom = Config.inst().get(
1.256 + Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
1.257 +
1.258 + // TODO: Make Article cloneable()
1.259 + article.getMessageID(); // Make sure an ID is existing
1.260 + article.removeHeader(Headers.NEWSGROUPS);
1.261 + article.removeHeader(Headers.PATH);
1.262 + article.removeHeader(Headers.LINES);
1.263 + article.removeHeader(Headers.BYTES);
1.264 +
1.265 + article.setHeader("To", rcptAddress);
1.266 + //article.setHeader("Reply-To", listAddress);
1.267 +
1.268 + if (Config.inst().get(Config.MLSEND_RW_SENDER, false))
1.269 + {
1.270 + rewriteSenderAddress(article); // Set the SENDER address
1.271 + }
1.272 +
1.273 + SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
1.274 + smtpTransport.send(article, smtpFrom, rcptAddress);
1.275 + smtpTransport.close();
1.276 +
1.277 + Stats.getInstance().mailGatewayed(group);
1.278 + Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
1.279 + + " was delivered to " + rcptAddress + ".");
1.280 + }
1.281 + }
1.282 +
1.283 + /**
1.284 + * Sets the SENDER header of the given MimeMessage. This might be necessary
1.285 + * for moderated groups that does not allow the "normal" FROM sender.
1.286 + * @param msg
1.287 + * @throws javax.mail.MessagingException
1.288 + */
1.289 + private static void rewriteSenderAddress(Article msg)
1.290 + throws MessagingException
1.291 + {
1.292 + String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
1.293 +
1.294 + if(mlAddress != null)
1.295 + {
1.296 + msg.setHeader(Headers.SENDER, mlAddress);
1.297 + }
1.298 + else
1.299 + {
1.300 + throw new MessagingException("Cannot rewrite SENDER header!");
1.301 + }
1.302 + }
1.303 +
1.304 +}