/*
** Copyright 2003-2022 S. Varshavchik.
** 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>

#include	<algorithm>

int maildir_acl_disabled=0;

maildir::aclt::~aclt()=default;

maildir::aclt &maildir::aclt::operator+=(const std::string &s)
{
	std::string::operator+=(s);
	fixup();

	return *this;
}

maildir::aclt &maildir::aclt::operator-=(const std::string &s)
{
	erase(std::remove_if(begin(), end(),
			     [&]
			     (char c)
			     {
				     return s.find(c) != s.npos;
			     }), end());
	return *this;
}

void maildir::aclt::fixup()
{
	erase(std::remove_if(begin(), end(),
			     [&]
			     (unsigned char c)
			     {
				     return c < ' ';
			     }), end());
	std::sort(begin(), end());
	erase(std::unique(begin(), end()), end());
}

struct maildir_aclt_impl {
	maildir::aclt impl;
};

int maildir_aclt_init(maildir_aclt *aclt,
		      const char *initvalue_cstr,
		      const maildir_aclt *initvalue_cpy)
{
	if (initvalue_cpy)
		initvalue_cstr= (*initvalue_cpy)->impl.c_str();

	*aclt=NULL;

	if (!initvalue_cstr || !*initvalue_cstr)
		return 0;

	if (!(*aclt=new maildir_aclt_impl{initvalue_cstr}))
	{
		*aclt=NULL;
		return -1;
	}

	return 0;
}

/* Destroy an aclt after it is no longer used. */

void maildir_aclt_destroy(maildir_aclt *aclt)
{
	if (!*aclt)
		return;

	delete *aclt;
	*aclt=NULL;
}


/* Add or remove access chars. */

void maildir_aclt_add(maildir_aclt *aclt,
		      const char *add_strs,
		      const maildir_aclt *add_aclt)
{
	if (add_aclt && *add_aclt)
		add_strs= (*add_aclt)->impl.c_str();

	if (!add_strs || !*add_strs)
		return;

	if (*aclt)
	{
		(*aclt)->impl += add_strs;
	}
	else
	{
		if (maildir_aclt_init(aclt, add_strs, NULL))
			abort();
	}
}

void maildir_aclt_del(maildir_aclt *aclt,
		      const char *del_strs,
		      const maildir_aclt *del_aclt)
{
	if (del_aclt && *del_aclt)
		del_strs= (*del_aclt)->impl.c_str();

	if (!del_strs)
		return;

	if (!*aclt)
		return;

	(*aclt)->impl -= del_strs;

	if ((*aclt)->impl.empty())
	{
		maildir_aclt_destroy(aclt);
	}
}

const char *maildir_aclt_ascstr(const maildir_aclt *aclt)
{
	if (!aclt || !*aclt)
		return "";

	return (*aclt)->impl.c_str();
}

/* -------------------------------------------------------------------- */

maildir::aclt_list::iterator maildir::aclt_list::lookup(
	const std::string &identifier
)
{
	return std::find_if(begin(), end(),
			    [&]
			    (const aclt_node &n)
			    {
				    return n.identifier == identifier;
			    });
}

void maildir::aclt_list::add(const std::string &identifier, const aclt &acl)
{
	if (std::find_if(identifier.begin(),
			 identifier.end(),
			 []
			 (unsigned char c)
			 {
				 return c < ' ';
			 }) != identifier.end())
		return;

	auto iter=std::find_if(begin(), end(),
			       [&]
			       (aclt_node &n)
			       {
				       return n.identifier == identifier;
			       });

	if (iter != end())
	{
		iter->acl=acl;
		return;
	}

	push_back({identifier, acl});
}

void maildir::aclt_list::del(const std::string &identifier)
{
	auto iter=std::find_if(begin(), end(),
			       [&]
			       (aclt_node &n)
			       {
				       return n.identifier == identifier;
			       });

	if (iter != end())
		erase(iter);
}

maildir::aclt_list::aclt_list()=default;

maildir::aclt_list::~aclt_list()=default;

struct maildir_aclt_list_impl {
	maildir::aclt_list list;
};


void maildir_aclt_list_init(maildir_aclt_list *aclt_list)
{
	*aclt_list=NULL;
}

static maildir_aclt_list_impl *get_impl(maildir_aclt_list *aclt_list)
{
	if (*aclt_list)
		return *aclt_list;

	if (!(*aclt_list=new maildir_aclt_list_impl))
		abort();

	return *aclt_list;
}

