/*
** Copyright 2000-2003 Double Precision, Inc.
** See COPYING for distribution information.
*/

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/types.h>
#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>
#include	<string.h>
#include	<stdlib.h>
#include	<time.h>
#if	HAVE_UNISTD_H
#include	<unistd.h>
#endif
#include	<stdio.h>
#include	<ctype.h>
#include	<errno.h>
#include	<fcntl.h>

#include	"maildirmisc.h"
#include	"maildircreate.h"
#include	"maildirsharedrc.h"

static const char rcsid[]="$Id: maildirshared.c,v 1.19 2003/01/20 13:47:20 mrsam Exp $";

/* Prerequisited for shared folder support */

#if	HAVE_READLINK
#if	HAVE_SYMLINK
#if	HAVE_DBOBJ

#define	YES_WE_CAN_DO_SHARED	1

#endif
#endif
#endif

#if	YES_WE_CAN_DO_SHARED

#include	"dbobj.h"

static void list_sharable(const char *, const char *,
	void (*)(const char *, void *),
	void *);

extern FILE *maildir_shared_fopen(const char *, const char *);
extern void maildir_shared_fparse(char *, char **, char **);

void maildir_list_sharable(const char *maildir,
	void (*func)(const char *, void *),
	void *voidp)
{
char	buf[BUFSIZ];
FILE	*fp;
char	*p;
int	pass;

	if (!maildir)	maildir=".";

	for (pass=0; pass<2; pass++)
	{
		fp=pass ? maildir_shared_fopen(maildir, "r")
			: fopen (MAILDIRSHAREDRC, "r");

		if (!fp)	continue;

		while ((p=fgets(buf, sizeof(buf), fp)) != 0)
		{
		char	*name, *dir;

			maildir_shared_fparse(p, &name, &dir);
			if (name)
				list_sharable(name, dir, func, voidp);
		}
		fclose(fp);
	}
}

static void list_sharable(const char *pfix, const char *path,
	void (*func)(const char *, void *),
	void *voidp)
{
DIR	*dirp;
struct	dirent *de;
struct	stat	stat_buf;

	dirp=opendir(path);
	while (dirp && (de=readdir(dirp)) != 0)
	{
	char	*z;

		if (de->d_name[0] != '.')	continue;
		if (strcmp(de->d_name, ".") == 0 ||
			strcmp(de->d_name, "..") == 0)	continue;

		z=malloc(strlen(path)+strlen(de->d_name)+12);
		if (!z)	continue;

		strcat(strcat(strcat(strcpy(z, path),
			"/"), de->d_name), "/cur/.");

		if (stat(z, &stat_buf))
		{
			free(z);
			continue;
		}
		free(z);
		z=malloc(strlen(pfix)+strlen(de->d_name)+1);
		if (!z)	continue;
		strcat(strcpy(z, pfix), de->d_name);
		(*func)(z, voidp);
		free(z);
	}
	if (dirp)	closedir(dirp);
}

