/* ** Copyright 2003-2012 Double Precision, Inc. ** See COPYING for distribution information. */ #include "maildiraclt.h" #include "maildirmisc.h" #include "maildircreate.h" #include #if HAVE_UNISTD_H #include #endif #if HAVE_DIRENT_H #include #define NAMLEN(dirent) strlen((dirent)->d_name) #else #define dirent direct #define NAMLEN(dirent) (dirent)->d_namlen #if HAVE_SYS_NDIR_H #include #endif #if HAVE_SYS_DIR_H #include #endif #if HAVE_NDIR_H #include #endif #endif #include #include #include #include #include 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 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); if (fp == NULL) { char *r; if (strcmp(path, ".") == 0) { free(q); /* 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; } r=malloc(strlen(maildir)+sizeof("/" ACLHIERDIR "/") + strlen(path)); if (!r) return -1; strcat(strcat(strcpy(r, maildir), "/" ACLHIERDIR "/"), path+1); rename(r, q); fp=fopen(q, "r"); if (!fp) fp=fopen(r, "r"); free(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; }