/*
** Copyright 2004-2026 S. Varshavchik.  See COPYING for
** distribution information.
*/


/*
*/
#include	<stdio.h>
#include	<string.h>
#include	<stdlib.h>
#include	<fcntl.h>
#include	<courierauth.h>
#include	"config.h"
#include	"sqwebmail.h"
#include	"maildir.h"
#include	"cgi/cgi.h"
#include	"auth.h"
#include	"acl.h"
#include	"maildir/maildirquota.h"
#include	"maildir/maildirrequota.h"
#include	"maildir/maildirgetquota.h"
#include	"maildir/maildirmisc.h"
#include	"maildir/maildircreate.h"
#include	"maildir/maildirwatch.h"
#include	"htmllibdir.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	<sys/types.h>
#include	<sys/stat.h>
#if	HAVE_UTIME_H
#include	<utime.h>
#endif

#include	<courier-unicode.h>
#include <string>

/* ACL support stuff */

extern const char *sqwebmail_folder;
extern void output_urlencoded(const char *p);
void output_attrencoded(const char *p);
void output_scriptptrget();
extern void output_scriptptr();
extern void output_scriptptrpostinfo();

extern dev_t sqwebmail_homedir_dev;
extern ino_t sqwebmail_homedir_ino;

int verify_shared_index_file=0;

int maildir_info_suppress(const char *maildir)
{
	struct stat stat_buf;

	if (stat(maildir, &stat_buf) < 0 ||

	    (stat_buf.st_dev == sqwebmail_homedir_dev &&
	     stat_buf.st_ino == sqwebmail_homedir_ino))
		return 1;
	return 0;
}

extern "C" const char *maildir_shared_index_file()
{
	static std::string filenamep;

	if (filenamep.empty())
	{
		const char *p=getenv("SQWEBMAIL_SHAREDINDEXFILE");

		if (!p || !*p)
			p=SHAREDINDEXFILE;

		if (p && *p)
		{
			const char *q=auth_getoptionenv("sharedgroup");

			if (!q) q="";

			filenamep.reserve(strlen(p)+strlen(q));

			filenamep.append(p);
			filenamep.append(q);
		}
	}

	if (!filenamep.empty() && verify_shared_index_file)
	{
		struct stat stat_buf;

		if (stat(filenamep.c_str(), &stat_buf))
		{
			fprintf(stderr, "ERR: ");
			perror(filenamep.c_str());
		}
	}

	return filenamep.c_str();
}

static bool acl_read2(
	maildir_aclt_list *l,
	const maildir::info &info,
	std::string &owner
);

static bool acl_read(
	maildir_aclt_list *l,
	const char *folder,
	std::string &owner
)
{
	auto minfo=maildir::info_imap_find(folder, login_returnaddr());
	if (!minfo)
		return false;

	bool rc=acl_read2(l, minfo, owner);
	return rc;
}

static bool acl_read2(maildir_aclt_list *l,
	      const maildir::info &info,
	      std::string &owner)
{
	bool rc;

	if (info.mailbox_type == MAILBOXTYPE_OLDSHARED)
	{
		/* Legacy shared., punt. */

		maildir_aclt_list_init(l);
		maildir_aclt_list_add(l, "anyone",
				      ACL_LOOKUP ACL_READ
				      ACL_SEEN ACL_WRITE
				      ACL_INSERT
				      ACL_DELETEMSGS ACL_EXPUNGE, NULL);
		owner="vendor=courier.internal";
		return true;
	}

	if (!info.regular_maildir())
		return false;

	auto p=maildir::name2dir(".", info.maildir);

	if (p.empty())
		return false;

	if (std::string_view{p}.substr(0, 2) == "./")
		p.erase(0, 2);

	rc=maildir_acl_read(l, info.homedir.c_str(), p.c_str()) == 0;

	if (rc)
	{
		owner=info.owner;
	}

	return rc;
}

void acl_computeRightsOnFolder(const char *folder, char *rights)
{
	maildir_aclt_list l;
	std::string owner;

	if (!acl_read(&l, folder, owner))
	{
		*rights=0;
		return;
	}
	acl_computeRights(&l, rights, owner.c_str());
	maildir_aclt_list_destroy(&l);
}

void acl_computeRights(maildir_aclt_list *l, char *rights,
		       const char *owner)
{
	char *p, *q;

	maildir_aclt a;

	if (maildir_acl_computerights(&a, l, login_returnaddr(), owner,
				      getenv("OPTIONS")) < 0)
	{
		*rights=0;
		return;
	}

	for (p=q=rights; *p; p++)
	{
		if (strchr(maildir_aclt_ascstr(&a), *p))
			*q++ = *p;
	}
	*q=0;
	maildir_aclt_destroy(&a);
}

static void showrights(const char *buf)
{
	size_t i;
	char buf2[40];

	for (i=0; buf[i]; i++)
	{
		const char *p;

		if (i)
			printf(", ");

		sprintf(buf2, "ACL_%c", buf[i]);

		p=getarg(buf2);
		if (p && *p)
			printf("%s", p);
		else
		{
			buf2[0]=buf[i];
			buf2[1]=0;

			printf(getarg("ACL_unknown"), buf2);
		}
	}
}