int maildir_shared_subscribe(const char *maildir, const char *folder)
{
char	linebuf[BUFSIZ];
FILE	*fp;
char	*p;
char	*name=strchr(folder, '.');
char	*s, *n, *dir;
char	*buf, *link;
unsigned l;
int	pass;

	if (!name)
	{
		errno=EINVAL;
		return (-1);
	}

	if (!maildir)	maildir=".";
	p=maildir_shareddir(maildir, folder);	/* valid folder name? */
	if (!p)
	{
		errno=EINVAL;
		return (-1);
	}
	free(p);

	p=0;

	for (pass=0; pass<2; pass++)
	{
		fp=pass ? maildir_shared_fopen(maildir, "r")
			: fopen (MAILDIRSHAREDRC, "r");

		if (!fp)	continue;

		while ((p=fgets(linebuf, sizeof(linebuf), fp)) != 0)
		{
			maildir_shared_fparse(p, &n, &dir);

			if (!n)	continue;

			if (strlen(n) == name - folder &&
				memcmp(n, folder, name-folder) == 0)	break;
		}
		fclose(fp);

		if (p)	break;
	}

	if (p)
	{
		/*
		** We will create:
		**
		**  maildir/shared-folders/ (name-folder) /(name)
		**
		**  there we'll have subdirs cur/new/tmp  and shared link
		*/

		l=sizeof("/" SHAREDSUBDIR "//shared") +
			strlen(maildir) + strlen(folder);
		buf=malloc(l);
		if (!buf)	return (-1);
		strcat(strcpy(buf, maildir), "/" SHAREDSUBDIR);
		mkdir(buf, 0700);
		strcat(buf, "/");
		strncat(buf, folder, name-folder);
		mkdir(buf, 0700);
		strcat(buf, "/");
		strcat(buf, name+1);
		if ( mkdir(buf, 0700))	return (-1);
		s=buf+strlen(buf);
		*s++='/';
		strcpy(s, "tmp");
		if ( mkdir(buf, 0700))
		{
			s[-1]=0;
			rmdir(buf);
			free(buf);
			return (-1);
		}
		strcpy(s, "cur");
		if ( mkdir(buf, 0700))
		{
			strcpy(s, "tmp");
			rmdir(buf);
			s[-1]=0;
			rmdir(buf);
			free(buf);
			return (-1);
		}
		strcpy(s, "new");
		if ( mkdir(buf, 0700))
		{
			strcpy(s, "cur");
			rmdir(buf);
			strcpy(s, "tmp");
			rmdir(buf);
			s[-1]=0;
			rmdir(buf);
			free(buf);
			return (-1);
		}

		strcpy(s, "shared");
		if ((link=malloc(strlen(dir)+strlen(name)+2)) == 0 ||
			symlink( strcat(strcat(strcpy(link, dir), "/"), name),
				buf))
		{
			if (link)	free(link);
			strcpy(s, "new");
			rmdir(buf);
			strcpy(s, "cur");
			rmdir(buf);
			strcpy(s, "tmp");
			rmdir(buf);
			s[-1]=0;
			rmdir(buf);
			free(buf);
			return (-1);
		}
		free(link);
		free(buf);
		return (0);
	}
	errno=ENOENT;
	return (-1);
}

void maildir_list_shared(const char *maildir,
	void (*func)(const char *, void *),
	void *voidp)
{
char	*sh;
DIR	*dirp;
struct	dirent *de;

	if (!maildir)	maildir=".";
	sh=malloc(strlen(maildir)+sizeof("/" SHAREDSUBDIR));
	if (!sh)	return;

	strcat(strcpy(sh, maildir), "/" SHAREDSUBDIR);

	dirp=opendir(sh);
	while (dirp && (de=readdir(dirp)) != 0)
	{
	DIR	*dirp2;
	struct	dirent *de2;
	char	*z;

		if (de->d_name[0] == '.')	continue;

		z=malloc(strlen(sh)+strlen(de->d_name)+2);
		if (!z)	continue;
		strcat(strcat(strcpy(z, sh), "/"), de->d_name);
		dirp2=opendir(z);
		free(z);

		while (dirp2 && (de2=readdir(dirp2)) != 0)
		{
		char	*s;

			if (de2->d_name[0] == '.')	continue;
			s=malloc(strlen(de->d_name)+strlen(de2->d_name)+2);
			if (!s)	continue;
			strcat(strcat(strcpy(s, de->d_name), "."), de2->d_name);
			(*func)(s, voidp);
			free(s);
		}
		if (dirp2)	closedir(dirp2);
	}
	free(sh);
	if (dirp)	closedir(dirp);
}

