// Seneschal e-mail list manager
// Williams
import com.jthomas.pop.*;
import com.al_williams.SMTP.*;
import java.util.*;
import java.io.*;

public class Seneschal {
    String smtpserver; 
    Properties maillist;  // list of members
    static Properties options;  // options
    // helper functions to get addresses
    public static String getAddress() { 
       return getOpt("address","list@al-williams.com"); 
       } 
    public static String getAdmin() { 
       return getOpt("adminAddress","listadmin@al-williams.com"); 
       }

    public static void main(String arg[]) { 
       String cfgfile="maillist.cfg";
       if (arg.length>1) {
	    System.out.println("Usage: Seneschal [configfile]"); 
	    System.exit(1);
       }
       if (arg.length==1) cfgfile=arg[0];
       options=new Properties();
       try {
	   options.load(new FileInputStream(cfgfile));
       }
       catch (IOException ioe) {
	   // can't open setup file?
	   System.out.println("Can't open configuration file "+ioe);
	   System.exit(9);
       }

       // Everything is ready to go, so start
       new Seneschal().go(arg);
   }


    // Real main routine
    public void go(String [] arg) {
	// set STMP port
	SMTP.smtpPort=getIntOpt("SMTPPort",25); 
	maillist=new Properties(); 
	// load subscribers list
	try { 
	    maillist.load(new FileInputStream(
            getOpt("list","maillist"))); 
	} 
	catch (IOException ioe) { 
	    System.out.println(ioe);  
	    System.exit(2); 
	}
	// Set up pop server
	pop3 pop = new pop3(getOpt("popserver"), 
			    getOpt("popuser"), getOpt("poppw")); 
	smtpserver=getOpt("smtpserver");  
	popStatus status=pop.connect(), xstatus; 
	if (status.OK()) 
        status = pop.login();
	if (status.OK()) { 
         status = pop.list();  // check mail
         String[] responses = status.Responses();
	 String[] xresponses;
	 int n;
	 // for each message....
         for(int i=0; i< responses.length; i++) {
	   n=responses[i].indexOf(' ');
	   n=Integer.parseInt(responses[i].substring(0,n));
	   // read it
	   xstatus=pop.retr(n);
	   if (xstatus.OK()) {
	       xresponses = xstatus.Responses();
	       // check for recipient
	       for (int n1=0;n<xresponses.length;n1++) {
		   String to;
		   boolean adminflg=false;
		   if (xresponses[n1].length()==0 ) break; 
		   if (!xresponses[n1].substring(0,3).
                  equalsIgnoreCase("to:")) continue;
		   if (xresponses[n1].indexOf(getAdmin())!=-1)
                  adminflg=true;
		   if (!adminflg && 
                  xresponses[n1].indexOf(getAddress())==-1) continue;
		   // this e-mail message is for me
		   // do something with e-mail message #n
		   // including delete it most likely
		   if (action(pop,n,adminflg))
 		      pop.dele(n); // kill message
	       }
	   } else { System.out.println("RETR ERROR"); }

         }
         status = pop.quit(); // must quit to delete properly
     }
   }

    // This is the low-level action that, by default,
    // builds a hashtable of the headers and a string of the message
    // only override this if you want greater control
    public boolean action(pop3 pop, int n,boolean adminflg) {
	boolean header=true;
	popStatus status=pop.retr(n);
	if (!status.OK()) return false;
	String [] responses = status.Responses();
	StringBuffer message = new StringBuffer();
	Hashtable headers = new Hashtable();
	for (int i=0;i<responses.length;i++) {
	    if (responses[i].length()==0) {
		header=false;
		continue;
	    }
	    if (header) {
		int n1=responses[i].indexOf(':');
		if (n1!=-1) 
               headers.put(responses[i].substring(0,n1).
                 trim(),responses[i].substring(n1+1).trim());
	    }
	    else {
		message.append(responses[i]);
		message.append("\n");
	    }
	}
	if (adminflg)
	    return admin(headers,message.toString());
	else
	    return action(headers,message.toString());
    }

    // This is the action routine for normal mail
    public boolean action(Hashtable headers, String message) {
	SMTP smtp=new SMTP(smtpserver);
	int rc;
	MailMessage msg; 
        message+=
          "\nSent by Seneschal by Al Williams\nTo unsubscribe " +
	    "send unsubscribe to " + getAdmin();
	// loop for all members
	msg=new MailMessage(getAddress(),"",
			    (String)headers.get("Subject"),message);
	for (Enumeration e=maillist.keys();e.hasMoreElements();) {
	    msg.to=(String)e.nextElement();
	    rc=smtp.sendMail(msg);
  	    if (rc!=0) {
	      System.out.println("Error " + rc);
	      System.out.println(smtp.getLastResponse());
	   }
	}
        return true;
    }

    // This is the admin routine that handles messages to the admin
    public boolean admin(Hashtable headers, String message) {
	// commands subscribe, unsubscribe
	boolean rv=true;
	StringTokenizer strtok = new StringTokenizer(message);
	while (strtok.hasMoreTokens()) {
	    String tok=strtok.nextToken();
	    if (tok.equalsIgnoreCase("subscribe")) {
		rv&=subscribe(baseEmail((String)headers.get("From")));
	    }
	    if (tok.equalsIgnoreCase("unsubscribe")) {
		rv&=unsubscribe(baseEmail((String)headers.get("From")));
	    }
	}
	return rv;  // all commands must succeed or fail
    }

    // subscribe and send confirming e-mail
    private boolean subscribe(String email) {
	boolean rv;
	MailMessage msg=new MailMessage("list",email,
          "You are subscribed",
          "This message is to confirm that you have " +
          "subscribed to the list\nIf you wish to unsubscribe " +
          "send unsubscribe to " + getAdmin());
	SMTP smtp=new SMTP(smtpserver);
	maillist.put(email,"0");
	if (rv=saveList())
  	   rv&=smtp.sendMail(msg)==0;
	return rv;
    }

    // unsubscribe and send confirming e-mail
    private boolean unsubscribe(String email) {
	boolean rv;
	MailMessage msg=new MailMessage("list",email,
          "You have unsubscribed",
          "This message is to confirm that you have " +
          "unsubscribed to the list\nIf you wish to subscribe " +
          "send subscribe to "+ getAdmin());
	SMTP smtp=new SMTP(smtpserver);
	maillist.remove(email);
	if (rv=saveList())
  	   rv&=smtp.sendMail(msg)==0;
	return rv;
    }


    // Strip e-mail from within <>
    private String baseEmail(String email) {
	int n,n1;
	n=email.indexOf('<');
	if (n==-1) return email;
	n1=email.indexOf('>');
	if (n1==-1) return email; //?
	return email.substring(n+1,n1);
    }

    // Save the list back to property file
    private boolean saveList() {
	try {
	    maillist.store(new FileOutputStream(
            getOpt("list","maillist")),"");
	    return true;
	}
	catch (IOException e) {
	    System.out.println("Can't save mail list: " + e);
	    return false;
	}
    }


    // Get an option with a default value (String)
    private static String getOpt(String key, String def) {
	String s;
	s=(String)options.get(key);
	if (s==null || s.equals("")) s=def;
	return s;
    }

    // Get an option
    private static String getOpt(String key) {
	return (String)options.get(key);
    }

    // Get an integer option
    private static int getIntOpt(String key, int def) {
	String s=getOpt(key,"");
	if (s.equals("")) return def;
        return def;
	}

}