/*
** Copyright 1998 - 2010 S. Varshavchik.
** 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>
#if	HAVE_SYS_STAT_H
#include	<sys/stat.h>
#endif

#include	"maildirquota.h"
#include	"maildirmisc.h"
#include	"maildircreate.h"
#include	"quotawarnmsg.h"
#include	"../rfc822/rfc822.h"
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<errno.h>
#include	<fcntl.h>
#include	<unistd.h>
#include	<time.h>
#include	<ctype.h>
#include	<numlib/numlib.h>


static void parsequotastr(const char *, struct maildirquota *);

#define DIG(c) ( (c) >= '0' && (c) <= '9')

/* Read the maildirsize file */

static int maildir_openquotafile_init(struct maildirsize *info,
				      const char *maildir,
				      const char *newquota);

static int do_maildir_openquotafile(struct maildirsize *info,
				    const char *filename,
				    const char *newquota);

int maildir_openquotafile(struct maildirsize *info, const char *maildir)
{
	return (maildir_openquotafile_init(info, maildir, NULL));
}

static int maildir_openquotafile_init(struct maildirsize *info,
				      const char *maildir,
				      const char *newquota)
{
	int rc;

	std::string buf;
	buf.reserve(strlen(maildir)+sizeof("/maildirfolder")-1);

	memset(info, 0, sizeof(*info));

	info->fd= -1;

	buf=maildir;
	buf += "/maildirfolder";
	if (stat(buf.c_str(), &info->statbuf) == 0)	/* Go to parent */
	{
		buf=maildir;
		buf += "/..";

		rc=maildir_openquotafile_init(info, buf.c_str(), newquota);
		return rc;
	}

	info->maildir=strdup(maildir);

	if (!info->maildir)
		return (-1);

	buf=maildir;
	buf += "/maildirsize";
	info->maildirsizefile=strdup(buf.c_str());

	rc=do_maildir_openquotafile(info, buf.c_str(), newquota);

	if (rc == 0)
		return (0);

	free(info->maildir);
	return (rc);
}

static int do_maildir_openquotafile(struct maildirsize *info,
				    const char *filename,
				    const char *newquota)
{
	char buf[5120];
	char *p;
	unsigned l;
	int n;
	int first;

	/*
	** When setting a new quota, we don't care about the existing
	** maildirsize.
	*/

	if ((info->fd=(newquota ? open("/dev/null", O_RDWR):
		       maildir_safeopen(filename,
					O_RDWR|O_APPEND, 0))) < 0)
		return (0);	/* No quota */

	if (newquota)
	{
		parsequotastr(newquota, &info->quota);

		if (info->quota.nbytes == 0 &&
		    info->quota.nmessages == 0)
		{
			close(info->fd);
			info->fd= -1;
			errno=EINVAL;
			return (-1);
		}
		info->recalculation_needed=1;
		return (0);
	}

	p=buf;
	l=sizeof(buf);

	while (l)
	{
		n=read(info->fd, p, l);
		if (n < 0)
		{
			close(info->fd);
			info->fd= -1;
			return (-1);
		}
		if (n == 0)	break;
		p += n;
		l -= n;
	}

	if (fstat(info->fd, &info->statbuf))	/* maildir too big */
	{
		close(info->fd);
		info->fd= -1;
		return (-1);
	}

	if (l == 0)	/*
			** maildirsize overflowed, still need to read its
			** quota
			*/
	{
		p[-1]=0;
		p=strchr(buf, '\n');
		if (p)
			*p=0;
		parsequotastr(buf, &info->quota);
		info->recalculation_needed=1;
		return (0);
	}


	info->size.nbytes=0;
	info->size.nmessages=0;
	info->nlines=0;
	*p=0;
	p=buf;
	first=1;
	while (*p)
	{
		int64_t n=0;
		int c=0;
		char	*q=p;
		int	neg;

		while (*p)
			if (*p++ == '\n')
			{
				p[-1]=0;
				break;
			}

		if (first)
		{
			parsequotastr(q, &info->quota);
			first=0;
			continue;
		}

		while (*q && isspace((int)(unsigned char)*q))
			++q;

		neg=0;
		if (*q == '-')
		{
			neg=1;
			++q;
		}

		if (DIG(*q))
		{
			while (DIG(*q))
			{
				n=n*10 + (*q++ - '0');
			}

			if (neg)
				n= -n;

			neg=0;
			while (*q && isspace((int)(unsigned char)*q))
				++q;

			if (*q == '-')
			{
				neg=1;
				++q;
			}
			if (DIG(*q))
			{
				while (DIG(*q))
				{
					c=c*10 + (*q++ - '0');
				}

				if (neg)
					c= -c;

				info->size.nbytes += n;
				info->size.nmessages += c;
			}
		}

		++ info->nlines;
	}
	if (info->size.nbytes < 0 ||
	    info->size.nmessages < 0)
		info->recalculation_needed=1;
	return (0);
}

