summaryrefslogtreecommitdiffstats
path: root/maildir/maildiraclt.c
diff options
context:
space:
mode:
Diffstat (limited to 'maildir/maildiraclt.c')
-rw-r--r--maildir/maildiraclt.c1290
1 files changed, 1290 insertions, 0 deletions
diff --git a/maildir/maildiraclt.c b/maildir/maildiraclt.c
new file mode 100644
index 0000000..d4b0966
--- /dev/null
+++ b/maildir/maildiraclt.c
@@ -0,0 +1,1290 @@
+/*
+** Copyright 2003-2012 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+
+#include "maildiraclt.h"
+#include "maildirmisc.h"
+#include "maildircreate.h"
+#include <time.h>
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#if HAVE_DIRENT_H
+#include <dirent.h>
+#define NAMLEN(dirent) strlen((dirent)->d_name)
+#else
+#define dirent direct
+#define NAMLEN(dirent) (dirent)->d_namlen
+#if HAVE_SYS_NDIR_H
+#include <sys/ndir.h>
+#endif
+#if HAVE_SYS_DIR_H
+#include <sys/dir.h>
+#endif
+#if HAVE_NDIR_H
+#include <ndir.h>
+#endif
+#endif
+#include <string.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+
+int maildir_acl_disabled=0;
+
+static int compar_aclt(const void *a, const void *b)
+{
+ char ca=*(const char *)a;
+ char cb=*(const char *)b;
+
+ return (int)(unsigned char)ca - (int)(unsigned char)cb;
+}
+
+/* Post-op fixup of an aclt: collate, remove dupes. */
+
+static void fixup(maildir_aclt *aclt)
+{
+ char *a, *b;
+
+ qsort(*aclt, strlen(*aclt), 1, compar_aclt);
+
+ for (a=b=*aclt; *a; a++)
+ {
+ if (*a == a[1])
+ continue;
+ if ((int)(unsigned char)*a <= ' ')
+ continue; /* Silently drop bad access rights */
+
+ *b++= *a;
+ }
+ *b=0;
+}
+
+static int validacl(const char *p)
+{
+ while (*p)
+ {
+ if ((int)(unsigned char)*p <= ' ')
+ {
+ errno=EINVAL;
+ return -1;
+ }
+ ++p;
+ }
+
+ return 0;
+}
+
+int maildir_aclt_init(maildir_aclt *aclt,
+ const char *initvalue_cstr,
+ const maildir_aclt *initvalue_cpy)
+{
+ if (initvalue_cpy)
+ initvalue_cstr= *initvalue_cpy;
+
+ *aclt=NULL;
+
+ if (!initvalue_cstr || !*initvalue_cstr)
+ return 0;
+
+ if (validacl(initvalue_cstr) < 0)
+ return -1;
+
+ if ( (*aclt=strdup(initvalue_cstr)) == NULL)
+ return -1;
+ fixup(aclt);
+ return 0;
+}
+
+/* Destroy an aclt after it is no longer used. */
+
+void maildir_aclt_destroy(maildir_aclt *aclt)
+{
+ if (*aclt)
+ free(*aclt);
+}
+
+
+/* Add or remove access chars. */
+
+int maildir_aclt_add(maildir_aclt *aclt,
+ const char *add_strs,
+ const maildir_aclt *add_aclt)
+{
+ if (add_aclt)
+ add_strs= *add_aclt;
+
+ if (!add_strs || !*add_strs)
+ return 0;
+
+ if (validacl(add_strs) < 0)
+ return -1;
+
+ if (*aclt)
+ {
+ char *p=realloc(*aclt, strlen(*aclt)+strlen(add_strs)+1);
+
+ if (!p)
+ return -1;
+ strcat(p, add_strs);
+ *aclt=p;
+
+ }
+ else if ( ((*aclt)=strdup(add_strs)) == NULL)
+ return -1;
+
+ fixup(aclt);
+ return 0;
+}
+
+int maildir_aclt_del(maildir_aclt *aclt,
+ const char *del_strs,
+ const maildir_aclt *del_aclt)
+{
+ char *a, *b;
+
+ if (del_aclt)
+ del_strs= *del_aclt;
+
+ if (!del_strs)
+ return 0;
+
+ if (!*aclt)
+ return 0;
+
+ for (a=b=*aclt; *a; a++)
+ {
+ if (strchr(del_strs, *a))
+ continue;
+ *b++= *a;
+ }
+ *b=0;
+
+ if (**aclt == 0)
+ {
+ free(*aclt);
+ *aclt=NULL;
+ }
+ return 0;
+}
+
+/* -------------------------------------------------------------------- */
+
+
+void maildir_aclt_list_init(maildir_aclt_list *aclt_list)
+{
+ aclt_list->head=NULL;
+ aclt_list->tail=NULL;
+}
+
+void maildir_aclt_list_destroy(maildir_aclt_list *aclt_list)
+{
+ struct maildir_aclt_node *p;
+
+ for (p=aclt_list->head; p; )
+ {
+ struct maildir_aclt_node *q=p->next;
+
+ free(p->identifier);
+ maildir_aclt_destroy(&p->acl);
+ free(p);
+ p=q;
+ }
+ maildir_aclt_list_init(aclt_list);
+}
+
+
+/* Add an <identifier,acl> pair. Returns 0 on success, -1 on failure */
+
+int maildir_aclt_list_add(maildir_aclt_list *aclt_list,
+ const char *identifier,
+ const char *aclt_str,
+ maildir_aclt *aclt_cpy)
+{
+ struct maildir_aclt_node *p;
+ const char *q;
+
+ /* Check for valid identifiers */
+
+ for (q=identifier; *q; q++)
+ if ( (int)(unsigned char)*q <= ' ')
+ {
+ errno=EINVAL;
+ return -1;
+ }
+
+ if (*identifier == 0)
+ {
+ errno=EINVAL;
+ return -1;
+ }
+
+ if (aclt_cpy && *aclt_cpy)
+ aclt_str= *aclt_cpy;
+
+ for (p=aclt_list->head; p; p=p->next)
+ {
+ if (strcmp(p->identifier, identifier) == 0)
+ {
+ maildir_aclt_destroy(&p->acl);
+ return maildir_aclt_init(&p->acl, aclt_str, NULL);
+ }
+ }
+
+ if ((p=malloc(sizeof(*p))) == NULL ||
+ (p->identifier=strdup(identifier)) == NULL)
+ {
+ if (p) free(p);
+ return -1;
+ }
+
+ if (maildir_aclt_init(&p->acl, aclt_str, NULL) < 0)
+ {
+ free(p->identifier);
+ free(p);
+ return -1;
+ }
+
+ p->next=NULL;
+ if ((p->prev=aclt_list->tail) != NULL)
+ p->prev->next=p;
+ else
+ aclt_list->head=p;
+ aclt_list->tail=p;
+ return 0;
+}
+
+/*
+** Remove 'identifier' from the ACL list.
+*/
+
+int maildir_aclt_list_del(maildir_aclt_list *aclt_list,
+ const char *identifier)
+{
+ struct maildir_aclt_node *p;
+
+ for (p=aclt_list->head; p; p=p->next)
+ {
+ if (strcmp(p->identifier, identifier) == 0)
+ {
+ if (p->prev)
+ p->prev->next=p->next;
+ else aclt_list->head=p->next;
+
+ if (p->next)
+ p->next->prev=p->prev;
+ else aclt_list->tail=p->prev;
+
+ maildir_aclt_destroy(&p->acl);
+ free(p->identifier);
+ free(p);
+ return 0;
+ }
+ }
+ return 0;
+}
+
+/*
+** Generic enumeration.
+*/
+
+int maildir_aclt_list_enum(maildir_aclt_list *aclt_list,
+ int (*cb_func)(const char *identifier,
+ const maildir_aclt *acl,
+ void *cb_arg),
+ void *cb_arg)
+{
+ struct maildir_aclt_node *p;
+ int rc;
+
+ for (p=aclt_list->head; p; p=p->next)
+ {
+ rc= (*cb_func)(p->identifier, &p->acl, cb_arg);
+
+ if (rc)
+ return rc;
+ }
+ return 0;
+}
+
+const maildir_aclt *maildir_aclt_list_find(maildir_aclt_list *aclt_list,
+ const char *identifier)
+{
+ struct maildir_aclt_node *p;
+
+ for (p=aclt_list->head; p; p=p->next)
+ {
+ if (strcmp(p->identifier, identifier) == 0)
+ return &p->acl;
+ }
+ return NULL;
+}
+
+/* ---------------------------------------------------------------------- */
+
+static int maildir_acl_read_check(maildir_aclt_list *aclt_list,
+ const char *maildir,
+ const char *path);
+
+int maildir_acl_read(maildir_aclt_list *aclt_list,
+ const char *maildir,
+ const char *path)
+{
+ int rc=maildir_acl_read_check(aclt_list, maildir, path);
+ char *p, *q;
+
+ if (rc)
+ maildir_aclt_list_destroy(aclt_list);
+
+ if (rc <= 0)
+ return rc;
+
+ /*
+ ** If the ACL config file for this folder was not found,
+ ** check for the ACL config file for its parent folder.
+ */
+
+ if ((p=strdup(path)) == NULL)
+ return -1;
+
+ strcpy(p, path);
+
+ q=strrchr(p, '.');
+
+ if (!q)
+ {
+ free(p);
+ errno=EIO; /* Should not happen */
+ return -1;
+ }
+
+ *q=0;
+
+ rc=maildir_acl_read(aclt_list, maildir, p);
+ if (rc == 0)
+ {
+ /* Make sure to save the default acl list */
+
+ rc=maildir_acl_write(aclt_list, maildir, path, NULL, NULL);
+ if (rc >= 0) /* Ok if rc=1 */
+ rc=0;
+ if (rc)
+ maildir_aclt_list_destroy(aclt_list);
+ }
+ free(p);
+ return rc;
+}
+
+/*
+** Attempt to retrieve the ACL set for the specified folder.
+**
+** Returns -1 if error.
+** Returns 0 if the ACL was retrieved.
+** Returns 1 if the ACL configuration file does not exist.
+*/
+
+static int maildir_aclt_add_default_admin(maildir_aclt_list *aclt_list);
+
+static int maildir_acl_read_check(maildir_aclt_list *aclt_list,
+ const char *maildir,
+ const char *path)
+{
+ char *p, *q;
+ FILE *fp;
+ char buffer[BUFSIZ];
+
+ maildir_aclt_list_init(aclt_list);
+
+ if (!maildir || !*maildir)
+ maildir=".";
+ if (!path || !*path)
+ path=".";
+
+ if (strchr(path, '/') || *path != '.')
+ {
+ errno=EINVAL;
+ return -1;
+ }
+
+ if (maildir_acl_disabled)
+ {
+ if (maildir_aclt_list_add(aclt_list, "owner",
+ ACL_LOOKUP ACL_READ
+ ACL_SEEN ACL_WRITE ACL_INSERT
+ ACL_CREATE
+ ACL_DELETEFOLDER
+ ACL_DELETEMSGS ACL_EXPUNGE,
+ NULL) < 0 ||
+ maildir_aclt_add_default_admin(aclt_list))
+ {
+ maildir_aclt_list_destroy(aclt_list);
+ return -1;
+ }
+ return 0;
+ }
+
+ p=malloc(strlen(maildir)+strlen(path)+2);
+
+ if (!p)
+ return -1;
+
+ strcat(strcat(strcpy(p, maildir), "/"), path);
+
+ q=malloc(strlen(p)+sizeof("/" ACLFILE));
+ if (!q)
+ {
+ free(p);
+ return -1;
+ }
+ fp=fopen(strcat(strcpy(q, p), "/" ACLFILE), "r");
+ free(p);
+ free(q);
+
+ if (fp == NULL)
+ {
+ if (strcmp(path, ".") == 0)
+ {
+ /* INBOX ACL default */
+
+ if (maildir_aclt_list_add(aclt_list, "owner",
+ ACL_ALL, NULL) < 0 ||
+ maildir_aclt_add_default_admin(aclt_list))
+ {
+ return -1;
+ }
+ return 0;
+ }
+
+ q=malloc(strlen(maildir)+sizeof("/" ACLHIERDIR "/") +
+ strlen(path));
+ if (!q)
+ return -1;
+
+ strcat(strcat(strcpy(q, maildir), "/" ACLHIERDIR "/"),
+ path+1);
+
+ fp=fopen(q, "r");
+ free(q);
+ }
+
+ if (!fp && errno != ENOENT)
+ return -1;
+
+ if (!fp)
+ return 1;
+
+ errno=0;
+
+ while (fgets(buffer, sizeof(buffer), fp) != NULL)
+ {
+ char *p=strchr(buffer, '\n');
+
+ if (p) *p=0;
+
+ for (p=buffer; *p; p++)
+ if (*p == ' ')
+ {
+ *p=0;
+ do
+ {
+ ++p;
+ } while (*p && *p == ' ');
+ break;
+ }
+
+ if (maildir_aclt_list_add(aclt_list, buffer, p, NULL) < 0)
+ {
+ if (errno != EINVAL)
+ return -1;
+ /* Sweep crap in the ACL file under the carpet */
+ }
+ }
+ if (ferror(fp))
+ {
+ fclose(fp);
+ return -1;
+ }
+ fclose(fp);
+ if (maildir_aclt_add_default_admin(aclt_list))
+ return -1;
+ return 0;
+}
+
+/*
+** Add the default ACL permissions to the administrators group.
+**
+** Make sure that the ACL entry for "administrators" includes all
+** rights.
+**
+** Make sure that any ACL entries for "-administrators" or
+** "-group=administrators" do not have LOOKUP and ADMIN.
+*/
+
+static int maildir_aclt_add_default_admin(maildir_aclt_list *aclt_list)
+{
+ const maildir_aclt *old_acl;
+
+ static const char * const drop_acls[]={"-administrators",
+ "-group=administrators"};
+ size_t i;
+
+ if ((old_acl=maildir_aclt_list_find(aclt_list, "group=administrators"))
+ != NULL)
+ {
+ maildir_aclt new_acl;
+
+ if (maildir_aclt_init(&new_acl, ACL_ALL, NULL))
+ return -1;
+
+ if (maildir_aclt_add(&new_acl, NULL, old_acl) ||
+ maildir_aclt_list_add(aclt_list, "group=administrators",
+ NULL, &new_acl))
+ {
+ maildir_aclt_destroy(&new_acl);
+ return -1;
+ }
+ maildir_aclt_destroy(&new_acl);
+ }
+ else
+ {
+ maildir_aclt new_acl;
+
+ old_acl=maildir_aclt_list_find(aclt_list, "administrators");
+
+ if (maildir_aclt_init(&new_acl, ACL_ALL, NULL))
+ return -1;
+
+ if (maildir_aclt_add(&new_acl, NULL, old_acl) ||
+ maildir_aclt_list_add(aclt_list, "administrators",
+ NULL, &new_acl))
+ {
+ maildir_aclt_destroy(&new_acl);
+ return -1;
+ }
+ maildir_aclt_destroy(&new_acl);
+ }
+
+ for (i=0; i<2; i++)
+ {
+ const char *n=drop_acls[i];
+ if (maildir_aclt_list_del(aclt_list, n) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+int maildir_acl_delete(const char *maildir,
+ const char *path)
+{
+ char *p, *q;
+
+#if 0
+ if (strcmp(path, SHARED) == 0)
+ return 0;
+
+ if (strncmp(path, SHARED ".", sizeof(SHARED)) == 0)
+ return 0;
+#endif
+ if (!maildir || !*maildir)
+ maildir=".";
+ if (!path || !*path)
+ path=".";
+
+ if (strchr(path, '/') || *path != '.')
+ {
+ errno=EINVAL;
+ return -1;
+ }
+
+ p=malloc(strlen(maildir)+strlen(path)+2);
+
+ if (!p)
+ return -1;
+
+ strcat(strcat(strcpy(p, maildir), "/"), path);
+
+ q=malloc(strlen(p)+sizeof("/" ACLFILE));
+ if (!q)
+ {
+ free(p);
+ return -1;
+ }
+
+ unlink(strcat(strcpy(q, p), "/" ACLFILE));
+ free(p);
+ free(q);
+
+ if (strcmp(path, ".") == 0)
+ {
+ /* INBOX ACL default */
+
+ return 0;
+ }
+
+ q=malloc(strlen(maildir)+sizeof("/" ACLHIERDIR "/") +
+ strlen(path));
+ if (!q)
+ {
+ return -1;
+ }
+ strcat(strcat(strcpy(q, maildir), "/" ACLHIERDIR "/"),
+ path+1);
+
+ unlink(q);
+ free(q);
+ return 0;
+}
+
+static int save_acl(const char *identifier, const maildir_aclt *acl,
+ void *cb_arg);
+
+
+static int is_owner(const char *isme, void *void_arg)
+{
+ if (void_arg && strcmp(isme, (const char *)void_arg) == 0)
+ return 1;
+
+ return strcmp(isme, "owner") == 0;
+}
+
+static int is_admin(const char *isme, void *void_arg)
+{
+ return strcmp(isme, "administrators") == 0;
+
+ /* We don't need to check for group=administrators, see chk_admin() */
+}
+
+static int check_adminrights(maildir_aclt *list)
+{
+ if (strchr(maildir_aclt_ascstr(list), ACL_LOOKUP[0]) == NULL ||
+ strchr(maildir_aclt_ascstr(list), ACL_ADMINISTER[0]) == NULL)
+ {
+ maildir_aclt_destroy(list);
+ return -1;
+ }
+
+ maildir_aclt_destroy(list);
+ return 0;
+}
+
+static int check_allrights(maildir_aclt *list)
+{
+ const char *all=ACL_ALL;
+
+ while (*all)
+ {
+ if (strchr(maildir_aclt_ascstr(list), *all) == NULL)
+ {
+ maildir_aclt_destroy(list);
+ return -1;
+ }
+ ++all;
+ }
+
+ maildir_aclt_destroy(list);
+ return 0;
+}
+
+static int maildir_acl_compute_chkowner(maildir_aclt *aclt,
+ maildir_aclt_list *aclt_list,
+ int (*cb_func)(const char *isme,
+ void *void_arg),
+ void *void_arg,
+ int chkowner);
+
+int maildir_acl_write(maildir_aclt_list *aclt_list,
+ const char *maildir,
+ const char *path,
+
+ const char *owner,
+ const char **err_failedrights)
+{
+ int trycreate;
+ struct maildir_tmpcreate_info tci;
+ FILE *fp;
+ char *p, *q;
+ const char *dummy_string;
+ maildir_aclt chkacls;
+
+ if (!err_failedrights)
+ err_failedrights= &dummy_string;
+
+ if (!maildir || !*maildir)
+ maildir=".";
+ if (!path || !*path)
+ path=".";
+
+ if (strchr(path, '/') || *path != '.')
+ {
+ errno=EINVAL;
+ return -1;
+ }
+
+ if (strcmp(path, ".")) /* Sanity check */
+ for (dummy_string=path; *dummy_string; dummy_string++)
+ if (*dummy_string == '.' &&
+ (dummy_string[1] == '.' ||
+ dummy_string[1] == 0))
+ {
+ errno=EINVAL;
+ return -1;
+ }
+
+
+ if (maildir_acl_compute_chkowner(&chkacls, aclt_list, is_owner, NULL,
+ 0))
+ {
+ maildir_aclt_destroy(&chkacls);
+ errno=EINVAL;
+ return -1;
+ }
+
+ if (check_adminrights(&chkacls))
+ {
+ *err_failedrights="owner";
+ errno=EINVAL;
+ return -1;
+ }
+
+ if (owner)
+ {
+ if (maildir_acl_compute_chkowner(&chkacls, aclt_list, is_owner,
+ (void *)owner, 0))
+ {
+ maildir_aclt_destroy(&chkacls);
+ errno=EINVAL;
+ return -1;
+ }
+ if (check_adminrights(&chkacls))
+ {
+ *err_failedrights=owner;
+ errno=EINVAL;
+ return -1;
+ }
+ }
+
+ if (maildir_acl_compute(&chkacls, aclt_list, is_admin, NULL))
+ {
+ maildir_aclt_destroy(&chkacls);
+ errno=EINVAL;
+ return -1;
+ }
+ if (check_allrights(&chkacls))
+ {
+ errno=EINVAL;
+ return -1;
+ }
+
+ p=malloc(strlen(maildir)+strlen(path)+2);
+
+ if (!p)
+ return -1;
+
+ strcat(strcat(strcpy(p, maildir), "/"), path);
+
+ maildir_tmpcreate_init(&tci);
+
+ tci.maildir=p;
+ tci.uniq="acl";
+ tci.doordie=1;
+
+ fp=maildir_tmpcreate_fp(&tci);
+
+ trycreate=0;
+
+ if (fp)
+ {
+ q=malloc(strlen(p) + sizeof("/" ACLFILE));
+ if (!q)
+ {
+ fclose(fp);
+ unlink(tci.tmpname);
+ maildir_tmpcreate_free(&tci);
+ free(p);
+ return -1;
+ }
+ strcat(strcpy(q, p), "/" ACLFILE);
+ free(tci.newname);
+ tci.newname=q;
+ free(p);
+ }
+ else
+ {
+ free(p);
+
+ q=malloc(strlen(maildir)+sizeof("/" ACLHIERDIR "/") +
+ strlen(path));
+ if (!q)
+ {
+ maildir_tmpcreate_free(&tci);
+ return -1;
+ }
+ strcat(strcat(strcpy(q, maildir), "/" ACLHIERDIR "/"), path+1);
+
+ tci.maildir=maildir;
+ tci.uniq="acl";
+ tci.doordie=1;
+
+ fp=maildir_tmpcreate_fp(&tci);
+
+ if (!fp)
+ {
+ free(q);
+ maildir_tmpcreate_free(&tci);
+ return -1;
+ }
+ free(tci.newname);
+ tci.newname=q;
+ trycreate=1;
+ }
+
+ if (maildir_aclt_list_enum(aclt_list, save_acl, fp) < 0 ||
+ ferror(fp) || fflush(fp) < 0)
+ {
+ fclose(fp);
+ unlink(tci.tmpname);
+ maildir_tmpcreate_free(&tci);
+ return -1;
+ }
+ fclose(fp);
+
+ if (rename(tci.tmpname, tci.newname) < 0)
+ {
+ /* Perhaps ACLHIERDIR needs to be created? */
+
+ if (!trycreate)
+ {
+ unlink(tci.tmpname);
+ maildir_tmpcreate_free(&tci);
+ return -1;
+ }
+
+ p=strrchr(tci.newname, '/');
+ *p=0;
+ mkdir(tci.newname, 0755);
+ *p='/';
+
+ if (rename(tci.tmpname, tci.newname) < 0)
+ {
+ unlink(tci.tmpname);
+ maildir_tmpcreate_free(&tci);
+ return -1;
+ }
+ }
+ maildir_tmpcreate_free(&tci);
+ return 0;
+}
+
+static int save_acl(const char *identifier, const maildir_aclt *acl,
+ void *cb_arg)
+{
+ if (fprintf((FILE *)cb_arg, "%s %s\n",
+ identifier,
+ maildir_aclt_ascstr(acl)) < 0)
+ return -1;
+ return 0;
+}
+
+struct maildir_acl_resetList {
+ struct maildir_acl_resetList *next;
+ char *mbox;
+};
+
+/*
+** When a maildir is opened check for stale entries in Maildir/ACLHIERDIR.
+**
+** Maildir/ACLHIERDIR/folder.subfolder should be removed unless there exists
+** Maildir/.folder.subfolder.subsubfolder
+**
+**
+** acl_check_cb is the callback function for maildir_list, which receives
+** INBOX.folder.subfolder.subsubfolder. It goes through the link list with
+** Maildir/ACLHIERDIR's contents, and removes folder.subfolder if its found.
+**
+** After maildir_list is done, anything that's left in the list can be safely
+** removed.
+*/
+
+static void acl_check_cb(const char *mbox, void *voidarg)
+{
+ struct maildir_acl_resetList **l=
+ (struct maildir_acl_resetList **)voidarg;
+
+ if (strncmp(mbox, INBOX ".", sizeof(INBOX ".")-1))
+ return; /* Huh? */
+
+ mbox += sizeof(INBOX ".")-1;
+
+ while (*l)
+ {
+ int cl= strlen( (*l)->mbox );
+
+ if (strncmp(mbox, (*l)->mbox, cl) == 0 &&
+ mbox[cl] == '.')
+ {
+ struct maildir_acl_resetList *p= *l;
+
+ *l= p->next;
+ free(p->mbox);
+ free(p);
+ continue;
+ }
+
+ l= &(*l)->next;
+ }
+}
+
+int maildir_acl_reset(const char *maildir)
+{
+ DIR *dirp;
+ struct dirent *de;
+ char *p;
+ struct maildir_acl_resetList *rl=NULL;
+ struct maildir_acl_resetList *r;
+ time_t now;
+ struct stat stat_buf;
+
+ p=malloc(strlen(maildir) + sizeof("/" ACLHIERDIR));
+ if (!p)
+ return -1;
+
+ strcat(strcpy(p, maildir), "/" ACLHIERDIR);
+
+ dirp=opendir(p);
+
+ if (!dirp)
+ {
+ mkdir(p, 0755);
+ dirp=opendir(p);
+ }
+ free(p);
+
+ while (dirp && (de=readdir(dirp)) != NULL)
+ {
+ if (de->d_name[0] == '.')
+ continue;
+
+ if ((r=malloc(sizeof(struct maildir_acl_resetList))) == NULL
+ || (r->mbox=strdup(de->d_name)) == NULL)
+ {
+ if (r)
+ free(r);
+
+ while (rl)
+ {
+ r=rl;
+ rl=r->next;
+ free(r->mbox);
+ free(r);
+ }
+ closedir(dirp);
+ return -1;
+ }
+
+ r->next=rl;
+ rl=r;
+ }
+ if (dirp) closedir(dirp);
+
+ maildir_list(maildir, acl_check_cb, &rl);
+
+ time(&now);
+
+ while (rl)
+ {
+ r=rl;
+ rl=r->next;
+
+ p=malloc(strlen(maildir)+strlen(r->mbox) +
+ sizeof("/" ACLHIERDIR "/"));
+ if (p)
+ {
+ strcat(strcat(strcpy(p, maildir),
+ "/" ACLHIERDIR "/"), r->mbox);
+
+ /* Only unlink stale entries after 1 hour (race) */
+
+ if (stat(p, &stat_buf) == 0 &&
+ stat_buf.st_mtime < now - 60*60)
+ unlink(p);
+ free(p);
+ }
+ free(r->mbox);
+ free(r);
+ }
+ return 0;
+}
+
+/*
+** An ACL entry for "administrators" or "group=administrators" will match
+** either one.
+*/
+
+static int chk_admin(int (*cb_func)(const char *isme,
+ void *void_arg),
+ const char *identifier,
+ void *void_arg)
+{
+ if (strcmp(identifier, "administrators") == 0 ||
+ strcmp(identifier, "group=administrators") == 0)
+ {
+ int rc=(*cb_func)("administrators", void_arg);
+
+ if (rc == 0)
+ rc=(*cb_func)("group=administrators", void_arg);
+ return rc;
+ }
+
+ return (*cb_func)(identifier, void_arg);
+}
+
+#define ISIDENT(s) \
+ (MAILDIR_ACL_ANYONE(s) ? 1: chk_admin(cb_func, (s), void_arg))
+
+static int maildir_acl_compute_chkowner(maildir_aclt *aclt,
+ maildir_aclt_list *aclt_list,
+ int (*cb_func)(const char *isme,
+ void *void_arg),
+ void *void_arg,
+ int chkowner)
+{
+ struct maildir_aclt_node *p;
+ int rc;
+
+ if (maildir_aclt_init(aclt, "", NULL) < 0)
+ return -1;
+
+ for (p=aclt_list->head; p; p=p->next)
+ {
+ if (p->identifier[0] == '-')
+ continue;
+
+ rc= ISIDENT(p->identifier);
+
+ if (rc < 0)
+ {
+ maildir_aclt_destroy(aclt);
+ return rc;
+ }
+
+ if (rc == 0)
+ continue;
+
+ if (maildir_aclt_add(aclt, NULL, &p->acl) < 0)
+ {
+ maildir_aclt_destroy(aclt);
+ return rc;
+ }
+ }
+
+ for (p=aclt_list->head; p; p=p->next)
+ {
+ if (p->identifier[0] != '-')
+ continue;
+
+ rc= ISIDENT(p->identifier+1);
+
+ if (rc < 0)
+ {
+ maildir_aclt_destroy(aclt);
+ return rc;
+ }
+
+ if (rc == 0)
+ continue;
+
+ if (maildir_aclt_del(aclt, NULL, &p->acl) < 0)
+ {
+ maildir_aclt_destroy(aclt);
+ return rc;
+ }
+ }
+
+ /*
+ ** In our scheme, the owner always gets admin rights.
+ */
+
+ rc=chkowner ? (*cb_func)("owner", void_arg):0;
+
+ if (maildir_acl_disabled)
+ rc=0; /* Except when ACLs are disabled */
+
+ if (rc < 0)
+ {
+ maildir_aclt_destroy(aclt);
+ return rc;
+ }
+
+ if (rc)
+ {
+ if (maildir_aclt_add(aclt, ACL_ADMINISTER, NULL) < 0)
+ {
+ maildir_aclt_destroy(aclt);
+ return rc;
+ }
+ }
+ return 0;
+}
+
+int maildir_acl_compute(maildir_aclt *aclt, maildir_aclt_list *aclt_list,
+ int (*cb_func)(const char *isme,
+ void *void_arg), void *void_arg)
+{
+ return maildir_acl_compute_chkowner(aclt, aclt_list, cb_func, void_arg,
+ 1);
+}
+
+static int chk_array(const char *identifier, void *void_arg);
+
+int maildir_acl_compute_array(maildir_aclt *aclt,
+ maildir_aclt_list *aclt_list,
+ const char * const *identifiers)
+{
+ return maildir_acl_compute(aclt, aclt_list, chk_array,
+ (void *)identifiers);
+}
+
+static int chk_array(const char *identifier, void *void_arg)
+{
+ const char * const *p=(const char * const *)void_arg;
+ size_t i;
+
+ for (i=0; p[i]; i++)
+ if (strcmp(identifier, p[i]) == 0)
+ return 1;
+ return 0;
+}
+
+/* -------------------------------------------------------------------- */
+
+int maildir_acl_canlistrights(const char *myrights)
+{
+ return (strchr(myrights, ACL_LOOKUP[0]) ||
+ strchr(myrights, ACL_READ[0]) ||
+ strchr(myrights, ACL_INSERT[0]) ||
+ strchr(myrights, ACL_CREATE[0]) ||
+ strchr(myrights, ACL_DELETEFOLDER[0]) ||
+ strchr(myrights, ACL_EXPUNGE[0]) ||
+ strchr(myrights, ACL_ADMINISTER[0]));
+}
+
+/* --------------------------------------------------------------------- */
+
+/*
+** Compute owner ACL identifiers applicable to a mailbox that's owned by
+** 'mailbox_owner'.
+*/
+
+static int get_owner_list( int (*cb_func)(const char *, void *),
+ const char *c,
+ const char *mailbox_owner, void *arg)
+{
+ char *a;
+ int rc;
+ const char *p, *q;
+
+ a=malloc(sizeof("user=")+strlen(c));
+ if (!a)
+ return -1;
+
+ strcat(strcpy(a, "user="), c);
+
+ rc=(*cb_func)(a, arg);
+
+ if (rc == 0 && strcmp(a, mailbox_owner) == 0)
+ rc=(*cb_func)("owner", arg);
+ free(a);
+
+ if (rc)
+ return rc;
+
+ c=getenv("OPTIONS");
+
+ for (p=c; p && *p; )
+ {
+ if (*p == ',')
+ {
+ ++p;
+ continue;
+ }
+
+ q=p;
+ while (*p && *p != ',')
+ ++p;
+
+ if (strncmp(q, "group=", 6) == 0)
+ {
+ a=malloc(p-q+1);
+ if (!a)
+ return -1;
+
+ memcpy(a, q, p-q);
+ a[p-q]=0;
+ rc=(*cb_func)(a, arg);
+ free(a);
+ if (rc)
+ return rc;
+ }
+ }
+ return 0;
+}
+
+static int count_owner_list(const char *o, void *arg)
+{
+ ++*(size_t *)arg;
+
+ return 0;
+}
+
+static int save_owner_list(const char *o, void *arg)
+{
+ char ***p=(char ***)arg;
+
+ **p=strdup(o);
+
+ if (**p == NULL)
+ return -1;
+
+ ++*p;
+ return 0;
+}
+
+int maildir_acl_computerights(maildir_aclt *rights,
+ maildir_aclt_list *acl_list,
+ const char *me,
+ const char *folder_owner)
+{
+ char **owner_array;
+ size_t owner_cnt;
+ char **p;
+ int rc;
+
+ owner_cnt=1;
+
+ if (get_owner_list(count_owner_list, me, folder_owner,
+ (void *)&owner_cnt) ||
+ (owner_array=calloc(owner_cnt, sizeof(char *))) == NULL)
+ return -1;
+
+ p=owner_array;
+
+ if (get_owner_list(save_owner_list, me, folder_owner, (void *)&p))
+ {
+ for (owner_cnt=0; owner_array[owner_cnt]; ++owner_cnt)
+ free(owner_array[owner_cnt]);
+ free(owner_array);
+ return -1;
+ }
+
+ rc=maildir_acl_compute_array(rights, acl_list,
+ (const char * const *)owner_array);
+
+ for (owner_cnt=0; owner_array[owner_cnt]; ++owner_cnt)
+ free(owner_array[owner_cnt]);
+ free(owner_array);
+ return rc;
+}