void maildir_aclt_list_destroy(maildir_aclt_list *aclt_list)
{
	if (!*aclt_list)
		return;

	delete *aclt_list;
	*aclt_list=NULL;
}


/* Add an <identifier,acl> pair.  Returns 0 on success, -1 on failure */

void maildir_aclt_list_add(maildir_aclt_list *aclt_list,
			   const char *identifier,
			   const char *aclt_str,
			   maildir_aclt *aclt_cpy)
{
	auto impl=get_impl(aclt_list);

	if (aclt_cpy)
	{
		const char *str=maildir_aclt_ascstr(aclt_cpy);

		if (*str)
			aclt_str=str;
	}

	if (!aclt_str)
		aclt_str="";

	impl->list.add(identifier, {aclt_str});
}

/*
** Remove 'identifier' from the ACL list.
*/

void maildir_aclt_list_del(maildir_aclt_list *aclt_list,
			  const char *identifier)
{
	auto impl=get_impl(aclt_list);

	impl->list.del(identifier);
}

/*
** Generic enumeration.
*/

int maildir_aclt_list_enum(maildir_aclt_list *aclt_list,
			   int (*cb_func)(const char *identifier,
					  const char *acl,
					  void *cb_arg),
			   void *cb_arg)
{
	int rc;

	if (!*aclt_list)
		return 0;

	for (auto &node:(*aclt_list)->list)
	{
		rc= (*cb_func)(node.identifier.c_str(),
			       node.acl.c_str(), cb_arg);

		if (rc)
			return rc;
	}
	return 0;
}

const char *maildir_aclt_list_lookup(maildir_aclt_list *aclt_list,
				     const char *identifier)
{
	if (!*aclt_list)
		return NULL;

	auto iter=std::find_if((*aclt_list)->list.begin(),
			       (*aclt_list)->list.end(),
			       [&]
			       (const maildir::aclt_node &n)
			       {
				       return n.identifier == identifier;
			       });

	if (iter == (*aclt_list)->list.end())
		return NULL;

	return iter->acl.c_str();
}

/*
** An ACL entry for "administrators" or "group=administrators" will match
** either one.
*/

static int chk_admin(const std::function<int (const char *)> &cb_func,
		     const char *identifier)
{
	if (strcmp(identifier, "administrators") == 0 ||
	    strcmp(identifier, "group=administrators") == 0)
	{
		int rc=cb_func("administrators");

		if (rc == 0)
			rc=cb_func("group=administrators");

		return rc;
	}

	return cb_func(identifier);
}

static int do_maildir_acl_compute_chkowner(
	maildir::aclt &aclt,
	const maildir::aclt_list &aclt_list,
	const std::function<int (const char *)> &cb_func,
	int chkowner);

static int check_adminrights(maildir::aclt &list)
{
	if (list.find(ACL_LOOKUP[0]) == list.npos ||
	    list.find(ACL_ADMINISTER[0]) == list.npos)
	{
		return -1;
	}

	return 0;
}

static bool validate_path(const std::string &path)
{
	if (path.find('/') != path.npos || path.substr(0, 1) != ".")
	{
		errno=EINVAL;
		return false;
	}

	if (path == ".")
		return true;

	char last_char=0;
	for (auto c:path)
	{
		if (c == '.' && last_char == '.')
		{
			errno=EINVAL;
			return false;
		}

		last_char=c;
	}

	if (last_char == '.')
	{
		errno=EINVAL;
		return false;
	}
	return true;
}