static void doupdate(const maildir::info &);

void listrights()
{
	maildir_aclt_list l;
	char buf[40];
	std::string owner;

	if (*cgi("do.update") || *cgi("delentity"))
	{
		auto minfo=maildir::info_imap_find(sqwebmail_folder, login_returnaddr());

		if (minfo)
		{
			if (!minfo.homedir.empty())
			{
				struct maildirwatch *w;
				char *lock;
				int tryanyway;

				w=maildirwatch_alloc(minfo.homedir.c_str());

				if (!w)
				{
					enomem();
					return;
				}

				lock=maildir_lock(minfo.homedir.c_str(), w,
						  &tryanyway);

				if (lock == NULL)
				{
					if (!tryanyway)
					{
						printf("%s",
						       getarg("ACL_noaccess"));
						return;
					}
				}
				doupdate(minfo);
				if (lock)
				{
					unlink(lock);
					free(lock);
				}
				maildirwatch_free(w);
			}
		}
	}

	if (!acl_read(&l, sqwebmail_folder, owner))
	{
		printf("%s", getarg("ACL_cantread"));
		return;
	}
	buf[0]=0;
	strncat(buf, getarg("ACL_all"), sizeof(buf)-2);
	acl_computeRights(&l, buf, owner.c_str());
	maildir_aclt_list_destroy(&l);

	if (!maildir_acl_canlistrights(buf))
	{
		printf("%s", getarg("ACL_cantread"));
		return;
	}

	showrights(buf);
}

static void doupdate(const maildir::info &minfo)
{
	maildir_aclt_list l;
	std::string owner;
	char buf[2];

	if (!acl_read2(&l, minfo, owner))
		return;

	strcpy(buf, ACL_ADMINISTER);
	acl_computeRights(&l, buf, owner.c_str());
	if (!*buf)
	{
		maildir_aclt_list_destroy(&l);
		return;
	}

	if (*cgi("delentity"))
	{
		maildir_aclt_list_del(&l, cgi("delentity"));
	}

	if (*cgi("do.update"))
	{
		std::string entity;
		const char *p;
		char new_acl[40];

		p=cgi("entitytype");

		if (strcmp(p, "anonymous") == 0 ||
		    strcmp(p, "owner") == 0)
			entity=p;
		else if (strcmp(p, "user") == 0)
		{
			p=cgi("entity");

			if (*p)
			{
				entity.clear();
				entity.reserve(sizeof("user=")-1+strlen(p));
				entity.append("user=");
				entity.append(p);
			}
		}
		else if (strcmp(p, "group") == 0)
		{
			p=cgi("entity");

			if (*p)
			{
				entity.clear();
				entity.reserve(sizeof("group=")-1+strlen(p));
				entity.append("group=");
				entity.append(p);
			}
		}
		else
		{
			entity=cgi("entity");
		}

		if (*cgi("negate") == '-' && !entity.empty())
		{
			entity.insert(0, "-");
		}

		if (!entity.empty())
		{
			entity=unicode::iconvert::convert(
				entity,
				sqwebmail_content_charset,
				unicode::utf_8
			);
		}
		p=getarg("ACL_all");

		new_acl[0]=0;

		while (*p && strlen(new_acl) < sizeof(new_acl)-2)
		{
			char b[40];

			sprintf(b, "acl_%c", *p);

			if (*cgi(b))
			{
				b[0]=*p;
				b[1]=0;
				strcat(new_acl, b);
			}
			++p;
		}

		if (entity.empty())
			printf("%s", getarg("ACL_failed"));

		maildir_aclt_list_add(&l, entity.c_str(), new_acl, NULL);
	}

	auto pacl=maildir::name2dir(".", minfo.maildir);

	if (!pacl.empty())
	{
		const char *err_ident;

		if (std::string_view{pacl}.substr(0, 2) == "./")
			pacl.erase(0, 2);

		if (maildir_acl_write(&l, minfo.homedir.c_str(),
				      pacl.c_str(),
				      owner.c_str(), &err_ident))
			printf("%s", getarg("ACL_failed"));
	}

	maildir_aclt_list_destroy(&l);
}

static void p_ident_name(const char *identifier)
{
	std::string val=unicode::iconvert::convert(
		identifier,
		unicode::utf_8,
		sqwebmail_content_charset
	);

	output_attrencoded(val.c_str());
}

static int getacl_cb(const char *identifier, const char *acl, void *dummy)
{
	printf("<tr><td>");
	p_ident_name(identifier);
	printf("</td><td>");
	showrights(acl);

	printf("<span class=\"folder-acl-list-action\">&nbsp;(<a href=\"");
	output_scriptptrget();
	printf("&amp;form=acl&amp;editentity=");
	output_urlencoded(identifier);
	printf("&amp;editaccess=");
	output_urlencoded(acl);
	printf("\">%s</a>)&nbsp;(<a href=\"", getarg("EDIT"));
	output_scriptptrget();
	printf("&amp;form=acl&amp;delentity=");
	output_urlencoded(identifier);
	printf("\">%s</a>)</td></tr>\n", getarg("DELETE"));
	return 0;
}