int maildir_shared_unsubscribe(const char *maildir, const char *folder)
{
char	*s;

	s=maildir_shareddir(maildir, folder);
	if (!s)	return (-1);

	if (maildir_mddelete(s))
	{
		free(s);
		return (-1);
	}
	*strrchr(s, '/')=0;	/* Try to remove the whole folder dir */
	rmdir(s);
	free(s);
	return (0);
}

char *maildir_shareddir(const char *maildir, const char *sharedname)
{
char    *p, *q;
const char *r;
size_t  l;

	if (!maildir)   maildir=".";
        l=strlen(maildir);

	if (strchr(sharedname, '.') == 0 || *sharedname == '.' ||
		strchr(sharedname, '/'))
	{
		errno=EINVAL;
		return (0);
	}

	for (r=sharedname; *r; r++)
	{
		if (*r == '.' && (r[1] == '.' || r[1] == '\0'))
		{
			errno=EINVAL;
			return (0);
		}
	}

	p=malloc(strlen(maildir)+sizeof("/" SHAREDSUBDIR "/")+strlen(sharedname));
	if (!p)	return (0);

        *p=0;
        if (strcmp(maildir, "."))
		strcat(strcpy(p, maildir), "/");
	strcat(p, SHAREDSUBDIR "/");
	q=p+strlen(p);
	strcpy(q, sharedname);
	*strchr(q, '.')='/';
	return (p);
}

/*                    LET'S SYNC IT                  */

static void do_maildir_shared_sync(const char *, const char *);

void maildir_shared_sync(const char *dir)
{
char	*shareddir;
char	*buf;

	shareddir=malloc(strlen(dir)+sizeof("/shared"));
	if (!shareddir)
	{
		perror("malloc");
		return;
	}
	strcat(strcpy(shareddir, dir),"/shared");

	buf=maildir_getlink(shareddir);
	free(shareddir);
	if (buf)
	{
		do_maildir_shared_sync(dir, buf);
		free(buf);
	}
}

/* Step 1 - safely create a temporary database */

static int create_db(struct dbobj *obj,
	const char *dir,
	char **dbname)
{
	struct maildir_tmpcreate_info createInfo;

	maildir_tmpcreate_init(&createInfo);

	createInfo.maildir=dir;
	createInfo.uniq="sync";
	createInfo.doordie=1;

	{
		int	fd;

		fd=maildir_tmpcreate_fd(&createInfo);

		if (fd < 0)
		{
			perror(dir);
			return -1;
		}
		close(fd);

		dbobj_init(obj);
		if (dbobj_open(obj, createInfo.tmpname, "N") < 0)
		{
			perror(createInfo.tmpname);
			unlink(createInfo.tmpname);
			maildir_tmpcreate_free(&createInfo);
			return (-1);
		}
	}

	*dbname=createInfo.tmpname;
	createInfo.tmpname=NULL;
	maildir_tmpcreate_free(&createInfo);
	return (0);
}

/*
** Populate the DB by building the db with the messages in the sharable
** folder's cur.  The key is the stripped message filename, the value is
** the complete message filename.
*/

static int build_db(const char *shared, struct dbobj *obj)
{
char	*dummy=malloc(strlen(shared)+sizeof("/cur"));
DIR	*dirp;
struct	dirent *de;

	if (!dummy)
	{
		perror("malloc");
		return (-1);
	}

	strcat(strcpy(dummy, shared), "/cur");

	dirp=opendir(dummy);
	while (dirp && (de=readdir(dirp)) != 0)
	{
	char	*a, *b;
	char	*c;

		if (de->d_name[0] == '.')
			continue;
		if ((a=malloc(strlen(de->d_name)+1)) == 0)
		{
			perror("malloc");
			closedir(dirp);
			free(dummy);
			return (-1);
		}
		if ((b=malloc(strlen(de->d_name)+1)) == 0)
		{
			perror("malloc");
			closedir(dirp);
			free(dummy);
			free(a);
			return (-1);
		}
		strcpy(a, de->d_name);
		strcpy(b, de->d_name);
		c=strrchr(a, MDIRSEP[0]);
		if (c)	*c=0;

		if (dbobj_store(obj, a, strlen(a), b, strlen(b), "R"))
		{
			perror("dbobj_store");
			free(a);
			free(b);
			closedir(dirp);
			free(dummy);
			return (-1);
		}
		free(a);
		free(b);
	}
	if (dirp)	closedir(dirp);
	free(dummy);
	return (0);
}

