Android Open Source - mailinglistmoderator List Server






From Project

Back to project page mailinglistmoderator.

License

The source code is released under:

Copyright (c) 2010, Magnus Hagander All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:...

If you think the Android project mailinglistmoderator listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/*
 * ListServer.java - This class holds an abstract base class for implementing a mailinglist server
 * //  w w w  . java 2 s.c om
 * Copyright (C) 2010 Magnus Hagander <magnus@hagander.net>
 * 
 * This software is released under the BSD license.
 */
package net.hagander.mailinglistmoderator.backend;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.security.KeyManagementException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.X509TrustManager;

import org.xmlpull.v1.XmlSerializer;

import net.hagander.mailinglistmoderator.backend.MailMessage.statuslevel;
import net.hagander.mailinglistmoderator.backend.providers.Dummy;
import net.hagander.mailinglistmoderator.backend.providers.Mailman;
import net.hagander.mailinglistmoderator.backend.providers.Majordomo2;
import net.hagander.mailinglistmoderator.backend.providers.Unconfigured;
import android.content.SharedPreferences;
import android.util.Log;

/**
 * 
 * @author Magnus Hagander <magnus@hagander.net>
 * 
 */
public abstract class ListServer {
  protected String listname;
  protected String rooturl;
  protected String password;
  protected String override_certname;
  protected String whitelisted_cert;

  protected boolean populated;
  protected boolean exceptioned;
  protected String status;
  protected Vector<MailMessage> messages;

  /**
   * 
   * @param name
   *            Name of the list
   * @param rooturl
   *            URL of the root mailinglist server installation (not including
   *            list name)
   * @param password
   *            Password to access the server (both read and write)
   */
  public ListServer(String name, String rooturl, String password, String override_certname, String whitelisted_cert) {
    this.listname = name;
    this.rooturl = rooturl;
    this.password = password;
    this.override_certname = override_certname;
    this.whitelisted_cert = whitelisted_cert;

    this.populated = false;
    this.exceptioned = false;
    this.messages = new Vector<MailMessage>();
  }

  /**
   * Create a list server instance, by figuring out which type of list it is,
   * and instantiating the proper class.
   * 
   * @param name
   *            Name of the list
   * @param rooturl
   *            Root URL of the server, not including the list name
   * @param password
   *            Password to access the server
   * @param override_certname
   *            SSL certificate name to accept
   * @param whitelisted_cert
   *            SSL certificate fingerprint to whitelist
   * @return A ListServer instance representing this server.
   */
  public static ListServer Create(String name, String rooturl, String password, String override_certname, String whitelisted_cert) {
    if (rooturl.startsWith("dummy:"))
      return new Dummy(name, rooturl, password);
    if (rooturl.contains("/admindb"))
      return new Mailman(name, rooturl, password, override_certname, whitelisted_cert);
    if (rooturl.contains("mj_wwwadm"))
      return new Majordomo2(name, rooturl, password, override_certname, whitelisted_cert);
    return new Unconfigured(name, rooturl, password);
  }

  /**
   * Create a ListServer instance by reading the application preferences for
   * it.
   * 
   * @param pref
   *            Instance of SharedPreferences to use.
   * @param name
   *            Name of the list.
   * @return A ListServer instance representing this server.
   */
  public static ListServer CreateFromPreference(SharedPreferences pref,
      String name) {
    String baseurl = pref.getString(name + "_baseurl", "");
    String password = pref.getString(name + "_password", "");
    String override_certname = pref.getString(name+"_overridecertname", "");
    String whitelisted_cert = pref.getString(name+"_whitelistedcert", "");

    return Create(name, baseurl, password, override_certname, whitelisted_cert);
  }

  /*
   * Abstract methods, do be implemented by child classes.
   */
  protected abstract Vector<MailMessage> EnumerateMessages();

  public abstract boolean doesIndividualModeration();