static void parsequotastr(const char *quota, struct maildirquota *q)
{
	int64_t i;
	const char *quota_start = quota;

	q->nbytes=0;
	q->nmessages=0;

	while (quota && *quota)
	{
		if (!DIG(*quota))
		{
			++quota;
			continue;
		}
		i=0;
		while (DIG(*quota))
			i=i*10 + (*quota++ - '0');
		switch (*quota)	{
		case MDQUOTA_SIZE:
			q->nbytes=i;
			break;
		case MDQUOTA_COUNT:
			q->nmessages=i;
			break;
		default:
			fprintf(stderr, "WARN: quota string '%s' not parseable\n",
				quota_start);
			break;
		}
	}
}


void maildir_closequotafile(struct maildirsize *info)
{
	if (info->maildir)
		free (info->maildir);
	info->maildir=NULL;

	if (info->maildirsizefile)
		free (info->maildirsizefile);
	info->maildirsizefile=NULL;

	if (info->fd >= 0)
		close(info->fd);
	info->fd= -1;
}

/**
 ** Check if size > quota, and calculate by how much
 */

static int checkOneQuota(int64_t size, int64_t quota, int *percentage);

static int checkQuota(struct maildirquota *size,
		      struct maildirquota *quota, int *percentage)
{
	int b_quota;
	int n_quota;

	if (checkOneQuota(size->nbytes, quota->nbytes, &b_quota) ||
	    checkOneQuota(size->nmessages, quota->nmessages,
			  &n_quota))
	{
		if (percentage)
			*percentage= 100;
		errno=ENOSPC;
		return -1;
	}

	if (b_quota < n_quota)
		b_quota=n_quota;

	if (percentage)
		*percentage=b_quota;
	return (0);
}

static int checkOneQuota(int64_t size, int64_t quota, int *percentage)
{
	int x=1;

	if (quota == 0) /* No quota */
	{
		*percentage=0;
		return (0);
	}

	if (size > quota)
	{
		*percentage=100;
		return (-1);
	}

	if (quota > 20000000)
		x=1024;

	*percentage= quota > 0 ? (size/x) * 100 / (quota/x):0;
	return 0;
}

static std::string makenewmaildirsizename(const char *, int *);
static int countcurnew(const char *, time_t *, int64_t *, unsigned *);
static int countsubdir(const char *, const char *,
		time_t *, int64_t *, unsigned *);
static int statcurnew(const char *, time_t *);
static int statsubdir(const char *, const char *, time_t *);

static int	doaddquota(struct maildirsize *, int, int64_t, int, int);

static int docheckquota(struct maildirsize *info,
			int64_t xtra_size,
			int xtra_cnt, int *percentage);

int maildir_checkquota(struct maildirsize *info,
		       int64_t xtra_size,
		       int xtra_cnt)
{
	int	dummy;

	return (docheckquota(info, xtra_size, xtra_cnt, &dummy));
}