static int do_maildir_acl_write(const 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;
	const char *dummy_string;

	if (!err_failedrights)
		err_failedrights= &dummy_string;

	*err_failedrights=NULL;
	if (!maildir || !*maildir)
		maildir=".";
	if (!path || !*path)
		path=".";

	if (!validate_path(path))
	{
		return -1;
	}

	// Sanity check: compute ACLs for "owner", and verify that "owner"
	// had admin rights.

	maildir::aclt chkacls{""};

	if (do_maildir_acl_compute_chkowner(
		    chkacls, aclt_list,
		    []
		    (const char *identifier)
		    {
			    return strcmp(identifier, "owner") == 0 ? 1:0;
		    },
		    0))
	{
		errno=EINVAL;
		return -1;
	}

	if (check_adminrights(chkacls))
	{
		*err_failedrights="owner";
		errno=EINVAL;
		return -1;
	}

	// Sanity check: if an owner identifier gets passed in, we also
	// compute the ACLs for the specified owner and "owner".

	if (owner && *owner)
	{
		if (do_maildir_acl_compute_chkowner(
			    chkacls, aclt_list,
			    [owner]
			    (const char *identifier)
			    {
				    return strcmp(identifier, "owner") == 0
					    || strcmp(identifier, owner) == 0
					    ? 1:0;
			    },
			    0))
		{
			errno=EINVAL;
			return -1;
		}
		if (check_adminrights(chkacls))
		{
			*err_failedrights=owner;
			errno=EINVAL;
			return -1;
		}
	}


	/* We don't need to check for group=administrators, see chk_admin() */

	if (aclt_list.compute(
		    chkacls,
		    []
		    (const char *identifier)
		    {
			    return strcmp(identifier, "administrators") == 0;
		    }))
	{
		errno=EINVAL;
		return -1;
	}

	// Administrators should have all ACLs.

	const char *all=ACL_ALL;

	while (*all)
	{
		if (chkacls.find(*all) == chkacls.npos)
		{
			errno=EINVAL;
			return -1;
		}
		++all;
	}

	std::string p;

	p.reserve(strlen(maildir) + strlen(path)+sizeof(ACLHIERDIR)+10);

	p=maildir;
	p += "/";
	p += path;

	maildir_tmpcreate_init(&tci);

	tci.maildir=p.c_str();
	tci.uniq="acl";
	tci.doordie=1;

	fp=maildir_tmpcreate_fp(&tci);

	std::string newname, tmpname;

	if (tci.newname)
		newname=tci.newname;

	if (tci.tmpname)
		tmpname=tci.tmpname;

	maildir_tmpcreate_free(&tci);

	trycreate=0;

	if (fp)
	{
		newname=p;
		newname += "/" ACLFILE;
	}
	else
	{
		// This maildir does not exist, but inferior maildirs may
		// exist, in this case we record the maildir in the
		// ACLHIERDIR.

		newname=maildir;
		newname +=  "/" ACLHIERDIR "/";
		newname += path+1;

		tci.maildir=maildir;
		tci.uniq="acl";
		tci.doordie=1;

		fp=maildir_tmpcreate_fp(&tci);

		tmpname=tci.tmpname;
		maildir_tmpcreate_free(&tci);
		trycreate=1;
	}

	for (const auto &node:aclt_list)
	{
		fprintf(fp, "%s %s\n", node.identifier.c_str(),
			node.acl.c_str());
	}

	if (ferror(fp) || fflush(fp) < 0)
	{
		fclose(fp);
		unlink(tmpname.c_str());
		return -1;
	}
	fclose(fp);

	if (rename(tmpname.c_str(), newname.c_str()) < 0)
	{
		/* Perhaps ACLHIERDIR needs to be created? */

		if (!trycreate)
		{
			unlink(tmpname.c_str());
			return -1;
		}

		size_t n=newname.rfind('/');

		mkdir(newname.substr(0, n).c_str(), 0755);

		if (rename(tmpname.c_str(), newname.c_str()) < 0)
		{
			unlink(tmpname.c_str());
			return -1;
		}
	}
	return 0;
}

int maildir_acl_write(maildir_aclt_list *aclt_list,
		      const char *maildir,
		      const char *path,

		      const char *owner,
		      const char **err_failedrights)
{
	if (!*aclt_list)
	{
		maildir_aclt_list_impl default_list;
		maildir_aclt_list default_list_ptr= &default_list;

		return maildir_acl_write(&default_list_ptr,
					 maildir,
					 path,
					 owner,
					 err_failedrights);
	}

	return do_maildir_acl_write((*aclt_list)->list,
		maildir,
		path,
		owner,
		err_failedrights);
}

int maildir::aclt_list::write(const std::string &maildir,
			      const std::string &path,
			      const std::string &owner) const
{
	std::string ignore;

	return write(maildir, path, owner, ignore);
}

int maildir::aclt_list::write(const std::string &maildir,
			      const std::string &path,
			      const std::string &owner,
			      std::string &failed_rights) const
{
	const char *err_failedrights;

	int rc=do_maildir_acl_write(*this, maildir.c_str(), path.c_str(),
				    owner.c_str(),
				    &err_failedrights);

	failed_rights.clear();
	if (err_failedrights)
		failed_rights=err_failedrights;

	return rc;
}

