src/org/sonews/mlgw/Dispatcher.java
author cli
Tue, 07 Jun 2011 11:55:22 +0200
changeset 44 5d7d1adf387f
parent 37 74139325d305
child 50 0bf10add82d9
permissions -rwxr-xr-x
Work on hsqldb support
chris@1
     1
/*
chris@1
     2
 *   SONEWS News Server
chris@1
     3
 *   see AUTHORS for the list of contributors
chris@1
     4
 *
chris@1
     5
 *   This program is free software: you can redistribute it and/or modify
chris@1
     6
 *   it under the terms of the GNU General Public License as published by
chris@1
     7
 *   the Free Software Foundation, either version 3 of the License, or
chris@1
     8
 *   (at your option) any later version.
chris@1
     9
 *
chris@1
    10
 *   This program is distributed in the hope that it will be useful,
chris@1
    11
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
chris@1
    12
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
chris@1
    13
 *   GNU General Public License for more details.
chris@1
    14
 *
chris@1
    15
 *   You should have received a copy of the GNU General Public License
chris@1
    16
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
chris@1
    17
 */
chris@1
    18
chris@1
    19
package org.sonews.mlgw;
chris@1
    20
chris@1
    21
import java.io.IOException;
chris@1
    22
import java.util.ArrayList;
chris@1
    23
import java.util.List;
cli@12
    24
import java.util.regex.Matcher;
cli@12
    25
import java.util.regex.Pattern;
chris@1
    26
import javax.mail.Address;
chris@1
    27
import javax.mail.Authenticator;
chris@1
    28
import javax.mail.Message;
chris@1
    29
import javax.mail.MessagingException;
chris@1
    30
import javax.mail.PasswordAuthentication;
chris@1
    31
import javax.mail.internet.InternetAddress;
chris@3
    32
import org.sonews.config.Config;
chris@3
    33
import org.sonews.storage.Article;
cli@12
    34
import org.sonews.storage.Group;
chris@3
    35
import org.sonews.storage.Headers;
chris@3
    36
import org.sonews.storage.StorageBackendException;
chris@3
    37
import org.sonews.storage.StorageManager;
chris@1
    38
import org.sonews.util.Log;
chris@1
    39
import org.sonews.util.Stats;
chris@1
    40
chris@1
    41
/**
cli@12
    42
 * Dispatches messages from mailing list to newsserver or vice versa.
chris@1
    43
 * @author Christian Lins
chris@1
    44
 * @since sonews/0.5.0
chris@1
    45
 */
cli@37
    46