int maildir_readquota(struct maildirsize *info)
{
	int	percentage=0;

	(void)docheckquota(info, 0, 0, &percentage);
	return (percentage);
}

static int docheckquota(struct maildirsize *info,
			int64_t xtra_size,
			int xtra_cnt,
			int *percentage)
{
	int	maildirsize_fd;
	int64_t	maildirsize_size;
	unsigned maildirsize_cnt;

	time_t	tm;
	time_t	maxtime;
	DIR	*dirp;
	struct dirent *de;

	struct maildirquota new_quota;

	*percentage=0;

	if (info->fd < 0)	/* No quota */
		return (0);

	new_quota=info->size;

	new_quota.nbytes += xtra_size;
	new_quota.nmessages += xtra_cnt;

	if (!info->recalculation_needed &&
	    checkQuota(&new_quota, &info->quota, percentage) == 0)
		return (0);	/* New size is under quota */

	/*
	** Overquota, see if it's time to recalculate the quota anyway
	*/

	time(&tm);
	if (!info->recalculation_needed &&
	    info->nlines == 1 && tm < info->statbuf.st_mtime + 15*60)
		return (-1);


	maxtime=0;
	maildirsize_size=0;
	maildirsize_cnt=0;

	if (countcurnew(info->maildir,
			&maxtime, &maildirsize_size, &maildirsize_cnt))
	{
		errno=EIO;
		return (-1);
	}

	dirp=opendir(info->maildir);
	while (dirp && (de=readdir(dirp)) != 0)
	{
#ifdef DT_LNK
		if (de->d_type == DT_LNK) continue;
#endif
		if (countsubdir(info->maildir, de->d_name,
				&maxtime, &maildirsize_size,
			&maildirsize_cnt))
		{
			errno=EIO;
			closedir(dirp);
			return (-1);
		}
	}
	if (dirp)
	{
#if	CLOSEDIR_VOID
		closedir(dirp);
#else
		if (closedir(dirp))
		{
			errno=EIO;
			return (-1);
		}
#endif
	}

	auto newmaildirsizename=
		makenewmaildirsizename(info->maildir, &maildirsize_fd);

	if (doaddquota(info, maildirsize_fd, maildirsize_size,
		maildirsize_cnt, 1))
	{
		unlink(newmaildirsizename.c_str());
		close(maildirsize_fd);
		errno=EIO;
		return (-1);
	}

	if (rename(newmaildirsizename.c_str(), info->maildirsizefile))
	{
		unlink(newmaildirsizename.c_str());
		close(maildirsize_fd);
		errno=EIO;
		return (-1);
	}

	info->recalculation_needed=0;
	info->size.nbytes=maildirsize_size;
	info->size.nmessages=maildirsize_cnt;
	info->nlines=1;
	close(info->fd);
	info->fd=maildirsize_fd;

	tm=0;

	if (statcurnew(info->maildir, &tm))
	{
		errno=EIO;
		return (-1);
	}

	dirp=opendir(info->maildir);
	while (dirp && (de=readdir(dirp)) != 0)
	{
#ifdef DT_LNK
		if (de->d_type == DT_LNK) continue;
#endif
		if (statsubdir(info->maildir, de->d_name, &tm))
		{
			errno=EIO;
			closedir(dirp);
			return (-1);
		}
	}
	if (dirp)
	{
#if	CLOSEDIR_VOID
		closedir(dirp);
#else
		if (closedir(dirp))
		{
			errno=EIO;
			return (-1);
		}
#endif
	}

	if (tm != maxtime)	/* Race condition, someone changed something */
	{
		info->recalculation_needed=1;
		info->nlines=0;
		errno=EAGAIN;
		return (-1);
	}

	*percentage=0;

	new_quota=info->size;

	new_quota.nbytes += xtra_size;
	new_quota.nmessages += xtra_cnt;

	return checkQuota(&new_quota, &info->quota, percentage);
}