int maildir::aclt_list::compute(
	maildir::aclt &ret,
	const std::function<int (const char *)> &cb
) const
{
	ret={""};

	int rc=do_maildir_acl_compute_chkowner(ret, *this, cb, 1);

	if (rc)
		ret={""};

	return rc;
}

int maildir_acl_compute(maildir_aclt *aclt, maildir_aclt_list *aclt_list,
			int (*cb_func)(const char *isme,
				       void *void_arg), void *void_arg)
{
	if (!*aclt_list)
	{
		maildir_aclt_list_impl default_list;
		maildir_aclt_list default_list_ptr= &default_list;

		return maildir_acl_compute(aclt, &default_list_ptr, cb_func,
					   void_arg);
	}

	if (!(*aclt=new maildir_aclt_impl{""}))
		return -1;

	maildir::aclt_list default_list;

	int rc=(*aclt_list)->list.compute(
		(*aclt)->impl,
		[cb_func, void_arg]
		(const char *identifier)
		{
			return cb_func(identifier, void_arg);
		});

	if (rc)
	{
		delete *aclt;
		*aclt=NULL;
	}

	return rc;
}

#define ISIDENT(s)	\
	(MAILDIR_ACL_ANYONE(s) ? 1: chk_admin(cb_func, (s)))

static int do_maildir_acl_compute_chkowner(
	maildir::aclt &aclt,
	const maildir::aclt_list &aclt_list,
	const std::function<int (const char *)> &cb_func,
	int chkowner)
{
	for (const auto &node:aclt_list)
	{
		auto identifier=node.identifier.c_str();

		if (*identifier == '-')
			continue;

		int rc=ISIDENT(identifier);

		if (rc < 0)
			return rc;

		if (rc > 0)
			aclt += node.acl;
	}

	for (const auto &node:aclt_list)
	{
		auto identifier=node.identifier.c_str();

		if (*identifier != '-')
			continue;

		++identifier;

		int rc=ISIDENT(identifier);

		if (rc < 0)
			return rc;

		if (rc > 0)
			aclt -= node.acl;
	}

	/*
	** In our scheme, the owner always gets admin rights.
	*/

	int rc=chkowner ? cb_func("owner"):0;

	if (maildir_acl_disabled)
		rc=0;	/* Except when ACLs are disabled */

	if (rc < 0)
	{
		return rc;
	}

	if (rc)
	{
		aclt += ACL_ADMINISTER;
	}
	return 0;
}

/* ---------------------------------------------------------------------- */

static int maildir_acl_read_check(maildir::aclt_list &aclt_list,
				  std::string maildir,
				  std::string path);

int maildir_acl_read(maildir_aclt_list *aclt_list,
		     const char *maildir,
		     const char *path)
{
	*aclt_list=NULL;

	auto impl=get_impl(aclt_list);

	if (!maildir)
		maildir="";
	if (!path)
		path="";

	int rc=impl->list.read(maildir, path);

	if (rc)
	{
		delete impl;
		*aclt_list=nullptr;
	}

	return rc;
}

