Hi,

I've been working on a patch for dovecot 1.2 from the Kolab branch
(http://hg.intevation.org/kolab/dovecot-1.2_kolab-branch/) that
implements listing of shared namespaces.  I've got something that works
in some basic way but is still missing some pieces.  See the attached
patch, which also contains some installation and configuration notes.


Implementation notes: 

One of the main problems the patch addresses is getting a list of all
users that have mailboxes the logged in user can see.  The patch uses a
dict to cache information about which users have at least one mailbox
that is visible to other users.  The dict doesn't cache which other
users, though.  The cache entry for a given user is updated whenever the
dovecot-acl-list file in the maildir root directory is updated.  This
ties the implementation to a specific acl backend to an extent, but that
shouldn't be a problem at the moment.

Another problem is that namespaces for all those users have to be
created.  The patch does that in shared-storage.c when the shared
storage is created.  At this stage of development of the patch that
works well enough, I think, but it might be better to update the
namespaces whenever a list iterator is created.

To avoid unnecessary coupling between the shared namespace code and the
ACL plugin, the shared namespace code has a hook that it calls when it
needs a list of all the users who may have mailboxes visible to the
current user.  The ACL plugin sets that hook and uses the dict to
produce that list.  This way, the ACL plugin depends on the shared
namespace code but not the other way round and all the dict handling is
in the ACL plugin.

I'm not sure the new hook is really needed.  The patch could perhaps
just as well extend the acl_next_hook_mail_storage_created and
acl_next_hook_mailbox_list_created functions to do the namespace
creation when they're called for a shared storage or mailbox list.


Problems:

All of my tests so far involved a shared namespace of the form

namespace shared {
  separator = /
  prefix = users/%%u/
  location = maildir:.../var/mail/%%u:...
  subscriptions = no
  list = yes
  hidden = no
}

Also, let's assume two users, ford and arthur with ford's "INBOX/hhgttg"
available to arthur as "users/ford/INBOX/hhgttg".  Arthur may not list
ford's INBOX, though.  In the following the current user is always
arthur.

I found the following problems:

 - LIST response includes namespaces the user doesn't really have access
   to.  E.g. if there's another user, zaphod who's made some mailbox
   available to somebody else, but not arthur, arthur still sees

   * LIST (\Noselect \HasChildren) "/" "users/zaphod"

   Not sure it's worth fixing this, though.

 - List with "%" doesn't list all intermediate mailboxes.

   On the one hand arthur sees this:

     x LIST "" "*"
     ...
     * LIST (\Noselect \HasChildren) "/" "users/ford"
     * LIST (\HasNoChildren) "/" "users/ford/INBOX/hhgttg"
     x OK List completed.

   OTOH, with "%" only this:

     x LIST "" "users/ford/%"
     x OK List completed.

   cyrus shows

     x LIST "" "users/ford/%"
     * LIST (\Noselect \HasChildren) "/" "users/ford/INBOX"
     x OK List completed.

   At least Kontact resp. KMail rely on this.

 - The dovecot-acl-list is not always rebuilt, even when it should have
   been, AFAICT.  In particular, if the file exists but is empty, it's
   never updated, even when ACL later change.  Maybe this is a bug in
   the Kolab branch.


Cheers,

   Bernhard

-- 
Bernhard Herzog  |  ++49-541-335 08 30  |  http://www.intevation.de/
Intevation GmbH, Neuer Graben 17, 49074 Osnabrück | AG Osnabrück, HR B 18998
Geschäftsführer: Frank Koormann, Bernhard Reiter, Dr. Jan-Oliver Wagner
Patch to add listing of shared namespaces to dovecot 1.2.
The patch was produced for dovecot revision 97ed7b408525.

To install, apply the patch, and regenerate the Makefile.in files with
autogen.sh and rerun configure and make.

To configure, create an entry for dict section of dovecot.conf like this:

   acl_shared_dict = sqlite:$PREFIX/etc/acl-shared-dict.conf

with the acl-shared-dict.conf containing this:

   connect = $PREFIX/var/lib/dovecot/acl-shared-ns.sqlite

   map {
     table = acl_shared_ns
     pattern = shared/acl_shared_ns/$owner
     value_field = has_visible_folders
     fields {
       owner = $owner
     }
   }


The corresponding table in the sqlite database can be created with

   CREATE TABLE acl_shared_ns (
        owner,
        has_visible_folders,
        PRIMARY KEY (owner) ON CONFLICT REPLACE
   );


In the imap section of dovecot.conf, add 

   acl_shared_dict = proxy::acl_shared_dict



diff -r 97ed7b408525 src/lib-storage/index/shared/shared-storage.c
--- a/src/lib-storage/index/shared/shared-storage.c	Tue Oct 28 10:08:33 2008 +0100
+++ b/src/lib-storage/index/shared/shared-storage.c	Tue Oct 28 16:44:46 2008 +0100
@@ -15,6 +15,10 @@ static MODULE_CONTEXT_DEFINE_INIT(shared
 static MODULE_CONTEXT_DEFINE_INIT(shared_mailbox_list_module,
 				  &mailbox_list_module_register);
 
+void
+(*hook_shared_storage_list_namespaces)(shared_storage_add_user_namespace_t,
+				       void *);
+
 static struct mail_storage *shared_alloc(void)
 {
 	struct shared_storage *storage;
@@ -27,6 +31,38 @@ static struct mail_storage *shared_alloc
 	storage->storage.storage_class = &shared_storage;
 
 	return &storage->storage;
+}
+
+static void add_namespace_for_user(void *p, const char *username)
+{
+	struct mail_storage *_storage = p;
+	struct shared_storage *storage = p;
+	struct mail_namespace *ns;
+	string_t *mailbox;
+	const char *mailboxname;
+	struct var_expand_table tab[] = {
+		{ 'u', username },
+		{ '\0', NULL }
+	};
+
+	if (strcmp(_storage->ns->user->username, username) == 0) {
+		return;
+	}
+
+	T_BEGIN {
+		mailbox = t_str_new(128);
+		var_expand(mailbox, storage->ns_prefix_pattern, tab);
+		mailboxname = str_c(mailbox);
+		shared_storage_get_namespace(_storage, &mailboxname, &ns);
+	} T_END;
+}
+
+void shared_storage_add_visible_namespaces(struct mail_storage *_storage)
+{
+	if (hook_shared_storage_list_namespaces != NULL) {
+		hook_shared_storage_list_namespaces(add_namespace_for_user,
+						    _storage);
+	}
 }
 
 static int shared_create(struct mail_storage *_storage, const char *data,
@@ -92,6 +128,8 @@ static int shared_create(struct mail_sto
 	list_set.lock_method = &_storage->lock_method;
 	mailbox_list_init(_storage->list, _storage->ns, &list_set,
 			  mail_storage_get_list_flags(_storage->flags));
+
+	shared_storage_add_visible_namespaces(_storage);
 	return 0;
 }
 
diff -r 97ed7b408525 src/lib-storage/index/shared/shared-storage.h
--- a/src/lib-storage/index/shared/shared-storage.h	Tue Oct 28 10:08:33 2008 +0100
+++ b/src/lib-storage/index/shared/shared-storage.h	Tue Oct 28 16:04:29 2008 +0100
@@ -16,10 +16,17 @@ struct shared_storage {
 	struct mail_storage *storage_class;
 };
 
+typedef void (*shared_storage_add_user_namespace_t)(void *p,
+						    const char *username);
+extern void
+(*hook_shared_storage_list_namespaces)(shared_storage_add_user_namespace_t,
+				       void *);
+
 struct mailbox_list *shared_mailbox_list_alloc(void);
 
 int shared_storage_get_namespace(struct mail_storage *storage,
 				 const char **name,
 				 struct mail_namespace **ns_r);
+void shared_storage_add_visible_namespaces(struct mail_storage *_storage);
 
 #endif
diff -r 97ed7b408525 src/plugins/acl/Makefile.am
--- a/src/plugins/acl/Makefile.am	Tue Oct 28 10:08:33 2008 +0100
+++ b/src/plugins/acl/Makefile.am	Tue Oct 28 14:44:41 2008 +0100
@@ -1,10 +1,13 @@ AM_CPPFLAGS = \
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/src/lib \
+	-I$(top_srcdir)/src/lib-dict \
 	-I$(top_srcdir)/src/lib-mail \
 	-I$(top_srcdir)/src/lib-imap \
 	-I$(top_srcdir)/src/lib-index \
 	-I$(top_srcdir)/src/imap \
-	-I$(top_srcdir)/src/lib-storage
+	-I$(top_srcdir)/src/lib-storage \
+	-I$(top_srcdir)/src/lib-storage/index/ \
+	-I$(top_srcdir)/src/lib-storage/index/shared
 
 lib01_acl_plugin_la_LDFLAGS = -module -avoid-version
 
@@ -20,6 +23,7 @@ lib01_acl_plugin_la_SOURCES = \
 	acl-mailbox.c \
 	acl-mailbox-list.c \
 	acl-plugin.c \
+	acl-shared.c \
 	acl-storage.c
 
 noinst_HEADERS = \
@@ -27,7 +31,8 @@ noinst_HEADERS = \
 	acl-api-private.h \
 	acl-backend-vfile.h \
 	acl-cache.h \
-	acl-plugin.h
+	acl-plugin.h \
+	acl-shared.h
 
 install-exec-local:
 	for d in imap lda; do \
diff -r 97ed7b408525 src/plugins/acl/acl-backend-vfile-acllist.c
--- a/src/plugins/acl/acl-backend-vfile-acllist.c	Tue Oct 28 10:08:33 2008 +0100
+++ b/src/plugins/acl/acl-backend-vfile-acllist.c	Tue Oct 28 16:37:20 2008 +0100
@@ -11,6 +11,7 @@
 #include "acl-plugin.h"
 #include "acl-cache.h"
 #include "acl-backend-vfile.h"
+#include "acl-shared.h"
 
 #include <stdio.h>
 #include <unistd.h>
@@ -262,6 +263,8 @@ int acl_backend_vfile_acllist_rebuild(st
 	if (ret == 0) {
 		backend->acllist_mtime = st.st_mtime;
 		backend->acllist_last_check = ioloop_time;
+		acl_shared_add_namespace(backend->backend.username,
+					 array_count(&backend->acllist) > 0);
 	} else {
 		acllist_clear(backend, 0);
 		if (unlink(str_c(path)) < 0 && errno != ENOENT)
diff -r 97ed7b408525 src/plugins/acl/acl-plugin.c
--- a/src/plugins/acl/acl-plugin.c	Tue Oct 28 10:08:33 2008 +0100
+++ b/src/plugins/acl/acl-plugin.c	Fri Oct 24 14:42:12 2008 +0100
@@ -428,6 +428,8 @@ void acl_plugin_init(void)
 		command_register("DELETEACL",cmd_setacl,0); //essentially equivalent,
 							    //since empty 3rd argument will
 							    //make it erase
+
+		acl_shared_init();
 	} else {
 		if (getenv("DEBUG") != NULL)
 			i_info("acl: ACL environment not set");
@@ -443,6 +445,8 @@ void acl_plugin_deinit(void)
 			acl_next_hook_mailbox_list_created;
 	}
 	if (getenv("ACL") != NULL) {
+		acl_shared_deinit();
+
 		command_unregister("GETACL");
 		command_unregister("MYRIGHTS");
 		command_unregister("SETACL");
diff -r 97ed7b408525 src/plugins/acl/acl-shared.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/acl/acl-shared.c	Tue Oct 28 17:02:11 2008 +0100
@@ -0,0 +1,122 @@
+#include "lib.h"
+#include "dict.h"
+#include "shared-storage.h"
+#include "acl-shared.h"
+
+#include <stdlib.h>
+
+static void
+(*next_hook_shared_storage_list_namespaces)(shared_storage_add_user_namespace_t,
+					    void *);
+
+
+static struct dict *acl_shared_dict;
+static bool acl_shared_debug;
+
+
+static void
+acl_shared_storage_list_namespaces_hook(shared_storage_add_user_namespace_t cb,
+					void * p)
+{
+	struct dict_iterate_context *iter;
+	const char *prefix;
+	size_t prefix_length;
+	const char *username;
+	const char *visible;
+
+	if (acl_shared_dict == NULL) {
+		if (acl_shared_debug)
+			i_info("acl_shared_storage_list_namespaces_hook:"
+			       " no acl_shared_dict");
+		return;
+	}
+
+	prefix = t_strconcat(DICT_PATH_SHARED, "acl_shared_ns/", NULL);
+	prefix_length = strlen(prefix);
+	iter = dict_iterate_init(acl_shared_dict, prefix,
+				 DICT_ITERATE_FLAG_RECURSE
+				 | DICT_ITERATE_FLAG_SORT_BY_KEY);
+	while (dict_iterate(iter, &username, &visible) > 0) {
+		if (acl_shared_debug) {
+			i_info("acl_shared_storage_list_namespaces_hook:"
+			       " found username=%s, visible=%s",
+			       username, visible);
+		}
+		if (visible[0] == '1') {
+			cb(p, username + prefix_length);
+		}
+	}
+	dict_iterate_deinit(&iter);
+
+	if (next_hook_shared_storage_list_namespaces != NULL)
+		next_hook_shared_storage_list_namespaces(cb, p);
+}
+
+void acl_shared_add_namespace(const char *username, bool visible)
+{
+	struct dict_transaction_context *dt;
+
+	if (acl_shared_dict == NULL) {
+		if (acl_shared_debug)
+			i_info("acl_shared_add_namespace: no acl_shared_dict");
+		return;
+	}
+
+	T_BEGIN {
+		dt = dict_transaction_begin(acl_shared_dict);
+		if (acl_shared_debug) {
+			i_info("acl_shared_add_namespace: setting %s to %s",
+			       t_strconcat(DICT_PATH_SHARED, "acl_shared_ns/",
+					   username, NULL),
+			       visible ? "1" : "0");
+		}
+		dict_set(dt, t_strconcat(DICT_PATH_SHARED, "acl_shared_ns/",
+					 username, NULL),
+			 visible ? "1" : "0");
+		if (dict_transaction_commit(&dt) < 0) {
+			i_error("acl_shared_add_namespace:"
+				" could not update acl_shared_ns dict");
+		}
+	} T_END;
+}
+
+void acl_shared_init(void)
+{
+	const char *dict_uri;
+	const char *username;
+
+	acl_shared_debug = getenv("ACL_SHARED_DEBUG") != NULL;
+
+	dict_uri = getenv("ACL_SHARED_DICT");
+	if (dict_uri == NULL) {
+		i_info("no acl_shared_dict specified;"
+		       " shared namespaces will not be listed");
+		return;
+	}
+
+	username = getenv("USER");
+	if (username == NULL)
+		i_fatal("acl_shared_init: USER not set");
+
+	acl_shared_dict = dict_init(dict_uri, DICT_DATA_TYPE_STRING, username);
+	if (acl_shared_dict == NULL) {
+		i_fatal("acl_shared_init: could not instantiate dict from '%s'",
+			dict_uri);
+		return;
+	}
+
+	next_hook_shared_storage_list_namespaces =
+		hook_shared_storage_list_namespaces;
+	hook_shared_storage_list_namespaces =
+		acl_shared_storage_list_namespaces_hook;
+}
+
+
+void acl_shared_deinit(void)
+{
+	if (acl_shared_dict != NULL) {
+		dict_deinit(&acl_shared_dict);
+		hook_shared_storage_list_namespaces =
+			next_hook_shared_storage_list_namespaces;
+	}
+}
diff -r 97ed7b408525 src/plugins/acl/acl-shared.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/plugins/acl/acl-shared.h	Fri Oct 24 19:31:33 2008 +0100
@@ -0,0 +1,9 @@
+#ifndef ACL_SHARED_H
+#define ACL_SHARED_H
+
+void acl_shared_add_namespace(const char *username, bool visible);
+
+void acl_shared_init(void);
+void acl_shared_deinit(void);
+
+#endif /* ACL_SHARED_H */

Attachment: signature.asc
Description: This is a digitally signed message part.

Reply via email to