int	maildir_addquota(struct maildirsize *info,
			 int64_t maildirsize_size, int maildirsize_cnt)
{
	if (info->fd < 0)
		return (0);

	return (doaddquota(info, info->fd, maildirsize_size,
			   maildirsize_cnt, 0));
}

static int doaddquota(struct maildirsize *info, int maildirsize_fd,
		      int64_t maildirsize_size, int maildirsize_cnt,
		      int isnew)
{
	char	n[NUMBUFSIZE];
	char	buf[NUMBUFSIZE * 4 + 32 ];
	char *p;
	int cnt;

	buf[0]=0;

	if (isnew)
	{
		if (info->quota.nbytes > 0)
		{
			char b[2];

			b[0]=MDQUOTA_SIZE;
			b[1]=0;

			strcat(strcat(buf, libmail_str_int64_t(info->quota.nbytes, n)),
			       b);
		}

		if (info->quota.nmessages > 0)
		{
			char b[2];

			b[0]=MDQUOTA_COUNT;
			b[1]=0;

			if (buf[0] != 0)
				strcat(buf, ",");

			strcat(strcat(buf,
				      libmail_str_size_t(info->quota.nmessages, n)),
			       b);
		}
		strcat(buf, "\n");
	}

	sprintf(buf + strlen(buf),
		"%12s ", libmail_str_int64_t(maildirsize_size, n));

	sprintf(buf + strlen(buf),
		"%12s\n", libmail_str_int64_t(maildirsize_cnt, n));

	p=buf;
	cnt=strlen(buf);

	while (cnt > 0)
	{
		int c=write( maildirsize_fd, p, cnt);

		if (c < 0)
			return (-1);

		cnt -= c;
		p += c;
	}

	return (0);
}


/* New maildirsize is built in the tmp subdirectory */

static std::string makenewmaildirsizename(const char *dir, int *fd)
{
char	hostname[256];
struct	stat stat_buf;
time_t	t;
std::string p;

	hostname[0]=0;
	hostname[sizeof(hostname)-1]=0;
	gethostname(hostname, sizeof(hostname)-1);
	p.reserve(strlen(dir)+strlen(hostname)+130);

	for (;;)
	{
		char	tbuf[NUMBUFSIZE];
		char	pbuf[NUMBUFSIZE];

		time(&t);
		p=dir;
		p += "/tmp/";
		p += libmail_str_time_t(t, tbuf);
		p += ".";
		p += libmail_str_pid_t(getpid(), pbuf);
		p += "_NeWmAiLdIrSiZe.";
		p += hostname;

		if (stat(p.c_str(), &stat_buf) < 0 &&
			(*fd=maildir_safeopen(p.c_str(),
				O_CREAT|O_RDWR|O_APPEND, 0644)) >= 0)
			break;
		sleep(3);
	}
	return (p);
}

static int statcurnew(const char *dir, time_t *maxtimestamp)
{
char	*p=(char *)malloc(strlen(dir)+5);
struct	stat	stat_buf;

	if (!p)	return (-1);
	strcat(strcpy(p, dir), "/cur");
	if ( stat(p, &stat_buf) == 0 && stat_buf.st_mtime > *maxtimestamp)
		*maxtimestamp=stat_buf.st_mtime;
	strcat(strcpy(p, dir), "/new");
	if ( stat(p, &stat_buf) == 0 && stat_buf.st_mtime > *maxtimestamp)
		*maxtimestamp=stat_buf.st_mtime;
	free(p);
	return (0);
}