static int update_link(const char *,
	const char *, const char *,
	const char *,
	const char *,
	size_t);

/*
**	Now, read our synced cur directory, and make sure that the soft
**	links are up to date.  Remove messages that have been deleted from
**	the sharable maildir, and make sure that the remaining links are
**	valid.
*/

static int update_cur(const char *cur, const char *shared, struct dbobj *obj)
{
DIR	*dirp;
struct	dirent *de;
char	*p;

	dirp=opendir(cur);
	while (dirp && (de=readdir(dirp)) != 0)
	{
	char	*cur_base;

	char	*cur_name_ptr;
	size_t	cur_name_len;

	char	*linked_name_buf;
	size_t	linked_name_len;
	int	n;

		if (de->d_name[0] == '.')	continue;

		/*
		** Strip the maildir flags, and look up the message in the
		** db.
		*/

		cur_base=malloc(strlen(de->d_name)+1);
		if (!cur_base)
		{
			perror("malloc");
			closedir(dirp);
			return (-1);
		}
		strcpy(cur_base, de->d_name);
		p=strrchr(cur_base, MDIRSEP[0]);
		if (p)	*p=0;

		cur_name_ptr=dbobj_fetch(obj, cur_base, strlen(cur_base),
			&cur_name_len, "");

		/* If it's there, delete the db entry. */

		if (cur_name_ptr)
			dbobj_delete(obj, cur_base, strlen(cur_base));

		/*
		** We'll either delete this soft link, or check its
		** contents, so we better build its complete pathname in
		** any case.
		*/

		free(cur_base);
		cur_base=malloc(strlen(de->d_name)+strlen(cur)+2);
		if (!cur_base)
		{
			perror("malloc");
			if (cur_name_ptr)	free(cur_name_ptr);
			closedir(dirp);
			return (-1);
		}
		strcat(strcat(strcpy(cur_base, cur), "/"), de->d_name);

		if (!cur_name_ptr)	/* Removed from sharable dir */
		{
			unlink(cur_base);
			free(cur_base);
			continue;
		}

		linked_name_len=strlen(shared)+strlen(de->d_name)+100;
			/* should be enough */

		if ((linked_name_buf=malloc(linked_name_len)) == 0)
		{
			perror("malloc");
			free(cur_base);
			free(cur_name_ptr);
			closedir(dirp);
			return (-1);
		}

		if ((n=readlink(cur_base, linked_name_buf, linked_name_len))< 0)
		{
			/* This is stupid, let's just unlink this nonsense */

			n=0;
		}

		if (n == 0 || n >= linked_name_len ||
			(linked_name_buf[n]=0,
			update_link(cur,
				cur_base, linked_name_buf, shared, cur_name_ptr,
				cur_name_len)))
		{
			unlink(cur_base);
			free(linked_name_buf);
			free(cur_base);
			free(cur_name_ptr);
			closedir(dirp);
			return (-1);
		}
		free(cur_base);
		free(linked_name_buf);
		free(cur_name_ptr);
	}
	if (dirp)	closedir(dirp);
	return (0);
}

/* Update the link pointer */