int maildir::aclt_list::read(const std::string &maildir,
			     const std::string &path)
{
	int rc=maildir_acl_read_check(*this, maildir, path);

	if (rc)
		clear();

	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.
	*/

	auto n=path.rfind('.');

	if (n == path.npos)
	{
		errno=EIO; /* Should not happen */
		return -1;
	}

	rc=read(maildir, path.substr(0, n));

	if (rc == 0)
	{
		/* Make sure to save the default acl list */

		rc=write(maildir, path, "");
		if (rc >= 0) /* Ok if rc=1 */
			rc=0;
		if (rc)
			clear();
	}
	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 add_default_admin(maildir::aclt_list &aclt_list);

static int maildir_acl_read_check(maildir::aclt_list &aclt_list,
				  std::string maildir,
				  std::string path)
{
	FILE *fp;
	char buffer[BUFSIZ];

	aclt_list.clear();

	if (maildir.empty())
		maildir=".";
	if (path.empty())
		path=".";

	if (!validate_path(path))
		return -1;

	if (maildir_acl_disabled)
	{
		aclt_list.add("owner",
			      ACL_LOOKUP ACL_READ
			      ACL_SEEN ACL_WRITE ACL_INSERT
			      ACL_CREATE
			      ACL_DELETEFOLDER
			      ACL_DELETEMSGS ACL_EXPUNGE);
		return 0;
	}

	std::string p;

	p.reserve(maildir.size() + path.size() + 1);

	p=maildir;
	p += "/";
	p += path;

	std::string q;

	q.reserve(p.size()+sizeof("/" ACLFILE));

	q=p;
	q += "/" ACLFILE;

	fp=fopen(q.c_str(), "r");

	if (fp == NULL)
	{
		if (path == ".")
		{
			/* INBOX ACL default */

			aclt_list.add("owner",
				      ACL_ALL);

			add_default_admin(aclt_list);
			return 0;
		}

		std::string r;

		r.reserve(maildir.size()+path.size()+sizeof("/" ACLHIERDIR "/"));

		r=maildir;
		r += "/" ACLHIERDIR "/";
		r += path.substr(1);

		rename(r.c_str(), q.c_str());
		fp=fopen(q.c_str(), "r");
		if (!fp)
			fp=fopen(r.c_str(), "r");
	}

	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;
			}

		aclt_list.add(buffer, maildir::aclt{p});
	}
	if (ferror(fp))
	{
		fclose(fp);
		return -1;
	}
	fclose(fp);

	if (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 add_default_admin(maildir::aclt_list &aclt_list)
{
	static const char * const drop_acls[]={
		"-administrators",
		"-group=administrators"
	};

	auto iter=std::find_if(
		aclt_list.begin(), aclt_list.end(),
		[&]
		(maildir::aclt_node &n)
		{
			return n.identifier == "group=administrators";
		});

	if (iter != aclt_list.end())
	{
		maildir::aclt new_acl{ACL_ALL};

		new_acl += iter->acl; // Should already be there

		aclt_list.add("group=administrators", new_acl);
		aclt_list.del("administrators");
	}
	else
	{
		maildir::aclt new_acl{ACL_ALL};

		iter=std::find_if(
			aclt_list.begin(), aclt_list.end(),
			[&]
			(maildir::aclt_node &n)
			{
				return n.identifier == "administrators";
			});

		if (iter != aclt_list.end())
			new_acl += iter->acl;

		aclt_list.add("administrators", new_acl);
	}

	for (auto &identifier:drop_acls)
	{
		auto iter=std::find_if(
			aclt_list.begin(), aclt_list.end(),
			[&]
			(maildir::aclt_node &n)
			{
				return n.identifier == identifier;
			});

		if (iter != aclt_list.end())
		{
			iter->acl -= ACL_LOOKUP ACL_ADMINISTER;
			if (iter->acl.empty())
				aclt_list.erase(iter);
		}
	}

	return 0;
}

int maildir_acl_compute_array(maildir_aclt *aclt,
			      maildir_aclt_list *aclt_list,
			      const char * const *identifiers)
{
	if (!*aclt_list)
	{
		maildir_aclt_list_impl default_list;
		maildir_aclt_list default_list_ptr= &default_list;

		return maildir_acl_compute_array(aclt, &default_list_ptr,
						 identifiers);
	}

	if (!(*aclt=new maildir_aclt_impl{""}))
		return -1;

	maildir::aclt_list default_list;

	int rc= (*aclt_list)->list.compute(
		(*aclt)->impl,
		[&]
		(const char *identifier)
		{
			for (size_t i=0; identifiers[i]; ++i)
			{
				if (strcmp(identifier, identifiers[i]) == 0)
					return 1;
			}
			return 0;
		});
	if (rc)
	{
		delete *aclt;
		*aclt=nullptr;
	}
	return 0;
}

/* --------------------------------------------------------------------- */

/*
** Compute owner ACL identifiers applicable to a mailbox that's owned by
** 'mailbox_owner'.
*/

int maildir_acl_computerights(maildir_aclt *aclt,
			      maildir_aclt_list *aclt_list,
			      const char *me,
			      const char *folder_owner,
			      const char *options)
{
	if (!*aclt_list)
	{
		maildir_aclt_list_impl default_list;
		maildir_aclt_list default_list_ptr= &default_list;

		return maildir_acl_computerights(aclt, &default_list_ptr,
						 me, folder_owner, options);
	}

	if (!(*aclt=new maildir_aclt_impl{""}))
		return -1;

	int rc= (*aclt_list)->list.computerights(
		(*aclt)->impl,
		me,
		folder_owner,
		options ? options:"");
	if (rc)
	{
		delete *aclt;
		*aclt=nullptr;
	}
	return rc;
}

int maildir::aclt_list::computerights(aclt &ret,
				      const std::string &me,
				      const std::string &owner,
				      const std::string &options)
{
	std::vector<std::string> owner_array;

	owner_array.reserve(2);
	std::string user;

	user.reserve(me.size()+5);

	user="user=";
	user+=me;

	if (user == owner)
		owner_array.emplace_back("owner");

	owner_array.push_back(std::move(user));

	for (auto b=options.begin(), e=options.end(); b != e; )
	{
		if (*b == ',')
		{
			++b;
			continue;
		}

		auto p=b;
		while (b != e && *b != ',')
			++b;

		if (b-p>5 && std::equal(p, p+6, "group="))
		{
			owner_array.emplace_back(p, b);
		}
	}

	return compute(ret,
		       [&]
		       (const std::string &identifier)
		       {
			       return std::find(owner_array.begin(),
						owner_array.end(),
						identifier) == owner_array.end()
				       ? 0:1;
		       });
}

/*
** 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(std::string_view mbox,
	std::vector<std::string> &aclhierdirlist)
{
	if (mbox.substr(0, sizeof(INBOX ".")-1) != INBOX ".")
		return; /* Just "INBOX" */

	mbox.remove_prefix(sizeof(INBOX ".")-1);

	auto l=mbox.size();

	aclhierdirlist.erase(
		std::remove_if(
			aclhierdirlist.begin(),
			aclhierdirlist.end(),
			[&]
			(const std::string &aclhierdir)
			{
				return aclhierdir.size() < l &&
					std::equal(aclhierdir.begin(),
						   aclhierdir.end(),
						   mbox.data()) &&
					mbox[aclhierdir.size()] == '.';
			}),
		aclhierdirlist.end());
}