static int statsubdir(const char *dir, const char *subdir, time_t *maxtime)
{
char	*p;
int	n;

	if ( *subdir != '.' || strcmp(subdir, ".") == 0 ||
		strcmp(subdir, "..") == 0
#ifndef TRASHQUOTA
				|| strcmp(subdir, "." TRASH) == 0
#endif
		)
		return (0);

	p=(char *)malloc(strlen(dir)+strlen(subdir)+2);
	if (!p)	return (-1);
	strcat(strcat(strcpy(p, dir), "/"), subdir);
	n=statcurnew(p, maxtime);
	free(p);
	return (n);
}

static int docount(const char *, time_t *, int64_t *, unsigned *);

static int countcurnew(const char *dir, time_t *maxtime,
	int64_t *sizep, unsigned *cntp)
{
char	*p=(char *)malloc(strlen(dir)+5);
int	n;

	if (!p)	return (-1);
	strcat(strcpy(p, dir), "/new");
	n=docount(p, maxtime, sizep, cntp);
	if (n == 0)
	{
		strcat(strcpy(p, dir), "/cur");
		n=docount(p, maxtime, sizep, cntp);
	}
	free(p);
	return (n);
}

static int countsubdir(const char *dir, const char *subdir, time_t *maxtime,
	int64_t *sizep, unsigned *cntp)
{
char	*p;
int	n;

	if ( *subdir != '.' || strcmp(subdir, ".") == 0 ||
	     strcmp(subdir, "..") == 0 ||
	     ! maildirquota_countfolder(subdir))
		return (0);

	p=(char *)malloc(strlen(dir)+strlen(subdir)+2);
	if (!p)	return (2);
	strcat(strcat(strcpy(p, dir), "/"), subdir);
	n=countcurnew(p, maxtime, sizep, cntp);
	free(p);
	return (n);
}

static int docount(const char *dir, time_t *dirstamp,
	int64_t *sizep, unsigned *cntp)
{
struct	stat	stat_buf;
char	*p;
DIR	*dirp;
struct dirent *de;
unsigned long s;

	if (stat(dir, &stat_buf))	return (0);	/* Ignore */
	if (stat_buf.st_mtime > *dirstamp)	*dirstamp=stat_buf.st_mtime;
	if ((dirp=opendir(dir)) == 0)	return (0);
	while ((de=readdir(dirp)) != 0)
	{
	const char *n=de->d_name;

		if (*n == '.')	continue;

		if (!maildirquota_countfile(n))
			continue;

		if (maildir_parsequota(n, &s) == 0)
			stat_buf.st_size=s;
		else
		{
			p=(char *)malloc(strlen(dir)+strlen(n)+2);
			if (!p)
			{
				closedir(dirp);
				return (-1);
			}
			strcat(strcat(strcpy(p, dir), "/"), n);
			if (stat(p, &stat_buf))
			{
				free(p);
				continue;
			}
			free(p);
		}
		*sizep += stat_buf.st_size;
		++*cntp;
	}

#if	CLOSEDIR_VOID
	closedir(dirp);
#else
	if (closedir(dirp))
		return (-1);
#endif
	return (0);
}

int maildirquota_countfolder(const char *folder)
{
#ifdef TRASHQUOTA

#else

	if (strcmp(folder, "." TRASH) == 0 ||
	    strcmp(folder, "." TRASH "/") == 0)
		return (0);

	for ( ; *folder; folder++)
		if (*folder == '/' &&
		    (strcmp(folder+1, "." TRASH) == 0 ||
		     strcmp(folder+1, "." TRASH "/") == 0))
			return (0);
#endif
	return (1);
}

int maildirquota_countfile(const char *n)
{
#ifdef TRASHQUOTA

#else
	const char *nn=strrchr(n, '/');

	if (nn != NULL)
		n=nn+1;

	/* do not count msgs marked as deleted */

	for ( ; *n; n++)
	{
		if (n[0] != MDIRSEP[0] || n[1] != '2' ||
		    n[2] != ',')	continue;
		n += 3;
		while (*n >= 'A' && *n <= 'Z')
		{
			if (*n == 'T')	return (0);
			++n;
		}
		break;
	}
#endif
	return (1);
}