void getacl()
{
	maildir_aclt_list l;
	char buf[2];
	std::string owner;
	const char *a;
	const char *editentity=cgi("editentity");
	const char *editaccess=cgi("editaccess");

	const char *entitytype="";
	const char *entityval="";
	int negate=0;

	if (!acl_read(&l, sqwebmail_folder, owner))
	{
		printf("%s", getarg("ACL_noaccess"));
		return;
	}
	strcpy(buf, ACL_ADMINISTER);
	acl_computeRights(&l, buf, owner.c_str());

	if (buf[0] == 0)
	{
		maildir_aclt_list_destroy(&l);
		return;
	}

	printf("<form method=\"post\" name=\"form1\" action=\"");
	output_scriptptr();
	printf("\">");
	output_scriptptrpostinfo();
	printf("<input type=\"hidden\" name=\"update\" value=\"1\" />\n"
	       "<input type=\"hidden\" name=\"form\" value=\"acl\" />\n");
	printf("<table class=\"folder-acl-list\"><tbody>"
	       "<tr><th align=\"left\">%s</th><th align=\"left\">%s</th></tr>\n",
	       getarg("ENTITY"),
	       getarg("ACCESSRIGHTS"));

	maildir_aclt_list_enum(&l, getacl_cb, NULL);

	if (*editentity == '-')
	{
		++editentity;
		negate=1;
	}

	if (*editentity)
	{
		if (strncmp(editentity, "user=", 5) == 0)
		{
			entitytype="user";
			entityval=editentity+5;
		}
		else if (strncmp(editentity, "group=", 6) == 0)
		{
			entitytype="group";
			entityval=editentity+6;
		}
		else if (strcmp(editentity, "owner") == 0 ||
			 strcmp(editentity, "anonymous") == 0)
		{
			entitytype=editentity;
		}
		else
		{
			entitytype="other";
			entityval=editentity;
		}
	}

	printf("<tr><td colspan=\"2\"><hr width=\"90%%\" />");
	printf("<table><tbody>\n");
	printf("<tr><th colspan=\"2\" align=\"left\">%s</th></tr>\n",
	       getarg("UPDATEHDR"));
	printf("<tr align=\"top\"><td>"
	       "<select name=\"negate\" id=\"negate\">\n"
	       "<option value=\"\" > </option>\n"
	       "<option value=\"-\" %s>-</option>\n"
	       "</select>\n"
	       "<select name=\"entitytype\" id=\"entitytype\" "
	       "onchange=\"javascript:updent()\" >\n"
	       "<option value=\"user\" %s >%s</option>\n"
	       "<option value=\"group\" %s >%s</option>\n"
	       "<option value=\"owner\" %s >%s</option>\n"
	       "<option value=\"anonymous\" %s >%s</option>\n"
	       "<option value=\"administrators\" %s >%s</option>\n"
	       "<option value=\"other\" %s >%s</option>\n"
	       "</select><input type=\"text\" name=\"entity\" "
	       " id=\"entity\" value=\"",
	       negate ? "selected=\"selected\"":"",
	       strcmp(entitytype, "user") == 0 ? "selected=\"selected\"":"",
	       getarg("USER"),

	       strcmp(entitytype, "group") == 0 ? "selected=\"selected\"":"",
	       getarg("GROUP"),

	       strcmp(entitytype, "owner") == 0 ? "selected=\"selected\"":"",
	       getarg("OWNER"),

	       strcmp(entitytype, "anonymous") == 0 ? "selected=\"selected\"":"",
	       getarg("ANONYMOUS"),

	       strcmp(entitytype, "administrators") == 0 ? "selected=\"selected\"":"",
	       getarg("ADMINISTRATORS"),

	       strcmp(entitytype, "other") == 0 ? "selected=\"selected\"":"",
	       getarg("OTHER"));

	p_ident_name(entityval);

	printf("\"/></td><td><table><tbody>");

	a=getarg("ACL_all");

	while (*a)
	{
		char buf2[40];

		sprintf(buf2, "ACL_%c", *a);

		printf("<tr><td><input type=\"checkbox\" name=\"acl_%c\" "
		       "id=\"acl_%c\" %s />"
		       "</td><td>%s</td></tr>\n",
		       *a, *a,
		       strchr(editaccess, *a) ? "checked=\"checked\"":"",
		       getarg(buf2));
		++a;
	}

	printf("</tbody></table></td></tr>\n"
	       "<tr><td>&nbsp;</td>"
	       "<td><input type=\"submit\" name=\"do.update\" value=\"%s\" />"
	       "</td>"
	       "</table></tbody></td></tr>\n",
	       getarg("UPDATE"));

	printf("</tbody></table></form>\n");
}