  public abstract boolean applyChanges(ListServerStatusCallbacks callbacks);

  /**
   * Return the name of the list.
   * 
   * @return the name of the list
   */
  public String getName() {
    return listname;
  }

  /**
   * Get the current status for this list (loading, number of messages etc).
   * 
   * @return a string representing the current status for this list
   */
  public String getStatus() {
    if (exceptioned)
      return String.format("Exception: %s", status);
    else if (populated)
      return status;
    else
      return "loading...";
  }

  /**
   * Check if this has been queried and populated with a list of mails to
   * moderate.
   * 
   * @return if the list is populated.
   */
  public boolean isPopulated() {
    return populated;
  }

  /**
   * Check if an exception has occurred when processing this list.
   * 
   * @return if the list is exceptioned.
   */
  public boolean isExceptioned() {
    // TODO Auto-generated method stub
    return exceptioned;
  }

  /**
   * Get all messages in the moderation queue on the list.
   * 
   * @return a list of all messages on this list
   */
  public Vector<MailMessage> getMessages() {
    return messages;
  }

  /**
   * Populate the list with messages by querying the server.
   * 
   * Also sets the local status string.
   */
  public void Populate() {
    messages.clear();
    populated = exceptioned = false;
    try {
      Vector<MailMessage> msglist = EnumerateMessages();
      if (msglist != null) {
        messages.addAll(EnumerateMessages());
        populated = true;
        status = String.format("%d unmoderated messages", messages.size());
      }
      else {
        // Status is assumed to be set by by the routine that failed
        populated = false;
        exceptioned = true;
      }
    }
    catch (RuntimeException e) {
      this.exceptioned = true;
      this.status = String.format("%s", e.getMessage());
      throw e;
    }
  }

  /**
   * Get the number of messages in the queue.
   * 
   * @return number of messages
   */
  public int count() {
    return messages.size();
  }

  /**
   * Check if the queue has any changes to apply (any messages marked as
   * Accept or Reject, not Defer)
   * 
   * @return if there are any queued changes.
   */
  public boolean hasChanges() {
    for (int i = 0; i < messages.size(); i++)
      if (messages.get(i).getStatus() != statuslevel.Defer)
        return true;
    return false;
  }

  /**
   * Interface listing callbacks from moderation operations that may take
   * time.
   */
  public interface ListServerStatusCallbacks {
    public void SetStatusMessage(String msg);

    public void SetProgressbarValue(int value);

    public void ShowError(String msg);

    public void SetMessageCount(int size);
  }

  /*
   * Utility functions for implementations to call.
   */