/*
** Prepare to add something to the maildir
*/

int maildir_quota_add_start(const char *maildir,
			    struct maildirsize *info,
			    int64_t msgsize, int nmsgs,
			    const char *newquota)
{
	struct maildirquota mq;
	int i;

	if ( maildir_openquotafile(info, maildir))
		info->fd= -1;

	if (newquota != NULL)
	{
		parsequotastr(newquota, &mq);

		if ((mq.nbytes > 0 || mq.nmessages > 0) &&
		    (info->fd < 0 || info->quota.nbytes != mq.nbytes ||
		     info->quota.nmessages != mq.nmessages))
		{
			if (info->fd < 0)
			{
				maildir_quota_set(maildir, newquota);
				if (maildir_openquotafile(info, maildir))
					info->fd= -1;
			}
			else
			{
				info->quota=mq;
				info->recalculation_needed=1;
			}
		}
	}
	if (info->fd < 0)
		return (0);	/* No quota set on this maildir */

	for (i=0; i<5; i++)
	{
		int rc;

		rc=maildir_checkquota(info, msgsize, nmsgs);
		if (rc == 0)
			return (0);

		if (errno != EAGAIN)
		{
			maildir_closequotafile(info);
			return (-1);
		}
	}
	maildir_closequotafile(info);

	/* Cannot recover from a race condition, just punt */

	return (0);
}

void maildir_quota_add_end(struct maildirsize *info,
			   int64_t msgsize, int nmsgs)
{
	maildir_addquota(info, msgsize, nmsgs);
	maildir_closequotafile(info);
}

void maildir_quota_deleted(const char *maildir,
			   int64_t nbytes,	/* Must be negative */
			   int nmsgs)	/* Must be negative */
{
	struct maildirsize info;

	if ( maildir_openquotafile(&info, maildir))
		return;

	maildir_checkquota(&info, nbytes, nmsgs); /* Cleanup */
	maildir_addquota(&info, nbytes, nmsgs);
	maildir_closequotafile(&info);
}

void maildir_quota_recalculate(const char *maildir)
{
	struct maildirsize info;

	if (maildir_openquotafile(&info, maildir))
		return;
	info.recalculation_needed=1;

	maildir_readquota(&info);
	maildir_closequotafile(&info);
}

int maildir_quota_delundel_start(const char *maildir,
				 struct maildirsize *info,
				 int64_t msgsize, int nmsgs)
{
#if TRASHQUOTA
	return (0);
#else
	if (nmsgs < 0)
	{
		maildir_quota_deleted(maildir, msgsize, nmsgs);
		return (0);	/* Always allowed */
	}

	return maildir_quota_add_start(maildir, info, msgsize, nmsgs, NULL);
#endif
}

void maildir_quota_delundel_end(struct maildirsize *info,
				int64_t msgsize, int nmsgs)
{
#if TRASHQUOTA
	return;
#else
	if (nmsgs < 0)
		return;

	maildir_quota_add_end(info, msgsize, nmsgs);
#endif
}


void maildir_quota_set(const char *dir, const char *quota)
{
	struct maildirsize info;

	if (maildir_openquotafile_init(&info, dir, quota) == 0)
	{
		maildir_checkquota(&info, 0, 0);
		maildir_closequotafile(&info);
	}
}


