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