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