From c0cdaca74524315b01e9973c027e689627d3d58e Mon Sep 17 00:00:00 2001 From: adam Date: Mon, 9 Jul 2007 02:00:36 +0000 Subject: [PATCH] add mailbox acls, further separate Mailbox and MailboxTree darcs-hash:20070709020036-5007d-81ef9e54ba47e2c02ca91668e966930fcfe1eefb.gz --- src/org/ibex/mail/Acl.java | 88 +++++++++++++++++++++++++++++++ src/org/ibex/mail/FileBasedMailbox.java | 31 ++++++----- src/org/ibex/mail/IMAP.java | 37 ++++++++----- src/org/ibex/mail/Mailbox.java | 48 ++++++++++------- src/org/ibex/mail/MailboxTree.java | 45 ++++++++++++++-- src/org/ibex/mail/Main.java | 4 +- src/org/ibex/mail/NNTP.java | 2 +- src/org/ibex/mail/Script.java | 4 +- 8 files changed, 207 insertions(+), 52 deletions(-) create mode 100644 src/org/ibex/mail/Acl.java diff --git a/src/org/ibex/mail/Acl.java b/src/org/ibex/mail/Acl.java new file mode 100644 index 0000000..e7558d4 --- /dev/null +++ b/src/org/ibex/mail/Acl.java @@ -0,0 +1,88 @@ +// Copyright 2007 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.mail.*; +import org.ibex.js.*; +import java.io.*; +import java.net.*; +import java.util.*; +import java.text.*; + +/** an Access Control List */ +public class Acl { + + public static class PermissionDenied extends RuntimeException { + public PermissionDenied() { super(); } + } + + /** an entry on an Acl; indicates permissions for a particular principal */ + public static class Entry { + public final boolean read; + public final boolean list; + public final boolean write; + public final boolean delete; + public final boolean insert; + public final boolean lock; + public final boolean admin; + public final boolean post; + public final boolean flags; + public final boolean mkdir; + private String toString = null; + + public Entry(boolean read, + boolean list, + boolean insert, + boolean delete, + boolean write, + boolean lock, + boolean admin, + boolean post, + boolean flags, + boolean mkdir) { + this.read = read; + this.list = list; + this.write = write; + this.delete = delete; + this.insert = insert; + this.lock = lock; + this.admin = admin; + this.post = post; + this.flags = flags; + this.mkdir = mkdir; + } + + public Entry(String asString) { + this.toString = asString; + this.read = asString.indexOf('r') != -1; + this.list = asString.indexOf('l') != -1; + this.insert = asString.indexOf('i') != -1; + this.delete = asString.indexOf('d') != -1; + this.write = asString.indexOf('w') != -1; + this.lock = asString.indexOf('k') != -1; + this.admin = asString.indexOf('a') != -1; + this.post = asString.indexOf('p') != -1; + this.flags = asString.indexOf('f') != -1; + this.mkdir = asString.indexOf('m') != -1; + } + + public String toString() { + if (toString != null) return toString; + StringBuffer sb = new StringBuffer(); + if (this.read) sb.append('r'); + if (this.list) sb.append('l'); + if (this.insert) sb.append('i'); + if (this.delete) sb.append('d'); + if (this.write) sb.append('w'); + if (this.lock) sb.append('k'); + if (this.admin) sb.append('a'); + if (this.post) sb.append('p'); + if (this.flags) sb.append('f'); + if (this.mkdir) sb.append('m'); + return toString = sb.toString(); + } + } + +} diff --git a/src/org/ibex/mail/FileBasedMailbox.java b/src/org/ibex/mail/FileBasedMailbox.java index 2340a16..066781a 100644 --- a/src/org/ibex/mail/FileBasedMailbox.java +++ b/src/org/ibex/mail/FileBasedMailbox.java @@ -6,7 +6,9 @@ package org.ibex.mail; import org.prevayler.*; import org.ibex.mail.*; import org.ibex.util.*; +import org.ibex.js.*; import org.ibex.io.*; +import org.ibex.io.Fountain; import java.io.*; import java.nio.*; import java.nio.channels.*; @@ -17,26 +19,31 @@ import javax.servlet.*; import javax.servlet.http.*; /** An exceptionally crude implementation of Mailbox relying on POSIXy filesystem semantics */ -public class FileBasedMailbox extends Mailbox.Default { +public class FileBasedMailbox extends Mailbox.Default implements MailboxTree { public static final long MAGIC_DATE = 0; private static final char slash = File.separatorChar; - private static final WeakHashMap instances = new WeakHashMap(); + private static final WeakHashMap instances = new WeakHashMap(); public String toString() { return "[FileBasedMailbox " + path.getAbsolutePath() + "]"; } - public Mailbox slash(String name, boolean create) { return getFileBasedMailbox(path.getAbsolutePath()+slash+name, create); } + + public MailboxTree slash(String name, boolean create) { return getFileBasedMailbox(path.getAbsolutePath()+slash+name, create); } + + public void rmdir(String subdir) { throw new RuntimeException("FIXME not implemented"); } + public void rename(String subdir, MailboxTree newParent, String newName) { throw new RuntimeException("FIXME not implemented"); } + public Mailbox getMailbox() { return this; } + + public JS get(JS key) throws JSExn { + return (JS)slash(JSU.toString(key), true); + } // FIXME: should be a File() - public static synchronized Mailbox getFileBasedMailbox(String path, boolean create) { + public static synchronized MailboxTree getFileBasedMailbox(String path, boolean create) { try { - Mailbox ret = instances.get(path); + MailboxTree ret = instances.get(path); if (ret == null) { if (!create && !(new File(path).exists())) return null; - if (new File(new File(path)+"/subscribers").exists()) { - ret = new MailingList(new File(path), new FileBasedMailbox(new File(path))); - } else { - ret = new FileBasedMailbox(new File(path)); - } - instances.put(path, ret); + ret = new FileBasedMailbox(new File(path)); + instances.put(path, (FileBasedMailbox)ret); } return ret; } catch (Exception e) { @@ -218,7 +225,7 @@ public class FileBasedMailbox extends Mailbox.Default { if (request.getServletPath().indexOf("..") != -1) throw new IOException(".. not allowed in image paths"); ServletContext cx = getServletContext(); String path = cx.getRealPath(request.getServletPath()); - Mailbox mbox = FileBasedMailbox.getFileBasedMailbox(path, false); + Mailbox mbox = FileBasedMailbox.getFileBasedMailbox(path, false).getMailbox(); if (mbox == null) throw new IOException("no such mailbox: " + path); Vec msgs = new Vec(); diff --git a/src/org/ibex/mail/IMAP.java b/src/org/ibex/mail/IMAP.java index 4f60d0d..dc3b8f3 100644 --- a/src/org/ibex/mail/IMAP.java +++ b/src/org/ibex/mail/IMAP.java @@ -110,16 +110,23 @@ public class IMAP { public MailboxWrapper(Login auth, Client c) { this.auth=auth; this.client=c;} public void setClient(IMAP.Client client) { } + private String dirname(String name) { return name.substring(0, name.lastIndexOf(sep)); } + private String basename(String name) { return name.substring(name.lastIndexOf(sep)+1); } private Mailbox mailbox(String name, boolean create) { return mailbox(name, create, true); } private Mailbox mailbox(String name, boolean create, boolean throwexn) { if (name.equalsIgnoreCase("inbox")) return inbox; + MailboxTree mt = mailboxTree(name, create, throwexn); + return mt==null ? null : mt.getMailbox(); + } + private MailboxTree mailboxTree(String name, boolean create) { return mailboxTree(name, create, true); } + private MailboxTree mailboxTree(String name, boolean create, boolean throwexn) { MailboxTree m = root; for(StringTokenizer st = new StringTokenizer(name, sep + ""); st.hasMoreTokens();) if ((m = m.slash(st.nextToken(), create)) == null) { if (throwexn) throw new Server.No("no such mailbox " + name); return null; } - return m.getMailbox(); + return m; } // FEATURE: not accurate when a wildcard and subsequent non-wildcards both match a single component @@ -133,13 +140,13 @@ public class IMAP { if (ref.length() == 0) { client.list(sep, start, lsub, false); return; } while (start.endsWith(""+sep)) start = start.substring(0, start.length() - 1); if (ref.endsWith("%")) ref = ref + sep; - String[] children = (start.length() == 0 ? root : mailbox(start, false)).children(); + String[] children = (start.length() == 0 ? root : mailboxTree(start, false)).children(); for(int i=0; i 0 ? sep+"" : "") + s; if (mailbox(kid, false) == null) continue; - Mailbox phant = mailbox(kid, false, false); + MailboxTree phant = mailboxTree(kid, false, false); if (phant != null) { - boolean phantom = phant.phantom(); + boolean phantom = phant.getMailbox()==null; while(true) { if (pre.length() == 0) { if (s.length() == 0) client.list(sep, kid, lsub, phantom); @@ -174,8 +181,15 @@ public class IMAP { for(Mailbox.Iterator it=selected().iterator(q);it.next();) to.insert(it.cur(), it.getFlags() | Mailbox.Flag.RECENT); } public void unselect() { selected = null; } - public void delete(String m0) { delete(mailbox(m0,false)); } - public void delete(Mailbox m) { if (!m.equals(inbox)) m.destroy(false); else throw new Bad("can't delete inbox"); } + + public void delete(String m0) { mailboxTree(dirname(m0),false).rmdir(basename(m0)); } + public void rename(String from0, String to) { + Mailbox from = mailbox(from0, false); + if (from.equals(inbox)) { from.copy(Query.all(), mailbox(to, true)); } + else if (to.equalsIgnoreCase("inbox")) { from.copy(Query.all(), mailbox(to, true)); delete(from0); } + else mailboxTree(dirname(from0), false).rename(dirname(from0), mailboxTree(dirname(to), false), basename(to)); + } + public void create(String m) { mailbox(m, true, false); } public void append(String m,int f,Date a,String b) { try { // FIXME: more efficient streaming here? @@ -229,12 +243,6 @@ public class IMAP { } } - public void rename(String from0, String to) { - Mailbox from = mailbox(from0, false); - if (from.equals(inbox)) { from.copy(Query.all(), mailbox(to, true)); } - else if (to.equalsIgnoreCase("inbox")) { from.copy(Query.all(), mailbox(to, true)); from.destroy(false); } - else from.rename(to); - } public void fetch(Query q, int spec, String[] headers, int start, int end, boolean uid) { for(Mailbox.Iterator it = selected().iterator(q); it.next(); ) { Message message = ((spec & (BODYSTRUCTURE | ENVELOPE | INTERNALDATE | FIELDS | FIELDSNOT | RFC822 | @@ -276,8 +284,9 @@ public class IMAP { } else { Account account = (Account)ret; ((MailboxWrapper)api).root = root = account.getMailbox(IMAP.class); - ((MailboxWrapper)api).inbox = inbox = root.slash("INBOX", false).getMailbox(); - if (inbox == null) ((MailboxWrapper)api).inbox = inbox = root.getMailbox(); + MailboxTree ibt = root.slash("INBOX", false); + Mailbox ib = ibt==null ? null : ibt.getMailbox(); + ((MailboxWrapper)api).inbox = inbox = ib; } } diff --git a/src/org/ibex/mail/Mailbox.java b/src/org/ibex/mail/Mailbox.java index a0dc665..ec0612f 100644 --- a/src/org/ibex/mail/Mailbox.java +++ b/src/org/ibex/mail/Mailbox.java @@ -13,11 +13,7 @@ import java.util.*; import java.text.*; /** abstract superclass for mailboxes, which store messages along with their flags */ -public abstract class Mailbox extends MailboxTree implements Target { - - public JS get(JS key) throws JSExn { - return slash(JSU.toString(key), true); - } +public abstract class Mailbox extends JS.Obj implements Target { public static final String STORAGE_ROOT = System.getProperty("ibex.mail.root", File.separatorChar + "var" + File.separatorChar + "org.ibex.mail"); @@ -25,8 +21,6 @@ public abstract class Mailbox extends MailboxTree implements Target { // Required Methods ////////////////////////////////////////////////////////////////////////////// - /** phantom mailboxes merely contain others; like the alt.binaries in alt.binaries.warez */ - public abstract boolean phantom(); public abstract Mailbox.Iterator iterator(Query q); public abstract void insert(Message message, int flags); @@ -35,9 +29,6 @@ public abstract class Mailbox extends MailboxTree implements Target { public abstract void copy(Query q, Mailbox dest); public abstract int count(Query q); public abstract int uidNext(); - public abstract void rename(String newName); /* FIXME: IMAP semantics require creating parent dirs */ - public abstract void destroy(boolean recursive); - public Mailbox getMailbox() { return this; } // Thunks //////////////////////////////////////////////////////////////////////////// @@ -53,7 +44,6 @@ public abstract class Mailbox extends MailboxTree implements Target { /** default, inefficient implementation of Mailbox; only requires a few methods to be implemented */ public static abstract class Default extends Mailbox { public Mailbox.Iterator iterator(Query q) { return new Mailbox.Iterator.QueryIterator(q, this); } - public boolean phantom() { return false; } public void copy(Query q, Mailbox dest) { for(Mailbox.Iterator it = iterator(q); it.next();) dest.insert(it.cur(), it.getFlags()); } public int count(Query q) { int count = 0; for(Mailbox.Iterator it = iterator(q); it.next();) count++; return count; } public void rename(String newName) { throw new MailException("not supported"); } @@ -118,6 +108,20 @@ public abstract class Mailbox extends MailboxTree implements Target { public void setFlags(int flags) { it.setFlags(flags); } } + public static class AclWrapper extends Wrapper { + private Acl.Entry acl; + public AclWrapper(Iterator it, Acl.Entry acl) { super(it); this.acl = acl; } + public Message cur() { if (acl.read && acl.list) return super.cur(); else throw new Acl.PermissionDenied(); } + public Headers head() { if (acl.read && acl.list) return super.head(); else throw new Acl.PermissionDenied(); } + public boolean next() { if (acl.list) return super.next(); else throw new Acl.PermissionDenied(); } + public int uid() { if (acl.list) return super.uid(); else throw new Acl.PermissionDenied(); } + public int nntpNumber() { if (acl.list) return super.nntpNumber(); else throw new Acl.PermissionDenied(); } + public int imapNumber() { if (acl.list) return super.imapNumber(); else throw new Acl.PermissionDenied(); } + public void delete() { if (acl.delete) super.delete(); else throw new Acl.PermissionDenied(); } + public int getFlags() { if (acl.list) return super.getFlags(); else throw new Acl.PermissionDenied(); } + public void setFlags(int flags) { if (acl.flags) super.setFlags(flags); else throw new Acl.PermissionDenied(); } + } + class QueryIterator extends Mailbox.Iterator.Wrapper { Query q; public QueryIterator(Query q, Mailbox m) { super(m.iterator()); this.q = q; } @@ -151,11 +155,10 @@ public abstract class Mailbox extends MailboxTree implements Target { } public static class MailboxWrapper extends Mailbox { - private Mailbox m; public MailboxWrapper(Mailbox m) { this.m = m; } - public boolean phantom() { return m.phantom(); } + public Mailbox.Iterator iterator() { return m.iterator(); } public Mailbox.Iterator iterator(Query q) { return m.iterator(q); } public void insert(Message message, int flags) { m.insert(message, flags); } public void post(Message message) { m.insert(message, Flag.RECENT); } @@ -163,12 +166,21 @@ public abstract class Mailbox extends MailboxTree implements Target { public void copy(Query q, Mailbox dest) { m.copy(q, dest); } public int count(Query q) { return m.count(q); } public int uidNext() { return m.uidNext(); } - public void rename(String newName) { m.rename(newName); } - public void destroy(boolean recursive) { m.destroy(recursive); } - public MailboxTree slash(String name, boolean create) { return m.slash(name, create); } - public String[] children() { return m.children(); } - public Mailbox.Iterator iterator() { return m.iterator(); } public int uidValidity() { return m.uidValidity(); } } + public static abstract class AclWrapper extends MailboxWrapper { + private Mailbox m; + private Acl.Entry acl; + public AclWrapper(Mailbox m, Acl.Entry acl) { super(m); this.acl = acl; } + public Mailbox.Iterator iterator(Query q) { if (acl.list) return new Mailbox.Iterator.AclWrapper(m.iterator(q), acl); else throw new Acl.PermissionDenied(); } + public Mailbox.Iterator iterator() { if (acl.list) return new Mailbox.Iterator.AclWrapper(m.iterator(), acl); else throw new Acl.PermissionDenied(); } + public void insert(Message message, int flags) { if (acl.insert) m.insert(message, flags); else throw new Acl.PermissionDenied(); } + public int uidValidity() { if (acl.list) return m.uidValidity(); else throw new Acl.PermissionDenied(); } + public void post(Message message) { if (acl.post) m.insert(message, Flag.RECENT); else throw new Acl.PermissionDenied(); } + public void move(Query q, Mailbox dest) { if (acl.list && acl.read && acl.delete) m.move(q, dest); else throw new Acl.PermissionDenied(); } + public void copy(Query q, Mailbox dest) { if (acl.list && acl.read) m.copy(q, dest); else throw new Acl.PermissionDenied(); } + public int count(Query q) { if (acl.list) return m.count(q); else throw new Acl.PermissionDenied(); } + public int uidNext() { if (acl.list) return m.uidNext(); else throw new Acl.PermissionDenied(); } + } } diff --git a/src/org/ibex/mail/MailboxTree.java b/src/org/ibex/mail/MailboxTree.java index 9e0bafe..4728857 100644 --- a/src/org/ibex/mail/MailboxTree.java +++ b/src/org/ibex/mail/MailboxTree.java @@ -1,4 +1,4 @@ -// Copyright 2000-2005 the Contributors, as shown in the revision logs. +// Copyright 2007 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. @@ -13,8 +13,47 @@ import java.util.*; import java.text.*; /** a node on the "mailbox tree" */ -public abstract class MailboxTree extends JS.Obj { +public interface MailboxTree { public abstract MailboxTree slash(String name, boolean create); public abstract String[] children(); - public Mailbox getMailbox() { return null; } + public abstract void rmdir(String subdir); + public abstract void rename(String subdir, MailboxTree newParent, String newName); /* FIXME: IMAP semantics require creating parent dirs */ + public abstract Mailbox getMailbox(); + + public static class Wrapper implements MailboxTree { + protected MailboxTree mt; + public Wrapper(MailboxTree mt) { this.mt = mt; } + public MailboxTree slash(String name, boolean create) { return mt.slash(name, create); } + public String[] children() { return mt.children(); } + public void rmdir(String subdir) { mt.rmdir(subdir); } + public void rename(String subdir, MailboxTree newParent, String newName) { mt.rename(subdir, newParent, newName); } + public Mailbox getMailbox() { return mt.getMailbox(); } + } + + public static class AclWrapper extends Wrapper { + protected Acl.Entry acl; + public AclWrapper(MailboxTree mt, Acl.Entry acl) { super(mt); this.acl = acl; } + public Mailbox getMailbox() { + return new Mailbox.AclWrapper(mt.getMailbox(), acl) { + public MailboxTree slash(String name, boolean create) { + return MailboxTree.AclWrapper.this.slash(name, create); + } + }; + } + public MailboxTree slash(String name, boolean create) { + if (!acl.mkdir) create = false; + if (!acl.list) return null; + return super.slash(name, create); + } + public void rmdir(String subdir) { + if (!acl.delete) throw new Acl.PermissionDenied(); + super.rmdir(subdir); + } + public void rename(String subdir, MailboxTree newParent, String newName) { + // FIXME: acl-checking on parent? + if (!acl.delete) throw new Acl.PermissionDenied(); + super.rename(subdir, newParent, newName); + } + } + } diff --git a/src/org/ibex/mail/Main.java b/src/org/ibex/mail/Main.java index db9a36b..a11b9a0 100644 --- a/src/org/ibex/mail/Main.java +++ b/src/org/ibex/mail/Main.java @@ -121,10 +121,10 @@ public class Main { private KerberosAuth ka = new KerberosAuth("MEGACZ.COM", "chaitin.megacz.com"); public Account anonymous() { try { - final Mailbox root = + final MailboxTree root = FileBasedMailbox.getFileBasedMailbox(Mailbox.STORAGE_ROOT + "/list", false); if (root==null) return null; - return new Account("anonymous", null, root){ + return new Account("anonymous", null, root) { public MailboxTree getMailbox(Class protocol) { return super.getMailbox(protocol); } diff --git a/src/org/ibex/mail/NNTP.java b/src/org/ibex/mail/NNTP.java index d9f6d20..f5c9fef 100644 --- a/src/org/ibex/mail/NNTP.java +++ b/src/org/ibex/mail/NNTP.java @@ -97,7 +97,7 @@ public class NNTP { String[] s = who.children(); for(int i=0; i