src/org/sonews/mlgw/Dispatcher.java
author cli
Sat Sep 10 18:18:05 2011 +0200 (2011-09-10)
changeset 45 7e24949b87b0
parent 36 c404a87db5b7
child 50 0bf10add82d9
permissions -rwxr-xr-x
HSQLDB backend support completed, but untested.
     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 	 * Chunks out the email address of the full List-Post header field.
    66 	 * @param listPostValue
    67 	 * @return The matching email address or null
    68 	 */
    69 	private static String chunkListPost(String listPostValue)
    70 	{
    71 		// listPostValue is of form "<mailto:dev@openoffice.org>"
    72 		Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
    73 		Matcher mailMatcher = mailPattern.matcher(listPostValue);
    74 		if (mailMatcher.find()) {
    75 			return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
    76 		} else {
    77 			return null;
    78 		}
    79 	}
    80 
    81 	/**
    82 	 * This method inspects the header of the given message, trying
    83 	 * to find the most appropriate recipient.
    84 	 * @param msg
    85 	 * @param fallback If this is false only List-Post and X-List-Post headers
    86 	 *                 are examined.
    87 	 * @return null or fitting group name for the given message.
    88 	 */
    89 	private static List<String> getGroupFor(final Message msg, final boolean fallback)
    90 		throws MessagingException, StorageBackendException
    91 	{
    92 		List<String> groups = null;
    93 
    94 		// Is there a List-Post header?
    95 		String[] listPost = msg.getHeader(Headers.LIST_POST);
    96 		InternetAddress listPostAddr;
    97 
    98 		if (listPost == null || listPost.length == 0 || "".equals(listPost[0])) {
    99 			// Is there a X-List-Post header?
   100 			listPost = msg.getHeader(Headers.X_LIST_POST);
   101 		}
   102 
   103 		if (listPost != null && listPost.length > 0
   104 			&& !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null) {
   105 			// listPost[0] is of form "<mailto:dev@openoffice.org>"
   106 			listPost[0] = chunkListPost(listPost[0]);
   107 			listPostAddr = new InternetAddress(listPost[0], false);
   108 			groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
   109 		} else if (fallback) {
   110 			Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
   111 			groups = new ArrayList<String>();
   112 			// Fallback to TO/CC/BCC addresses
   113 			Address[] to = msg.getAllRecipients();
   114 			for (Address toa : to) // Address can have '<' '>' around
   115 			{
   116 				if (toa instanceof InternetAddress) {
   117 					List<String> g = StorageManager.current().getGroupsForList(((InternetAddress) toa).getAddress());
   118 					groups.addAll(g);
   119 				}
   120 			}
   121 		}
   122 
   123 		return groups;
   124 	}
   125 
   126 	/**
   127 	 * Posts a message that was received from a mailing list to the
   128 	 * appropriate newsgroup.
   129 	 * If the message already exists in the storage, this message checks
   130 	 * if it must be posted in an additional group. This can happen for
   131 	 * crosspostings in different mailing lists.
   132 	 * @param msg
   133 	 */
   134 	public static boolean toGroup(final Message msg)
   135 	{
   136 		if (msg == null) {
   137 			throw new IllegalArgumentException("Argument 'msg' must not be null!");
   138 		}
   139 
   140 		try {
   141 			// Create new Article object
   142 			Article article = new Article(msg);
   143 			boolean posted = false;
   144 
   145 			// Check if this mail is already existing the storage
   146 			boolean updateReq =
   147 				StorageManager.current().isArticleExisting(article.getMessageID());
   148 
   149 			List<String> newsgroups = getGroupFor(msg, !updateReq);
   150 			List<String> oldgroups = new ArrayList<String>();
   151 			if (updateReq) {
   152 				// Check for duplicate entries of the same group
   153 				Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
   154 				if (oldArticle != null) {
   155 					List<Group> oldGroups = oldArticle.getGroups();
   156 					for (Group oldGroup : oldGroups) {
   157 						if (!newsgroups.contains(oldGroup.getName())) {
   158 							oldgroups.add(oldGroup.getName());
   159 						}
   160 					}
   161 				}
   162 			}
   163 
   164 			if (newsgroups.size() > 0) {
   165 				newsgroups.addAll(oldgroups);
   166 				StringBuilder groups = new StringBuilder();
   167 				for (int n = 0; n < newsgroups.size(); n++) {
   168 					groups.append(newsgroups.get(n));
   169 					if (n + 1 != newsgroups.size()) {
   170 						groups.append(',');
   171 					}
   172 				}
   173 				Log.get().info("Posting to group " + groups.toString());
   174 
   175 				article.setGroup(groups.toString());
   176 				//article.removeHeader(Headers.REPLY_TO);
   177 				//article.removeHeader(Headers.TO);
   178 
   179 				// Write article to database
   180 				if (updateReq) {
   181 					Log.get().info("Updating " + article.getMessageID()
   182 						+ " with additional groups");
   183 					StorageManager.current().delete(article.getMessageID());
   184 					StorageManager.current().addArticle(article);
   185 				} else {
   186 					Log.get().info("Gatewaying " + article.getMessageID() + " to "
   187 						+ article.getHeader(Headers.NEWSGROUPS)[0]);
   188 					StorageManager.current().addArticle(article);
   189 					Stats.getInstance().mailGatewayed(
   190 						article.getHeader(Headers.NEWSGROUPS)[0]);
   191 				}
   192 				posted = true;
   193 			} else {
   194 				StringBuilder buf = new StringBuilder();
   195 				for (Address toa : msg.getAllRecipients()) {
   196 					buf.append(' ');
   197 					buf.append(toa.toString());
   198 				}
   199 				buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
   200 				Log.get().warning("No group for" + buf.toString());
   201 			}
   202 			return posted;
   203 		} catch (Exception ex) {
   204 			ex.printStackTrace();
   205 			return false;
   206 		}
   207 	}
   208 
   209 	/**
   210 	 * Mails a message received through NNTP to the appropriate mailing list.
   211 	 * This method MAY be called several times by PostCommand for the same
   212 	 * article.
   213 	 */
   214 	public static void toList(Article article, String group)
   215 		throws IOException, MessagingException, StorageBackendException
   216 	{
   217 		// Get mailing lists for the group of this article
   218 		List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
   219 
   220 		if (rcptAddresses == null || rcptAddresses.size() == 0) {
   221 			Log.get().warning("No ML-address for " + group + " found.");
   222 			return;
   223 		}
   224 
   225 		for (String rcptAddress : rcptAddresses) {
   226 			// Compose message and send it via given SMTP-Host
   227 			String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
   228 			int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
   229 			String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
   230 			String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
   231 			String smtpFrom = Config.inst().get(
   232 				Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
   233 
   234 			// TODO: Make Article cloneable()
   235 			article.getMessageID(); // Make sure an ID is existing
   236 			article.removeHeader(Headers.NEWSGROUPS);
   237 			article.removeHeader(Headers.PATH);
   238 			article.removeHeader(Headers.LINES);
   239 			article.removeHeader(Headers.BYTES);
   240 
   241 			article.setHeader("To", rcptAddress);
   242 			//article.setHeader("Reply-To", listAddress);
   243 
   244 			if (Config.inst().get(Config.MLSEND_RW_SENDER, false)) {
   245 				rewriteSenderAddress(article); // Set the SENDER address
   246 			}
   247 
   248 			SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
   249 			smtpTransport.send(article, smtpFrom, rcptAddress);
   250 			smtpTransport.close();
   251 
   252 			Stats.getInstance().mailGatewayed(group);
   253 			Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
   254 				+ " was delivered to " + rcptAddress + ".");
   255 		}
   256 	}
   257 
   258 	/**
   259 	 * Sets the SENDER header of the given MimeMessage. This might be necessary
   260 	 * for moderated groups that does not allow the "normal" FROM sender.
   261 	 * @param msg
   262 	 * @throws javax.mail.MessagingException
   263 	 */
   264 	private static void rewriteSenderAddress(Article msg)
   265 		throws MessagingException
   266 	{
   267 		String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
   268 
   269 		if (mlAddress != null) {
   270 			msg.setHeader(Headers.SENDER, mlAddress);
   271 		} else {
   272 			throw new MessagingException("Cannot rewrite SENDER header!");
   273 		}
   274 	}
   275 }