// Copyright 2000-2005 the Contributors, as shown in the revision logs. // Licensed under the Apache Public Source License 2.0 ("the License"). // You may not use this file except in compliance with the License. package org.ibex.mail; import org.ibex.util.*; import org.ibex.io.*; import org.ibex.mail.target.*; import org.ibex.mail.protocol.*; import java.util.*; import java.io.*; import java.net.*; import javax.servlet.*; import javax.servlet.http.*; // TODO: RFC 2919 (List-ID) // TODO: RFC 2142 (*-Request@) // TODO: RFC 2369 (URLs as Meta-Syntax) // FEATURE: store interesting/important stuff in sqlite public class MailingList extends Mailbox.MailboxWrapper { // FIXME public MailingList(File path, FileBasedMailbox fbm) throws IOException { super(fbm); this.path = path; this.mailbox = fbm; properties = new PropertiesFile(new File(path.getCanonicalPath() + File.separatorChar + "properties")); address = new Address(properties.get("address")); homepage = properties.get("homepage"); one_line_description = properties.get("description"); message_footer = properties.get("footer"); } public void banner(HttpServletRequest request, HttpServletResponse response) throws IOException { String basename = request.getRequestURL()+""; PrintWriter pw = new PrintWriter(response.getWriter()); /* pw.println(""); pw.println(" "); pw.println(" "); pw.println(" "); pw.println(" "); */ String confirm = request.getParameter("confirm"); if (confirm != null) { Subscribe sub = (Subscribe)Confirmation.decode(confirm, secret, new Date()); String email = sub.email; synchronized(this) { String path = this.path.getAbsolutePath() + File.separatorChar + "subscribers" + File.separatorChar+sub.email; if (sub.un) new File(path).delete(); else { Log.warn(null, "creating " + path); new FileOutputStream(path).close(); } } pw.println(" successfully "+sub.adj+"d " + email + " to " + properties.get("address")); } else { String action = request.getParameter("action"); String email = request.getParameter("email"); if (action != null) { Subscribe sub = new Subscribe(email, request.getRequestURL().toString(), action.equals("unsubscribe")); sub.signAndSend(new Address(properties.get("owner")), secret, new Date()); pw.println("a confirmation email has been sent to " + email + "; click the enclosed link to confirm your request to " + action); } else { pw.println("
"); pw.println(" "+properties.get("address")+"
"); pw.println(" "+properties.get("description")+" "); pw.println("
"); pw.println("access via: "); if (path.getAbsolutePath().startsWith("/afs/")) pw.println("[AFS] "); pw.println("[NNTP]"); pw.println("[SMTP]"); pw.println("[HTTP]"); pw.println("
"); pw.println("
"); pw.println(" "); pw.println(" "); pw.println(" "); pw.println("
"); pw.println("
"); } } //pw.println(" "); //pw.println(""); pw.flush(); } public class Subscribe extends Confirmation { public String email; public String basename; public boolean un; public String adj; public Subscribe(String email, String basename, boolean un) { super(new Address(email), new Date().getTime() + (1000 * 60 * 60 * 24)); this.email = email; this.basename = basename; this.un = un; this.adj = un ? "unsubscribe" : "subscribe"; } public String getDescription() { return adj + " " + email + " to " + properties.get("address"); } public String getURL(String tail) { return basename + "?frame=banner&confirm="+URLEncoder.encode(tail); } } private final File path; private final PropertiesFile properties; private final FileBasedMailbox mailbox; private final long secret = new Random().nextLong(); public final Address address; public String homepage; public String one_line_description; public String message_footer; public int bounceThreshhold = 10; public static class Subscriber { public Subscriber(Address a) { this.address = a; } public Address address; } // Pooling ////////////////////////////////////////////////////////////////////////////// public Iterable subscribers() { return new Iterable() { public java.util.Iterator iterator() { final File subdir = new File(path.getAbsolutePath() + File.separatorChar + "subscribers"); if (!subdir.exists()) subdir.mkdirs(); final String[] subs = !subdir.isDirectory() ? new String[0] : subdir.list(); return new java.util.Iterator() { int i=0; Subscriber prep = null; public void remove() { try { new File(subdir.getAbsolutePath() + File.separatorChar + subs[i++]).delete(); } catch (Exception e) { Log.error(MailingList.class, e); } } public boolean hasNext() { if (prep==null) prep = next(); return prep!=null; } public Subscriber next() { if (prep!=null) { Subscriber ret = prep; prep = null; return ret; } while(i cache = new HashMap(); public static MailingList getMailingList(String path) throws IOException { return getMailingList(new File(path)); } public static MailingList getMailingList(File path) throws IOException { if (!path.exists()) path.mkdirs(); MailingList ret = cache.get(path.getCanonicalPath()); if (ret==null) cache.put(path.getCanonicalPath(), ret = new MailingList(path)); return ret; } */ // Methods ////////////////////////////////////////////////////////////////////////////// public void add(Message message) { try { accept(message); } catch (Exception e) { throw new RuntimeException(e); } } public void add(Message message, int flags) { add(message); /* FIXME: flags? */ } public void accept(Message m) throws MailException { try { StringBuffer buf = new StringBuffer(); m.getBody().getStream().transcribe(buf); Headers head = new Headers(m.headers, new String[] { "List-Id", one_line_description + "<"+address+">", "Subject", properties.get("prefix") + " " + m.headers.get("Subject") }); m = Message.newMessage(Fountain.Util.concat(new Fountain[] { head, Fountain.Util.create("\r\n"), Fountain.Util.create(buf.toString()) })); Log.warn(MailingList.class, "archiving list message " + m.subject); mailbox.accept(m); for(Subscriber s : subscribers()) try { Log.warn(MailingList.class, " trying " + s.address); SMTP.enqueue(m.withEnvelope(m.envelopeFrom, s.address)); Log.warn("[list]", "successfully sent to " + s); } catch (Exception e2) { Log.error("[list]", e2); } } catch (Exception e) { throw new RuntimeException(e); } } //public Filter[] filters = new Filter[0]; //public static class Filter { // public class EmergencyModerationFilter { } // public class MaximumLengthFilter { } // public class SpamFilter { } // public class MIMETypes { } // public class MungeReplyTo { } // public class AnonymizeSender { public boolean uncorrelated; } //} } //public static enum UserType { Administrator, Moderator, Member } //public static enum SubscriptionType { All, None, Digest, MimeDigest } //public static enum Visibility { Members, Public, Nobody } //public static enum Action { Accept, Hold, Reject } //public Visibility listVisibility = Visibility.Nobody; //public Visibility membershipVisibility = Visibility.Nobody; //public Visibility archiveVisibility = Visibility.Members; //public Action defaultPostingType = Action.Hold; //public Action posting = Action.Accept; //public UserType type = UserType.Member; //public SubscriptionType subscription = SubscriptionType.All; //public boolean send_copy_of_own_post = false; //public boolean filter_duplicates_when_ccd = true;