  /**
   * Connect and fetch an URL, returning a string with the contents of the
   * URL.
   */
  private Pattern DNPattern = Pattern.compile("^CN=([^,]+),", Pattern.CASE_INSENSITIVE);
  protected String FetchUrl(String url) {
    try {
      final URL u = new URL(url);
      URLConnection c = u.openConnection(java.net.Proxy.NO_PROXY);

      if (u.getProtocol().equals("https")) {
        HttpsURLConnection sslconn  = (HttpsURLConnection) c;
        if (override_certname != null && !override_certname.equals("")) {
          sslconn.setHostnameVerifier(new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
              /* For each certificate, check */
              X509Certificate cert;
              try {
                cert = (X509Certificate)session.getPeerCertificates()[0];
              }
              catch (SSLPeerUnverifiedException e) {
                throw new RuntimeException(String.format(
                    "Failed to verify peer for url: %s (%s)", e, u.toString()));
              }
              Matcher m = DNPattern.matcher(cert.getSubjectDN().getName());
              if (!m.find()) {
                throw new RuntimeException(String.format(
                    "Could not extract hostname from '%s' for url %s", cert.getSubjectDN(), u.toString()));
              }
              String sslname = m.group(1);
              if (sslname.equals(override_certname)) {
                /* Matched the overridden certname, so allow this connection */
                return true;
              }

              /*
               * Could return false here, but that won't show as
               * useful error message, so throw RuntimeEception
               * instead
               */
              throw new RuntimeException(String.format(
                  "Certificate hostname '%s' does not match expected hostname '%s'", sslname, override_certname));
            }
          });
        }

        /* Let's see if we should also check the actual certificate */
        if (whitelisted_cert != null && !whitelisted_cert.equals("")) {
          SSLContext context;
          try {
            context = SSLContext.getInstance("TLS");
          } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Could not find TLS context!");
          }
          try {
            context.init(null,
                new X509TrustManager[] { new X509TrustManager() {
                  public void checkClientTrusted(
                      X509Certificate[] chain, String authType)
                      throws CertificateException {
                  }

                  public void checkServerTrusted(
                      X509Certificate[] chain, String authType)
                      throws CertificateException {

                    MessageDigest md;
                    try {
                      md = MessageDigest.getInstance("SHA-1");
                    } catch (NoSuchAlgorithmException e) {
                      throw new RuntimeException("Could not find SHA-1 digest");
                    }
                    md.update(chain[0].getEncoded());
                    byte[] digest = md.digest();
                    BigInteger bi = new BigInteger(1, digest);
                      String fingerprint = String.format("%0" + (digest.length << 1) + "X", bi);

                      if (!fingerprint.equals(whitelisted_cert)) {
                        throw new CertificateException("Certificate fingerprint does not match the configured one!");
                      }

                      /* Otherwise, if it matches, we said override, so trust everything */
                  }

                  public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                  }
                } }, null);
          } catch (KeyManagementException e) {
            throw new RuntimeException(String.format("Unable to set up key management: %s", e.toString()));
          }
          sslconn.setSSLSocketFactory(context.getSocketFactory());
        }
      }
      InputStreamReader isr = new InputStreamReader(c.getInputStream());
      BufferedReader r = new BufferedReader(isr);
      StringWriter sw = new StringWriter();
      String line;
      while ((line = r.readLine()) != null) {
        sw.write(line);
        sw.write("\n");
      }
      r.close();
      isr.close();
      return sw.toString();
    } catch (MalformedURLException e) {
      throw new RuntimeException(String.format(
          "Failed to fetch url: %s (%s)", e, url));
    } catch (IOException e) {
      throw new RuntimeException(String.format(
          "Failed to fetch url: %s (%s)", e, url));
    }
  }

  public void writeXmlElement(XmlSerializer xml) throws IOException {
    xml.startTag(null, "list");
    xml.attribute(null, "name", listname);
    xml.attribute(null, "url", rooturl);
    xml.attribute(null, "password", password);
    if (override_certname != null && !override_certname.equals(""))
      xml.attribute(null, "overridecertname", override_certname);
    if (whitelisted_cert != null && !whitelisted_cert.equals(""))
      xml.attribute(null, "whitelistedcert", whitelisted_cert);
    xml.endTag(null, "list");
  }
}




Java Source Code List

net.hagander.mailinglistmoderator.MailinglistModerator.java
net.hagander.mailinglistmoderator.MessageViewActivity.java
net.hagander.mailinglistmoderator.QueueListActivity.java
net.hagander.mailinglistmoderator.ServerEditor.java
net.hagander.mailinglistmoderator.backend.ListServer.java
net.hagander.mailinglistmoderator.backend.MailMessage.java
net.hagander.mailinglistmoderator.backend.providers.Dummy.java
net.hagander.mailinglistmoderator.backend.providers.Mailman.java
net.hagander.mailinglistmoderator.backend.providers.Majordomo2.java
net.hagander.mailinglistmoderator.backend.providers.Unconfigured.java
net.hagander.mailinglistmoderator.glue.ListServerAdapter.java
net.hagander.mailinglistmoderator.glue.MailMessageAdapter.java
net.hagander.mailinglistmoderator.preferences.SSLCertDialogPreference.java