static void do_deliver_warning(const char *msgfile, const char *dir)
{
	int	fdin, fd, ret;
	FILE *fpout;
	time_t	t;
	size_t	msg_len;
	struct stat	sb;
	char	hostname[256];
	char	buf[4096];
	size_t	n;
	struct	maildirsize info;
	struct maildir_tmpcreate_info createInfo;

	fdin=-1;

	if (msgfile && *msgfile)
		fdin=open(msgfile, O_RDONLY);

	if (fdin < 0)
	{
		msgfile=QUOTAWARNMSG;
		fdin=open(msgfile, O_RDONLY);
	}

	if (fdin < 0)
		return;

	std::string qname;

	qname.reserve(strlen(dir)+sizeof("/quotawarn")-1);
	qname += dir;
	qname += "/quotawarn";

	/* Send only one warning every 24 hours */
	time(&t);
	ret = stat(qname.c_str(), &sb);
	if ((ret == -1 && errno != ENOENT) ||
	    (ret == 0 && (sb.st_mtime + 86400) > t))
	{
		close(fdin);
		return;
	}

	fd = open(qname.c_str(), O_WRONLY|O_CREAT|O_TRUNC, 0644);
	if (fd == -1)
	{
		close(fdin);
		return;
	}
	if (write(fd, buf, 1) < 0)
		perror(msgfile);

	close(fd);

	strcpy(buf, "Date: ");
	strcat(buf, rfc822::mkdate(t).c_str());
	strcat(buf, "\n");

	hostname[0]=0;
	hostname[sizeof(hostname)-1]=0;
	gethostname(hostname, sizeof(hostname)-1);
	sprintf(buf+strlen(buf), "Message-Id: <%lu.overquota@%-1.256s>\n",
		(unsigned long)t, hostname);

	if (fstat(fdin, &sb) < 0) {
		close(fdin);
		return;
	}
	msg_len=strlen(buf)+sb.st_size;



	maildir_tmpcreate_init(&createInfo);
	createInfo.maildir=dir;
	createInfo.uniq="warn";
	createInfo.msgsize=msg_len;
	createInfo.doordie=1;

	if ((fpout=maildir_tmpcreate_fp(&createInfo)) == NULL)
	{
		close(fdin);
		return;
	}

	fprintf(fpout, "%s", buf);

	while ((n=read(fdin, buf, sizeof(buf))) > 0)
	{
		if (fwrite(buf, n, 1, fpout) != 1)
		{
			perror(createInfo.tmpname);
			break;
		}
	}
	close(fdin);

	if (fflush(fpout) || ferror(fpout))
	{
		fclose(fpout);
		unlink(createInfo.tmpname);
		maildir_tmpcreate_free(&createInfo);
		return;
	}

	if (fseek(fpout, 0L, SEEK_SET) >= 0)
	{
		/* Make sure the quota message's size itself is factored into
		** the quota. Deliver the message regardless of whether the
		** user is over quota.
		*/
		if (maildirquota_countfolder(dir))
		{
			maildir_quota_add_start(dir, &info, msg_len, 1, NULL);
			maildir_quota_add_end(&info, msg_len, 1);
		}
	}

	fclose(fpout);
	if (maildir_movetmpnew(createInfo.tmpname,
			       createInfo.newname))
	{
		unlink(createInfo.tmpname);
		maildir_tmpcreate_free(&createInfo);
		return;
	}
	maildir_tmpcreate_free(&createInfo);
}

void maildir_deliver_quota_warning(const char *dir, const int percent,
				   const char *msgquotafile)
{
	std::string p;
	struct stat	sb;

	/* If we delivered to a folder, dump the warning message into INBOX */

	p.reserve(strlen(dir)+sizeof("/maildirfolder")-1);
	p += dir;
	p += "/maildirfolder";

	/* If delivering to a folder, find quotawarn in its parent directory */

	if (stat(p.c_str(), &sb) == 0)
	{
		p.reserve(strlen(dir)+sizeof("/..")-1);
		p += dir;
		p += "/..";
		maildir_deliver_quota_warning(p.c_str(), percent, msgquotafile);
		return;
	}

	if (percent >= 0)
	{
		struct maildirsize info;

		if (maildir_openquotafile(&info, dir) == 0)
		{
			if (maildir_readquota(&info) >= percent)
			{
				maildir_closequotafile(&info);
				do_deliver_warning(msgquotafile, dir);
				return;
			}
			maildir_closequotafile(&info);
		}
	}
}