public class Dispatcher
chris@1
    47
{
chris@1
    48
cli@37
    49
	static class PasswordAuthenticator extends Authenticator
cli@37
    50
	{
chris@1
    51
cli@37
    52
		@Override
cli@37
    53
		public PasswordAuthentication getPasswordAuthentication()
cli@37
    54
		{
cli@37
    55
			final String username =
cli@37
    56
				Config.inst().get(Config.MLSEND_USER, "user");
cli@37
    57
			final String password =
cli@37
    58
				Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
cli@12
    59
cli@37
    60
			return new PasswordAuthentication(username, password);
cli@37
    61
		}
cli@37
    62
	}
cli@12
    63
cli@37
    64
	/**
cli@37
    65
	 * Chunks out the email address of the full List-Post header field.
cli@37
    66
	 * @param listPostValue
cli@37
    67
	 * @return The matching email address or null
cli@37
    68
	 */
cli@37
    69
	private static String chunkListPost(String listPostValue)
cli@37
    70
	{
cli@37
    71
		// listPostValue is of form "<mailto:dev@openoffice.org>"
cli@37
    72
		Pattern mailPattern = Pattern.compile("(\\w+[-|.])*\\w+@(\\w+.)+\\w+");
cli@37
    73
		Matcher mailMatcher = mailPattern.matcher(listPostValue);
cli@37
    74
		if (mailMatcher.find()) {
cli@37
    75
			return listPostValue.substring(mailMatcher.start(), mailMatcher.end());
cli@37
    76
		} else {
cli@37
    77
			return null;
cli@37
    78
		}
cli@37
    79
	}
cli@12
    80
cli@37
    81
	/**
cli@37
    82
	 * This method inspects the header of the given message, trying
cli@37
    83
	 * to find the most appropriate recipient.
cli@37
    84
	 * @param msg
cli@37
    85
	 * @param fallback If this is false only List-Post and X-List-Post headers
cli@37
    86
	 *                 are examined.
cli@37
    87
	 * @return null or fitting group name for the given message.
cli@37
    88
	 */
cli@37
    89
	private static List<String> getGroupFor(final Message msg, final boolean fallback)
cli@37
    90
		throws MessagingException, StorageBackendException
cli@37
    91
	{
cli@37
    92
		List<String> groups = null;
cli@12
    93
cli@37
    94
		// Is there a List-Post header?
cli@37
    95
		String[] listPost = msg.getHeader(Headers.LIST_POST);
cli@37
    96
		InternetAddress listPostAddr;
cli@12
    97
cli@37
    98
		if (listPost == null || listPost.length == 0 || "".equals(listPost[0])) {
cli@37
    99
			// Is there a X-List-Post header?
cli@37
   100
			listPost = msg.getHeader(Headers.X_LIST_POST);
cli@37
   101
		}
cli@37
   102
cli@37
   103
		if (listPost != null && listPost.length > 0
cli@37
   104
			&& !"".equals(listPost[0]) && chunkListPost(listPost[0]) != null) {
cli@37
   105
			// listPost[0] is of form "<mailto:dev@openoffice.org>"
cli@37
   106
			listPost[0] = chunkListPost(listPost[0]);
cli@37
   107
			listPostAddr = new InternetAddress(listPost[0], false);
cli@37
   108
			groups = StorageManager.current().getGroupsForList(listPostAddr.getAddress());
cli@37
   109
		} else if (fallback) {
cli@37
   110
			Log.get().info("Using fallback recipient discovery for: " + msg.getSubject());
cli@37
   111
			groups = new ArrayList<String>();
cli@37
   112
			// Fallback to TO/CC/BCC addresses
cli@37
   113
			Address[] to = msg.getAllRecipients();
cli@37
   114
			for (Address toa : to) // Address can have '<' '>' around
cli@37
   115
			{
cli@37
   116
				if (toa instanceof InternetAddress) {
cli@37
   117
					List<String> g = StorageManager.current().getGroupsForList(((InternetAddress) toa).getAddress());
cli@37
   118
					groups.addAll(g);
cli@37
   119
				}
cli@37
   120
			}
cli@37
   121
		}
cli@37
   122
cli@37
   123
		return groups;
cli@37
   124
	}
cli@37
   125
cli@37
   126
	/**
cli@37
   127
	 * Posts a message that was received from a mailing list to the
cli@37
   128
	 * appropriate newsgroup.
cli@37
   129
	 * If the message already exists in the storage, this message checks
cli@37
   130
	 * if it must be posted in an additional group. This can happen for
cli@37
   131
	 * crosspostings in different mailing lists.
cli@37
   132
	 * @param msg
cli@37
   133
	 */
cli@37
   134
	public static boolean toGroup(final Message msg)
cli@36
   135
	{
cli@37
   136
		if (msg == null) {
cli@37
   137
			throw new IllegalArgumentException("Argument 'msg' must not be null!");
cli@37
   138
		}
cli@36
   139
cli@37
   140
		try {
cli@37
   141
			// Create new Article object
cli@37
   142
			Article article = new Article(msg);
cli@37
   143
			boolean posted = false;
cli@12
   144
cli@37
   145
			// Check if this mail is already existing the storage
cli@37
   146
			boolean updateReq =
cli@37
   147
				StorageManager.current().isArticleExisting(article.getMessageID());
cli@12
   148
cli@37
   149
			List<String> newsgroups = getGroupFor(msg, !updateReq);
cli@37
   150
			List<String> oldgroups = new ArrayList<String>();
cli@37
   151
			if (updateReq) {
cli@37
   152
				// Check for duplicate entries of the same group
cli@37
   153
				Article oldArticle = StorageManager.current().getArticle(article.getMessageID());
cli@37
   154
				if (oldArticle != null) {
cli@37
   155
					List<Group> oldGroups = oldArticle.getGroups();
cli@37
   156
					for (Group oldGroup : oldGroups) {
cli@37
   157
						if (!newsgroups.contains(oldGroup.getName())) {
cli@37
   158
							oldgroups.add(oldGroup.getName());
cli@37
   159
						}
cli@37
   160
					}
cli@37
   161
				}
cli@37
   162
			}
cli@37
   163
cli@37
   164
			if (newsgroups.size() > 0) {
cli@37
   165
				newsgroups.addAll(oldgroups);
cli@37
   166
				StringBuilder groups = new StringBuilder();
cli@37
   167
				for (int n = 0; n < newsgroups.size(); n++) {
cli@37
   168
					groups.append(newsgroups.get(n));
cli@37
   169
					if (n + 1 != newsgroups.size()) {
cli@37
   170
						groups.append(',');
cli@37
   171
					}
cli@37
   172
				}
cli@37
   173
				Log.get().info("Posting to group " + groups.toString());
cli@37
   174
cli@37
   175
				article.setGroup(groups.toString());
cli@37
   176
				//article.removeHeader(Headers.REPLY_TO);
cli@37
   177
				//article.removeHeader(Headers.TO);
cli@37
   178
cli@37
   179
				// Write article to database
cli@37
   180
				if (updateReq) {
cli@37
   181
					Log.get().info("Updating " + article.getMessageID()
cli@37
   182
						+ " with additional groups");
cli@37
   183
					StorageManager.current().delete(article.getMessageID());
cli@37
   184
					StorageManager.current().addArticle(article);
cli@37
   185
				} else {
cli@37
   186
					Log.get().info("Gatewaying " + article.getMessageID() + " to "
cli@37
   187
						+ article.getHeader(Headers.NEWSGROUPS)[0]);
cli@37
   188
					StorageManager.current().addArticle(article);
cli@37
   189
					Stats.getInstance().mailGatewayed(
cli@37
   190
						article.getHeader(Headers.NEWSGROUPS)[0]);
cli@37
   191
				}
cli@37
   192
				posted = true;
cli@37
   193
			} else {
cli@37
   194
				StringBuilder buf = new StringBuilder();
cli@37
   195
				for (Address toa : msg.getAllRecipients()) {
cli@37
   196
					buf.append(' ');
cli@37
   197
					buf.append(toa.toString());
cli@37
   198
				}
cli@37
   199
				buf.append(" " + article.getHeader(Headers.LIST_POST)[0]);
cli@37
   200
				Log.get().warning("No group for" + buf.toString());
cli@37
   201
			}
cli@37
   202
			return posted;
cli@37
   203
		} catch (Exception ex) {
cli@37
   204
			ex.printStackTrace();
cli@37
   205
			return false;
cli@36
   206
		}
cli@37
   207
	}
chris@3
   208
cli@37
   209
	/**
cli@37
   210
	 * Mails a message received through NNTP to the appropriate mailing list.
cli@37
   211
	 * This method MAY be called several times by PostCommand for the same
cli@37
   212
	 * article.
cli@37
   213
	 */
cli@37
   214
	public static void toList(Article article, String group)
cli@37
   215
		throws IOException, MessagingException, StorageBackendException
cli@37
   216
	{
cli@37
   217
		// Get mailing lists for the group of this article
cli@37
   218
		List<String> rcptAddresses = StorageManager.current().getListsForGroup(group);
cli@12
   219
cli@37
   220
		if (rcptAddresses == null || rcptAddresses.size() == 0) {
cli@37
   221
			Log.get().warning("No ML-address for " + group + " found.");
cli@37
   222
			return;
cli@37
   223
		}
cli@12
   224
cli@37
   225
		for (String rcptAddress : rcptAddresses) {
cli@37
   226
			// Compose message and send it via given SMTP-Host
cli@37
   227
			String smtpHost = Config.inst().get(Config.MLSEND_HOST, "localhost");
cli@37
   228
			int smtpPort = Config.inst().get(Config.MLSEND_PORT, 25);
cli@37
   229
			String smtpUser = Config.inst().get(Config.MLSEND_USER, "user");
cli@37
   230
			String smtpPw = Config.inst().get(Config.MLSEND_PASSWORD, "mysecret");
cli@37
   231
			String smtpFrom = Config.inst().get(
cli@37
   232
				Config.MLSEND_ADDRESS, article.getHeader(Headers.FROM)[0]);
cli@12
   233
cli@37
   234
			// TODO: Make Article cloneable()
cli@37
   235
			article.getMessageID(); // Make sure an ID is existing
cli@37
   236
			article.removeHeader(Headers.NEWSGROUPS);
cli@37
   237
			article.removeHeader(Headers.PATH);
cli@37
   238
			article.removeHeader(Headers.LINES);
cli@37
   239
			article.removeHeader(Headers.BYTES);
chris@1
   240
cli@37
   241
			article.setHeader("To", rcptAddress);
cli@37
   242
			//article.setHeader("Reply-To", listAddress);
chris@1
   243
cli@37
   244
			if (Config.inst().get(Config.MLSEND_RW_SENDER, false)) {
cli@37
   245
				rewriteSenderAddress(article); // Set the SENDER address
cli@37
   246
			}
chris@1
   247
cli@37
   248
			SMTPTransport smtpTransport = new SMTPTransport(smtpHost, smtpPort);
cli@37
   249
			smtpTransport.send(article, smtpFrom, rcptAddress);
cli@37
   250
			smtpTransport.close();
chris@1
   251
cli@37
   252
			Stats.getInstance().mailGatewayed(group);
cli@37
   253
			Log.get().info("MLGateway: Mail " + article.getHeader("Subject")[0]
cli@37
   254
				+ " was delivered to " + rcptAddress + ".");
cli@37
   255
		}
cli@37
   256
	}
chris@1
   257
cli@37
   258
	/**
cli@37
   259
	 * Sets the SENDER header of the given MimeMessage. This might be necessary
cli@37
   260
	 * for moderated groups that does not allow the "normal" FROM sender.
cli@37
   261
	 * @param msg
cli@37
   262
	 * @throws javax.mail.MessagingException
cli@37
   263
	 */
cli@37
   264
	private static void rewriteSenderAddress(Article msg)
cli@37
   265
		throws MessagingException
cli@37
   266
	{
cli@37
   267
		String mlAddress = Config.inst().get(Config.MLSEND_ADDRESS, null);
chris@1
   268
cli@37
   269
		if (mlAddress != null) {
cli@37
   270
			msg.setHeader(Headers.SENDER, mlAddress);
cli@37
   271
		} else {
cli@37
   272
			throw new MessagingException("Cannot rewrite SENDER header!");
cli@37
   273
		}
cli@37
   274
	}
chris@1
   275
}