static int update_link(const char *curdir,
	const char *linkname, const char *linkvalue,
	const char *shareddir,
	const char *msgfilename,
	size_t msgfilenamelen)
{
	char	*p=malloc(strlen(shareddir)+sizeof("/cur/")+msgfilenamelen);
	char	*q;
	int	fd;
	struct maildir_tmpcreate_info createInfo;

	if (!p)
	{
		perror("malloc");
		return (-1);
	}

	strcat(strcpy(p, shareddir), "/cur/");
	q=p+strlen(p);
	memcpy(q, msgfilename, msgfilenamelen);
	q[msgfilenamelen]=0;

	if (linkvalue && strcmp(p, linkvalue) == 0)
	{
		/* the link is good */

		free(p);
		return (0);
	}

	/* Ok, we want this to be an atomic operation. */

	maildir_tmpcreate_init(&createInfo);
	createInfo.maildir=curdir;
	createInfo.uniq="relink";
	createInfo.doordie=1;

	if ((fd=maildir_tmpcreate_fd(&createInfo)) < 0)
		return -1;

	close(fd);
	unlink(createInfo.tmpname);

	if (symlink(p, createInfo.tmpname) < 0 ||
	    rename(createInfo.tmpname, linkname) < 0)
	{
		perror(createInfo.tmpname);
		maildir_tmpcreate_free(&createInfo);
		return (-1);
	}

	maildir_tmpcreate_free(&createInfo);
	return (0);
}

/* and now, anything that's left in the temporary db must be new messages */

static int newmsgs(const char *cur, const char *shared, struct dbobj *obj)
{
	char	*key, *val;
	size_t	keylen, vallen;
	int fd;
	struct maildir_tmpcreate_info createInfo;

	maildir_tmpcreate_init(&createInfo);
	createInfo.maildir=cur;
	createInfo.uniq="newlink";
	createInfo.doordie=1;

	if ((fd=maildir_tmpcreate_fd(&createInfo)) < 0)
		return -1;
	close(fd);

	unlink(createInfo.tmpname);

	for (key=dbobj_firstkey(obj, &keylen, &val, &vallen); key;
		key=dbobj_nextkey(obj, &keylen, &val, &vallen))
	{
	char	*slink=malloc(strlen(shared)+sizeof("/cur/")+vallen);
	char	*q;

		if (!slink)
		{
			free(val);
			maildir_tmpcreate_free(&createInfo);
			return (-1);
		}

		strcat(strcpy(slink, shared), "/cur/");
		q=slink+strlen(slink);
		memcpy(q, val, vallen);
		q[vallen]=0;
		free(val);

		if (symlink(slink, createInfo.tmpname))
		{
			perror(createInfo.tmpname);

			free(slink);
			maildir_tmpcreate_free(&createInfo);
			return (-1);
		}

		free(slink);
		slink=malloc(strlen(cur)+sizeof("/new/" MDIRSEP "2,")+keylen);
		if (!slink)
		{
			perror("malloc");
			maildir_tmpcreate_free(&createInfo);
			return (-1);
		}

		strcat(strcpy(slink, cur), "/new/");
		q=slink+strlen(slink);
		memcpy(q, key, keylen);
		strcpy(q+keylen, MDIRSEP "2,");

		if (rename(createInfo.tmpname, slink))
		{
			free(slink);
			maildir_tmpcreate_free(&createInfo);
			return (-1);
		}
		free(slink);
	}
	maildir_tmpcreate_free(&createInfo);
	return (0);
}