void maildir_acl_reset(const char *maildir)
{
	maildir::acl_reset(maildir);
}

void maildir::acl_reset(const std::string &maildir)
{

	DIR *dirp;
	struct dirent *de;
	time_t now;
	struct stat stat_buf;
	std::vector<std::string> aclhierdirlist;

	std::string aclhierdir;

	aclhierdir.reserve(maildir.size() + sizeof("/" ACLHIERDIR)-1);

	aclhierdir=maildir;

	aclhierdir += "/" ACLHIERDIR;

	dirp=opendir(aclhierdir.c_str());

	if (!dirp)
	{
		mkdir(aclhierdir.c_str(), 0755);
		dirp=opendir(aclhierdir.c_str());
	}

	while (dirp && (de=readdir(dirp)) != NULL)
	{
		if (de->d_name[0] == '.')
			continue;

		aclhierdirlist.push_back(de->d_name);
	}
	if (dirp) closedir(dirp);

	maildir::list(maildir, [&]
		(auto &mbox)
		{
			acl_check_cb(mbox, aclhierdirlist);
		});

	time(&now);

	for (auto &mbox:aclhierdirlist)
	{
		std::string p;

		p.reserve(aclhierdir.size() + 1 + mbox.size());

		p=aclhierdir;
		p+= "/";
		p+= mbox;

		/* Only unlink stale entries after 1 hour (race) */

		if (stat(p.c_str(), &stat_buf) == 0 &&
		    stat_buf.st_mtime < now - 60*60)
			unlink(p.c_str());
	}
}
/* ---------------------------------------------------------------------- */

int maildir_acl_delete(const char *maildir,
			const char *path)
{
	return maildir::acl_delete(maildir ? maildir:"",
				   path ? path:"") ? 0:-1;
}

bool maildir::acl_delete(const std::string &maildir,
			 const std::string &path)
{
	if (maildir.empty())
		return acl_delete(".", path);

	if (path.empty())
		return acl_delete(maildir, ".");

	if (!validate_path(path))
		return false;

	std::string p;

	p.reserve(maildir.size()+path.size()+sizeof("/" ACLHIERDIR "/"
						    ACLFILE));

	p=maildir;
	p += "/";
	p += path;
	p += "/" ACLFILE;

	unlink(p.c_str());

	if (path == ".")
	{
		/* INBOX ACL default */

		return 0;
	}

	p=maildir;
	p += "/" ACLHIERDIR "/";
	p += path.substr(1);

	unlink(p.c_str());
	return true;
}

/* -------------------------------------------------------------------- */

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]));
}