static void do_maildir_shared_sync(const char *dir, const char *shared)
{
struct	dbobj obj;
char	*dbname;
char	*cur;
char	*shared_update_name;

struct	stat	stat1, stat2;
int	fd;

	maildir_purgetmp(dir);	/* clean up after myself */
	maildir_getnew(dir, 0, NULL, NULL);

	maildir_purgetmp(shared);
	maildir_getnew(shared, 0, NULL, NULL);

	/* Figure out if we REALLY need to sync something */

	shared_update_name=malloc(strlen(dir)+sizeof("/shared-timestamp"));
	if (!shared_update_name)	return;
	strcat(strcpy(shared_update_name, dir), "/shared-timestamp");
	cur=malloc(strlen(shared)+sizeof("/new"));
	if (!cur)
	{
		free(shared_update_name);
		return;
	}

	if (stat(shared_update_name, &stat1) == 0)
	{
		if ( stat( strcat(strcpy(cur, shared), "/new"), &stat2) == 0 &&
			stat2.st_mtime < stat1.st_mtime &&
			stat( strcat(strcpy(cur, shared), "/cur"), &stat2)
			== 0 && stat2.st_mtime < stat1.st_mtime)
		{
			free(shared_update_name);
			free(cur);
			return;
		}
	}
	if ((fd=maildir_safeopen(shared_update_name, O_RDWR|O_CREAT, 0600))>= 0)
	{
		write(fd, "", 1);
		close(fd);
	}

	if (create_db(&obj, dir, &dbname))	return;

	if (build_db(shared, &obj))
	{
		dbobj_close(&obj);
		unlink(dbname);
		free(dbname);
		return;
	}

	if ((cur=malloc(strlen(dir)+sizeof("/cur"))) == 0)
	{
		perror("malloc");
		dbobj_close(&obj);
		unlink(dbname);
		free(dbname);
		return;
	}
	strcat(strcpy(cur, dir), "/cur");
	if (update_cur(cur, shared, &obj) == 0)
	{
		strcat(strcpy(cur, dir), "/new");
		if (update_cur(cur, shared, &obj) == 0)
		{
			*strrchr(cur, '/')=0;	/* Chop off the /new */
			newmsgs(cur, shared, &obj);
		}
	}

	free(cur);
	dbobj_close(&obj);
	unlink(dbname);
	free(dbname);
}

int maildir_sharedisro(const char *maildir)
{
char	*p=malloc(strlen(maildir)+sizeof("/shared/cur"));

	if (!p)
	{
		perror("malloc");
		return (-1);
	}
	strcat(strcpy(p, maildir), "/shared/cur");

	if (access(p, W_OK) == 0)
	{
		free(p);
		return (0);
	}
	free(p);
	return (1);
}

int maildir_unlinksharedmsg(const char *filename)
{
char	*buf=maildir_getlink(filename);

	if (buf)
	{
		struct stat stat_buf;
		int rc=unlink(buf);

		/*
		** If we FAILED to unlink the real message in the real
		** sharable folder, but the message still exists, it means
		** that we do not have the permission to do so, so do not
		** purge this folder.  Instead, remove the T flag from
		** this message.
		*/

		if (rc && stat(buf, &stat_buf) == 0)
		{
			char *cpy=strdup(filename);

			if (cpy)
			{
				char *p=strrchr(cpy, MDIRSEP[0]);

				if (p && strchr(p, '/') == 0 &&
				    strncmp(p, MDIRSEP "2,", 3) == 0 &&
				    (p=strchr(p, 'T')) != 0)
				{
					while ((*p=p[1]) != 0)
						++p;
					rename(filename, cpy);
				}
				free(cpy);
			}
						
			free(buf);
			return (0);
		}
		free(buf);
	}
	unlink(filename);
	return (0);
}


#else

/* We cannot implement sharing */

void maildir_list_sharable(const char *maildir,
	void (*func)(const char *, void *),
	void *voidp)
{
}

int maildir_shared_subscribe(const char *maildir, const char *folder)
{
	errno=EINVAL;
	return (-1);
}

void maildir_list_shared(const char *maildir,
	void (*func)(const char *, void *),
	void *voidp)
{
}

int maildir_shared_unsubscribe(const char *maildir, const char *folder)
{
	errno=EINVAL;
	return (-1);
}

char *maildir_shareddir(const char *maildir, const char *sharedname)
{
	errno=EINVAL;
	return (0);
}

void maildir_shared_sync(const char *maildir)
{
}

int maildir_sharedisro(const char *maildir)
{
	return (-1);
}

int maildir_unlinksharedmsg(const char *filename)
{
	return (-1);
}
#endif
