/*
** Copyright 2001 Double Precision, Inc.  See COPYING for
** distribution information.
*/

static const char rcsid[]="$Id: pcpdir.c,v 1.5 2003/01/20 14:09:13 mrsam Exp $";

#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <rfc822/rfc822hdr.h>
#include "pcp.h"
#if HAVE_SYSLOG_H
#include "syslog.h"
#define syslog2 syslog
#else
#defien syslog2(a,b,c)
#endif

#if HAVE_DIRENT_H
#include <dirent.h>
#else
#define dirent direct
#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

/* PCP driver for filesystem-based calendar */

#define HOSTNAMELEN 256

struct PCPdir {
	struct PCP pcp;
	char *username;
	char *dirname;
	char *indexname;
	char *newindexname;
	char hostname_buf[HOSTNAMELEN];
	char unique_filename_buf[256];
	unsigned uniq_cnt;
} ;

struct PCPdir_event_participant {
	char *address;
	char *eventid;
} ;

struct PCPdir_new_eventid {
	struct PCP_new_eventid eventid;
	char *newfile;
	char *tmpfile;

	char *oldeventid;

	struct PCPdir_event_participant *event_participants;
	unsigned n_event_participants;

	int booked;
} ;

static void pcp_close_dir(struct PCPdir *);
static int cleanup(struct PCPdir *);

static struct PCPdir_new_eventid *neweventid(struct PCPdir *,
					     const char *,
					     struct PCP_save_event *);
static void destroyeventid(struct PCPdir *, struct PCPdir_new_eventid *);

static int saveevent(struct PCPdir *, struct PCPdir_new_eventid *,
		     struct PCP_save_event *);

static int commitevent(struct PCPdir *, struct PCPdir_new_eventid *,
		       struct PCP_commit *);
static int bookevent(struct PCPdir *, struct PCPdir_new_eventid *,
		     struct PCP_commit *);

static int listallevents(struct PCPdir *, struct PCP_list_all *);

static int cancelevent(struct PCPdir *, const char *, int *);
static int uncancelevent(struct PCPdir *, const char *,
			 int, struct PCP_uncancel *);
static int deleteevent(struct PCPdir *, struct PCP_delete *);
static int retrevent(struct PCPdir *, struct PCP_retr *);
static int setacl(struct PCPdir *, const char *, int);
static int listacl(struct PCPdir *,
		   int (*)(const char *, int, void *),
		   void *);

static const char *errmsg(struct PCP *pcp)
{
	return (strerror(errno));
}

static void noop(struct PCP *pcp)
{
}

static const char *getauthtoken(struct PCP *pcp)
{
	return (NULL);
}

struct PCP *pcp_open_dir(const char *dirname, const char *username)
{
	struct PCPdir *pd=(struct PCPdir *)malloc(sizeof(struct PCPdir));

	if (!pd)
		return (NULL);

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

	pd->username=strdup(username);
	if (!pd->username)
	{
		free(pd);
		return (NULL);
	}

	pd->dirname=strdup(dirname);

	if (!pd->dirname)
	{
		free(pd);
		return (NULL);
	}

	pd->indexname=malloc(strlen(dirname)+sizeof("/index"));
	if (!pd->indexname)
	{
		free(pd->username);
		free(pd->dirname);
		free(pd);
		return (NULL);
	}
	strcat(strcpy(pd->indexname, dirname), "/index");

	pd->newindexname=malloc(strlen(dirname)+sizeof("/.index.tmp"));
	if (!pd->newindexname)
	{
		free(pd->username);
		free(pd->indexname);
		free(pd->dirname);
		free(pd);
		return (NULL);
	}
	strcat(strcpy(pd->newindexname, dirname), "/.index.tmp");

	pd->hostname_buf[sizeof(pd->hostname_buf)-1]=0;
	pd->hostname_buf[0]=0;
	if (gethostname(pd->hostname_buf, sizeof(pd->hostname_buf)-1) < 0
	    || pd->hostname_buf[0] == 0)
		strcpy(pd->hostname_buf, "localhost");


	pd->pcp.authtoken_func=getauthtoken;
	pd->pcp.close_func= (void (*)(struct PCP *)) pcp_close_dir;
	pd->pcp.cleanup_func= (int (*)(struct PCP *)) cleanup;

	pd->pcp.create_new_eventid_func=
		(struct PCP_new_eventid *(*)(struct PCP *, const char *,
					     struct PCP_save_event *))
		neweventid;

	pd->pcp.destroy_new_eventid_func=
		(void (*)(struct PCP *, struct PCP_new_eventid *))
		destroyeventid;

	pd->pcp.commit_func=
		(int (*)(struct PCP *, struct PCP_new_eventid *,
			 struct PCP_commit *))
		commitevent;

	pd->pcp.book_func=
		(int (*)(struct PCP *, struct PCP_new_eventid *,
			 struct PCP_commit *))
		bookevent;

	pd->pcp.list_all_func=
		(int (*)(struct PCP *, struct PCP_list_all *))
		listallevents;

	pd->pcp.cancel_func=
		(int (*)(struct PCP *, const char *, int *))
		cancelevent;

	pd->pcp.uncancel_func=
		(int (*)(struct PCP *, const char *, int,
			 struct PCP_uncancel *))
		uncancelevent;

	pd->pcp.delete_func=
		(int (*)(struct PCP *, struct PCP_delete *))
		deleteevent;

	pd->pcp.retr_func=
		(int (*)(struct PCP *, struct PCP_retr *))
		retrevent;

	pd->pcp.errmsg_func=errmsg;

	pd->pcp.noop_func=noop;

	pd->pcp.acl_func=
		(int (*)(struct PCP *, const char *, int))setacl;
	pd->pcp.listacl_func=
		(int (*)(struct PCP *, int (*)(const char *, int, void *),
			 void *))listacl;

	return ((struct PCP *)pd);
}

static void pcp_close_dir(struct PCPdir *pd)
{
	free(pd->newindexname);
	free(pd->indexname);
	free(pd->dirname);
	free(pd->username);
	free(pd);
}


static void mkunique(struct PCPdir *pd)
{
	struct timeval tv;

	gettimeofday(&tv, NULL);

	sprintf(pd->unique_filename_buf, "%lu.%lu.%lu_%u.",
		(unsigned long)tv.tv_sec,
		(unsigned long)tv.tv_usec,
		(unsigned long)getpid(),
		++pd->uniq_cnt);

	strncat(pd->unique_filename_buf, pd->hostname_buf,
		sizeof(pd->unique_filename_buf)-1
		-strlen(pd->unique_filename_buf));
}

static char *dotlockname(struct PCPdir *pd)
{
	char *p=malloc(strlen(pd->dirname)+sizeof("/.lock"));

	if (!p)
		return (NULL);

	return (strcat(strcpy(p, pd->dirname), "/.lock"));
}

/*
** Mark calendar as updated
*/

static char *changedname(struct PCPdir *pd)
{
	char *p=malloc(strlen(pd->dirname)+sizeof("/changed"));

	if (!p)
	{
		syslog(LOG_CRIT, "pcp: out of memory.");
		return (NULL);
	}

	return (strcat(strcpy(p, pd->dirname), "/changed"));
}

static void markchanged(struct PCPdir *pd)
{
	char *p=changedname(pd);
	int n;

	if (!p) return;

	n=open(p, O_RDWR|O_CREAT, 0600);
	if (n >= 0)
		close(n);
}

/*
** Attempt to get rid of a stale dot-lock file.
**
** Return code: 0 - no dot-lock exists
**              1 - transient race condition, try again later
**              2 - valid dot-lock exists
**             <0 - permanent error.
*/

#define TRY_SLEEP 3
#define TRY_MAX 3

static int kill_stale_lock(struct PCPdir *pd, const char *stale_lock,
			   time_t *tm)
{
	int fd;
	char buffer[HOSTNAMELEN+256];
	int n;
	struct stat stat_buf;

	fd=open(stale_lock, O_RDONLY);
	if (fd < 0)
	{
		if (errno == ENOENT)
			return (0);
		return (-1);
	}

	n=read(fd, buffer, sizeof(buffer)-1);

	if (n < 0 || fstat(fd, &stat_buf) < 0)
	{
		close(fd);
		return (-1);
	}

	*tm=stat_buf.st_mtime;
	close(fd);

	if (n > 0 && buffer[n-1] == '\n')
	{
		char *p;

		buffer[n-1]=0;

		p=strchr(buffer, ' ');
		if (p)
		{
			*p++=0;
			if (strcmp(p, pd->hostname_buf) == 0)
				/* Dot-lock created on this server */
			{
				pid_t pn=atol(buffer);

				if (pn > 1)
				{
					if (kill(pn, 0) == 0 ||
					    errno != ESRCH)
					{
						return (2);
					}
					/* Process doesn't exist no more */

					if (unlink(stale_lock) == 0
					    || errno == ENOENT)
					{
						return (1);
					}
					return (-1);
				}

				syslog2(LOG_INFO,
					"Corrupted dot-lock - removing %s",
					stale_lock);

				if (unlink(stale_lock) == 0
				    || errno == ENOENT)
				{
					return (1);
				}
				return (-1);
			}

			return (1);
		}
	}

	syslog2(LOG_INFO,
		"Corrupted dot-lock - removing %s", stale_lock);

	if (unlink(stale_lock) == 0
	    || errno == ENOENT)
	{
		return (1);
	}

	return (-1);
}

static char *acquire_dotlock(struct PCPdir *pd)
{
	char *n=dotlockname(pd);
	char *tmpname;
	FILE *fp;
	unsigned try_cnt;
	time_t last_time;

	if (!n)
		return (NULL);

	mkunique(pd);

	tmpname=malloc(strlen(pd->dirname)+strlen(pd->unique_filename_buf)
		       +20);

	if (!tmpname)
	{
		free(n);
		syslog2(LOG_ALERT,
			"Failed to create dotlock: %s - %m",
			n);
		return (NULL);
	}
	strcat(strcat(strcat(strcpy(tmpname, pd->dirname), "/."),
		      pd->unique_filename_buf), ".lock");

	fp=fopen(tmpname, "w");

	if (!fp)
	{
		free(tmpname);
		free(n);
		syslog2(LOG_ALERT,
			"Failed to create dotlock: %s - %m",
			n);
		return (NULL);
	}

	if (fprintf(fp, "%lu %s\n", (unsigned long)getpid(), pd->hostname_buf)
	    < 0 || fflush(fp))
	{
		fclose(fp);
		unlink(tmpname);
		free(tmpname);
		free(n);
		syslog2(LOG_ALERT,
			"Failed to create dotlock: %s - %m",
			n);
		return (NULL);
	}

	if (fclose(fp) < 0)
	{
		unlink(tmpname);
		free(tmpname);
		free(n);
		syslog2(LOG_ALERT,
			"Failed to create dotlock: %s - %m",
			n);
		return (NULL);
	}

	try_cnt=0;
	last_time=0;
	for (;;)
	{
		time_t timestamp;
		int rc=kill_stale_lock(pd, n, &timestamp);

		if (rc > 0)
		{
			++try_cnt;
			if (last_time == 0)
				last_time=timestamp;

			if (timestamp != last_time)
				try_cnt=0;	/* Dot-lock was modified,
						** reset.
						*/

			if (try_cnt == TRY_MAX)	/* Time to kill dot lock */
			{
				if (rc > 1 || (unlink(n) < 0
					       && errno != ENOENT))
				{
					syslog2(LOG_ALERT,
						"Failed to obtain dotlock: %s",
						n);

					unlink(tmpname);
					free(tmpname);
					free(n);
					return (NULL);
				}
				syslog2(LOG_ALERT, "Removed stale dotlock: %s",
					n);

			}
			else if (try_cnt > TRY_MAX)
			{
				syslog2(LOG_ALERT,
					"Failed to obtain dotlock: %s",
					n);

				unlink(tmpname);
				free(tmpname);
				free(n);
				return (NULL);
			}
			last_time=timestamp;
			sleep(TRY_SLEEP);
			continue;
		}

		if (rc < 0)
		{
			syslog2(LOG_ALERT,
				"Failed to obtain dotlock: %s",
				n);
			unlink(tmpname);
			free(tmpname);
			free(n);
			return (NULL);
		}

		if (link(tmpname, n) == 0)
			break;

		if (try_cnt > TRY_MAX)
		{
			syslog2(LOG_ALERT,
				"Failed to obtain dotlock: %s",
				n);

			unlink(tmpname);
			free(tmpname);
			free(n);
			return (NULL);
		}
		++try_cnt;
		sleep(TRY_SLEEP);
	}

	unlink(tmpname);
	free(tmpname);
	return (n);
}

#if 0
static void renew_dotlock(struct PCPdir *pd, char *n)
{
	FILE *fp;
	char buf[HOSTNAMELEN*2];

	fp=fopen(n, "r");

	if (fp && fgets(buf, sizeof(buf), fp) != NULL)
	{
		char *p=strchr(buf, '\n');

		if (p)
			*p=0;

		p=strchr(buf, ' ');
		if (p)
		{
			*p++=0;

			if (atol(buf) == getpid() &&
			    strcmp(p, pd->hostname_buf) == 0)
			{
				fclose(fp);
				fp=fopen(n, "r+");
				if (fp)
				{
					if (fseek(fp, -1, SEEK_END) >= 0)
						putc('\n', fp);
					fflush(fp);
					fclose(fp);
				}
				return;
			}
		}
	}
	if (fp)
		fclose(fp);
}
#endif

static void release_dotlock(struct PCPdir *pd, char *n)
{
	FILE *fp;
	char buf[HOSTNAMELEN*2];

	fp=fopen(n, "r");

	if (fp && fgets(buf, sizeof(buf), fp) != NULL)
	{
		char *p=strchr(buf, '\n');

		if (p)
			*p=0;

		p=strchr(buf, ' ');
		if (p)
		{
			*p++=0;

			if (atol(buf) == getpid() &&
			    strcmp(p, pd->hostname_buf) == 0)
			{
				fclose(fp);
				unlink(n);
				free(n);
				return;
			}
		}
	}
	if (fp)
		fclose(fp);
	syslog2(LOG_ALERT, "Dotlock unexpectedly gone: %s", n);
	free(n);
}

static struct PCPdir_new_eventid *neweventid(struct PCPdir *pd,
					     const char *oldeventid_arg,
					     struct PCP_save_event *se)
{
	unsigned ut;

	struct PCPdir_new_eventid *p=(struct PCPdir_new_eventid *)
		malloc(sizeof(struct PCPdir_new_eventid));

	if (!p)
		return (NULL);

	mkunique(pd);

	p->eventid.eventid=malloc(strlen(pd->unique_filename_buf)+
				  strlen(pd->username)+2);
	if (!p->eventid.eventid)
	{
		free(p);
		return (NULL);
	}

	strcat(strcat(strcpy(p->eventid.eventid, pd->unique_filename_buf),
		      "@"), pd->username);

	p->tmpfile=malloc(strlen(pd->unique_filename_buf)
			  +strlen(pd->dirname)+10);
	if (!p->tmpfile)
	{
		free(p->eventid.eventid);
		free(p);
		return (NULL);
	}
	strcat(strcat(strcat(strcpy(p->tmpfile, pd->dirname),
			     "/."), pd->unique_filename_buf), ".tmp");

	p->newfile=malloc(strlen(pd->unique_filename_buf)
			  +strlen(pd->dirname)+2);
	if (!p->newfile)
	{
		free(p->tmpfile);
		free(p->eventid.eventid);
		free(p);
		return (NULL);
	}
	strcat(strcat(strcpy(p->newfile, pd->dirname),
		      "/"), pd->unique_filename_buf);

	if (oldeventid_arg)
	{
		if ((p->oldeventid=strdup(oldeventid_arg)) == NULL)
		{
			free(p->newfile);
			free(p->tmpfile);
			free(p->eventid.eventid);
			free(p);
			return (NULL);
		}
	}
	else
		p->oldeventid=0;
	p->booked=0;

	p->event_participants=NULL;
	p->n_event_participants=0;

	for (ut=0; ut<se->n_event_participants; ut++)
	{
		const char *q;

		for (q=se->event_participants[ut].address; *q; q++)
			if (isspace((int)(unsigned char)*q))
			{
				errno=EINVAL;
				destroyeventid(pd, p);
				return (NULL);
			}

		for (q=se->event_participants[ut].eventid; q && *q; q++)
			if (isspace((int)(unsigned char)*q))
			{
				errno=EINVAL;
				destroyeventid(pd, p);
				return (NULL);
			}
	}

	if (se->n_event_participants > 0)
	{
		if ((p->event_participants=
		     malloc( sizeof (*p->event_participants)
			     * se->n_event_participants)) == 0)
		{
			destroyeventid(pd, p);
			return (NULL);
		}

		for ( ; p->n_event_participants < se->n_event_participants;
		      ++p->n_event_participants)
		{
			p->event_participants[p->n_event_participants]
				.eventid=NULL;

			if (se->event_participants[p->n_event_participants]
			    .eventid
			    && (p->event_participants[p->n_event_participants]
				.eventid=strdup(se->event_participants
						[p->n_event_participants]
						.eventid))
			     == NULL)
			{
				destroyeventid(pd, p);
				return (NULL);
			}

			if ( (p->event_participants[p->n_event_participants]
			      .address=strdup(se->event_participants
					      [p->n_event_participants]
					      .address))
			     == NULL)
			{
				if (p->event_participants
				    [p->n_event_participants].eventid)
					free(p->event_participants
					     [p->n_event_participants]
					     .eventid);
				destroyeventid(pd, p);
				return (NULL);
			}
		}
	}

	if (saveevent(pd, p, se))
	{
		destroyeventid(pd, p);
		p=NULL;
	}

	return (p);
}

static void destroyeventid(struct PCPdir *pd, struct PCPdir_new_eventid *p)
{
	unsigned i;

	for (i=0; i<p->n_event_participants; i++)
	{
		if (p->event_participants[i].eventid)
			free(p->event_participants[i].eventid);
		if (p->event_participants[i].address)
			free(p->event_participants[i].address);
	}
	if (p->event_participants)
		free(p->event_participants);

	if (p->booked && p->eventid.eventid)
	{
		struct PCP_delete del;

		memset(&del, 0, sizeof(del));
		del.id=p->eventid.eventid;

		deleteevent(pd, &del);
	}

	if (p->oldeventid)
		free(p->oldeventid);
	free(p->newfile);

	if (p->tmpfile)
	{
		unlink(p->tmpfile);
		free(p->tmpfile);
		p->tmpfile=NULL;
	}

	free(p->eventid.eventid);
	free(p);
}		

static int saveevent(struct PCPdir *pd, struct PCPdir_new_eventid *ne,
		     struct PCP_save_event *si)
{
	char buf[BUFSIZ];
	int n;

	FILE *nf=fopen(ne->tmpfile, "w");

	if (!nf)
		return (-1);

	while ((n=pcp_read_saveevent(si, buf, sizeof(buf))) > 0)
	{
		if (fwrite(buf, n, 1, nf) != 1)
		{
			fclose(nf);
			return (-1);
		}
	}

	if (fflush(nf) || ferror(nf))
		n= -1;
	if (fclose(nf))
		n= -1;

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

	return (0);
}

/*
** Structure of the index file:
**
** eventid<TAB>filename<TAB>field<TAB>field<TAB>field...
**
** field is name=value
**
** Fields:
*/

#define TIME_FIELD	"t"		/* start,end */
#define CANCELLED_FIELD "c"		/* This event has been cancelled */
#define BOOKED_FIELD	"b"		/* This is a booked event */
#define PENDEL_FIELD	"d"		/* This event is pending to be deleted */
#define PROXY_FIELD	"P"		/* This event was placed by proxy */
#define PARTICIPANT_FIELD "p"		/* event participant */

/* The line_buffer struct is a line's worth of a buffer */

struct line_buffer {
	int bufsiz;
	char *buffer;	/* Also the eventid */

	char *next_field;	/* Iterator through the fields */
} ;

static int lb_init(struct line_buffer *pi)
{
	pi->bufsiz=BUFSIZ;
	pi->buffer=malloc(pi->bufsiz);
	if (!pi->buffer)
		return (-1);
	return (0);
}

static void lb_destroy(struct line_buffer *pi)
{
	free(pi->buffer);
}

static int lb_read(FILE *fp, struct line_buffer *pi)
{
	int n=0;
	int c;

	for (;;)
	{
		if (n >= pi->bufsiz)
		{
			int news=pi->bufsiz + BUFSIZ;
			char *newp=realloc(pi->buffer, news);

			if (!newp)
				return (-1);

			pi->buffer=newp;
			pi->bufsiz=news;
		}

		c=getc(fp);
		if (c == EOF)
			return (-1);

		if (c == '\n')
		{
			pi->buffer[n]=0;
			break;
		}
		pi->buffer[n++]=c;
	}

	return (0);
}

static int lb_is_eventid(struct line_buffer *pi, const char *ei)
{
	int l=strlen(ei);
	const char *p=pi->buffer;

	return (p && strncmp(p, ei, l) == 0 && p[l] == '\t');
}

static char *lb_event_id(struct line_buffer *pi)
{
	char *p=strchr(pi->buffer, '\t');
	char *q;

	if (!p)
		return (strdup(""));	/* Corrupted index file */

	q=malloc(p-pi->buffer+1);

	if (!q)
		return (NULL);

	memcpy(q, pi->buffer, p-pi->buffer);
	q[p-pi->buffer]=0;
	return (q);
}

static char *lb_filename(struct PCPdir *pd, struct line_buffer *pi)
{
	char *p=strchr(pi->buffer, '\t');
	char *q, *r;

	if (!p)
		return (strdup(""));	/* Corrupted index file */

	++p;
	r=strchr(p, '\t');
	if (!r)
		r=p+strlen(p);

	q=malloc(strlen(pd->dirname)+2+ (r-p));

	if (!q)
		return (NULL);

	strncat(strcat(strcpy(q, pd->dirname), "/"), p, r-p);
	return (q);
}

static char *lb_filename_nodir(struct line_buffer *pi)
{
	char *p=strchr(pi->buffer, '\t');
	char *q, *r;

	if (!p)
		return (strdup(""));	/* Corrupted index file */

	++p;
	r=strchr(p, '\t');
	if (!r)
		r=p+strlen(p);

	q=malloc(1+(r-p));

	if (!q)
		return (NULL);

	*q=0;
	strncat(q, p, r-p);
	return (q);
}

static const char *lb_first_field(struct line_buffer *pi)
{
	char *p;

	p=pi->buffer ? strchr(pi->buffer, '\t'):NULL;

	if (p)
	{
		++p;	/* p now is the filename */
		p=strchr(p, '\t');
		if (p)
			++p;
	}

	return (pi->next_field=p);
}

static const char *lb_next_field(struct line_buffer *pi)
{
	char *p=pi->next_field ? strchr(pi->next_field, '\t'):NULL;

	if (p)
		++p;
	return (pi->next_field=p);
}

static int lb_is_field(const char *p, const char *n)
{
	if (p)
	{
		int l=strlen(n);

		if (memcmp(p, n, l) == 0 &&
		    (p[l] == '=' || p[l] == '\t' || p[l] == 0))
			return (1);
	}
	return (0);
}

static int lb_is_cancelled(struct line_buffer *pi)
{
	const char *p;

	for (p=lb_first_field(pi); p; p=lb_next_field(pi))
		if (lb_is_field(p, CANCELLED_FIELD))
			return (1);
	return (0);
}

static int lb_is_proxy(struct line_buffer *pi)
{
	const char *p;

	for (p=lb_first_field(pi); p; p=lb_next_field(pi))
		if (lb_is_field(p, PROXY_FIELD))
			return (1);
	return (0);
}

static const char *lb_field_value(const char *p)
{
	while (p && *p && *p != '\t')
		if ( *p++ == '=')
			return (p);
	return (NULL);
}

static int lb_is_booked(struct line_buffer *pi, time_t *t)
{
	const char *p;

	for (p=lb_first_field(pi); p; p=lb_next_field(pi))
		if (lb_is_field(p, BOOKED_FIELD))
		{
			const char *q=lb_field_value(p);
			unsigned long ul;

			if (q && sscanf(q, "%lu", &ul) == 1)
			{
				if (t)
					*t= (time_t)ul;
				return (1);
			}
		}
	return (0);
}

static char *lb_field_value_buf(const char *p)
{
	const char *q;
	char *r;

	while (p && *p && *p != '\t')
		if ( *p++ == '=')
			break;

	if (!p)
		return (strdup(""));

	q=strchr(p, '\t');
	if (!q)
		return (strdup(p));

	r=malloc(q-p+1);

	if (!r)
		return (NULL);

	memcpy(r, p, q-p);
	r[q-p]=0;
	return (r);
}

static const char *lb_is_pendel(struct line_buffer *pi)
{
	const char *p;

	for (p=lb_first_field(pi); p; p=lb_next_field(pi))
		if (lb_is_field(p, PENDEL_FIELD))
			return (p);
	return (0);
}

static int lb_remove_field(struct line_buffer *pi,
			   FILE *nfp,
			   const char *fieldname,
			   const char *newfield,
			   const char *newvalue)
{
	char *q;
	const char *cp;

	q=lb_event_id(pi);
	if (!q)
		return (-1);

	fprintf(nfp, "%s\t", q);
	free(q);
	q=lb_filename_nodir(pi);
	if (!q)
		return (-1);
	fprintf(nfp, "%s", q);
	free(q);

	for (cp=lb_first_field(pi); cp; cp=lb_next_field(pi))
	{
		const char *cc;

		if (lb_is_field(cp, fieldname))
		    continue;

		cc=strchr(cp, '\t');
		if (!cc) cc=cp+strlen(cp);

		putc('\t', nfp);
		fwrite(cp, cc-cp, 1, nfp);
	}
	if (newfield)
	{
		fprintf(nfp, "\t%s", newfield);
		if (newvalue)
			fprintf(nfp, "=%s", newvalue);
	}
	putc('\n', nfp);
	return (0);
}

static int docommitevent(struct PCPdir *, struct PCPdir_new_eventid *,
			 int,
			 struct PCP_commit *);

static int commitevent(struct PCPdir *pd, struct PCPdir_new_eventid *ae,
		       struct PCP_commit *ci)
{
	return (docommitevent(pd, ae, 0, ci));
}

static int bookevent(struct PCPdir *pd, struct PCPdir_new_eventid *ae,
		     struct PCP_commit *ci)
{
	return (docommitevent(pd, ae, 1, ci));
}

static int pcp_is_conflict(struct line_buffer *,
			   const struct PCP_event_time **, unsigned,
			   struct PCP_commit *,
			   struct PCP_uncancel *);

static int eventexists(struct PCPdir *pd, const char *e, int *flag)
{
	FILE *fp=fopen(pd->indexname, "r");
	struct line_buffer pi;

	if (!fp)
		return (0);

	if (fseek(fp, 0L, SEEK_SET) < 0)
		return (-1);


	if (lb_init(&pi))
	{
		fclose(fp);
		return (-1);
	}

	*flag=0;

	while (lb_read(fp, &pi) == 0)
	{
		if (lb_is_eventid(&pi, e))
		{
			*flag=1;
			break;
		}
	}
	lb_destroy(&pi);
	if (ferror(fp))
	{
		fclose(fp);
		return (-1);
	}

	if (fclose(fp))
		return (-1);
	return (0);
}


static int docommitevent(struct PCPdir *pd, struct PCPdir_new_eventid *ae,
			 int bookmode,
			 struct PCP_commit *ci)
{
	const struct PCP_event_time **times;
	char *dotlock;
	FILE *fp;
	FILE *nfp;
	int rc;
	unsigned ut;

	char *deleted_event=0;	/* Filename of deleted event */
	int is_cancelled=0;
	int is_booked=0;
	int was_booked=0;

	ci->errcode=0;


	if (ae->tmpfile == NULL)	/* Already commited */
	{
		ci->errcode=PCP_ERR_EVENTNOTFOUND;
		return (-1);
	}

	if (ci->n_event_times <= 0)
	{
		ci->errcode=PCP_ERR_CONFLICT;
		return (-1);
	}

	/* Sort event times in chronological order */

	times=pcp_add_sort_times(ci->event_times,
				     ci->n_event_times);
	if (!times)
		return (-1);

	/* Use a dotlock to protect: reading old index + checking for
	** conflicts, writing a new index, updating everything */

	dotlock=acquire_dotlock(pd);

	if (!dotlock)
	{
		ci->errcode=PCP_ERR_LOCK;
		free(times);
		return (-1);
	}

	rc= -1;
	fp=fopen(pd->indexname, "a+");
	if (fp && fseek(fp, 0L, SEEK_SET) >= 0)
	{
		nfp=fopen(pd->newindexname, "w");
		if (nfp)
		{
			struct line_buffer pi;

			if (lb_init(&pi) == 0)
			{
				rc= 0;
				while (lb_read(fp, &pi) == 0)
				{
					if (ae->booked &&
					    lb_is_eventid(&pi,
							  ae->eventid.eventid))
					{
						was_booked=1;
						if (bookmode)
							continue;
						/* Drop old booking */

						if (lb_remove_field(&pi, nfp,
								    BOOKED_FIELD,
								    NULL,
								    NULL))
							rc= -1;
						is_booked=1;
						continue;
					}
					if (ae->oldeventid
					    && lb_is_eventid(&pi,
							     ae->oldeventid))
					{
						if (lb_is_cancelled(&pi))
							is_cancelled=1;
						if (deleted_event) /* ??? */
							free(deleted_event);
						deleted_event=lb_filename
							(pd, &pi);
						if (!deleted_event)
							rc= -1;
						if (bookmode)
						{
							const char *pendel=
								lb_is_pendel(&pi);
							if (pendel)
							{
								/* Event is locked by another book/update? */

								char *delid=lb_field_value_buf(pendel);

								if (!delid)
									rc= -1;
								else if (strcmp(delid, ae->eventid.eventid))
								{
									int flag;
									/*
									** Possibly, but make sure the locking
									** event actually exists.
									*/
									if (eventexists(pd, delid, &flag))
										rc= -1;
									else if (flag)
									{
										rc= -1;
										ci->errcode=PCP_ERR_EVENTLOCKED;
									}
								}
								fprintf(nfp, "%s\n",
									pi.buffer);
								free(delid);
							}
							else
							{
								lb_remove_field(&pi,
										nfp,
										PENDEL_FIELD,
										PENDEL_FIELD,
										ae->eventid.eventid);
							}
							/* Dont del just yet */
						}
						continue;
					}
					fprintf(nfp, "%s\n", pi.buffer);

					/* Check for conflicts UNLESS */

					if ((ci->flags & PCP_OK_CONFLICT)
					    == 0 &&
					    /* Conflics are OK */

					    (!ae->booked || bookmode) &&
					    /*
					    ** We're not committing a booked
					    ** event (check for conflicts at
					    ** time of booking).
					    */

					    !lb_is_cancelled(&pi) &&
					    /* This event is cancelled */

					    pcp_is_conflict(&pi,
							    times,
							    ci->n_event_times,
							    ci,
							    NULL)
					    )
					{
						rc= -1;
					}
				}

				if (rc == 0 && !ferror(fp) && !ferror(nfp)
				    && !is_booked)
				{
					/* Add a new index record */

					const char *p=
						strrchr(ae->newfile, '/');

					if (p)
						++p;
					else
						p=ae->newfile;

					fprintf(nfp, "%s\t%s",
						ae->eventid.eventid, p);

					/*
					** If older event is cancelled, so
					** is this event.
					*/

					if (is_cancelled)
					{
						fprintf(nfp, "\t"
							CANCELLED_FIELD);
					}

					if (ci->flags & PCP_BYPROXY)
						fprintf(nfp, "\t"
							PROXY_FIELD);

					if (bookmode)
					{
						time_t book_time;

						time(&book_time);
						fprintf(nfp, "\t" BOOKED_FIELD
							"=%lu",
							(unsigned long)
							book_time);
					}

					for (ut=0; ut<ci->n_event_times; ut++)
						fprintf(nfp, "\t"
							TIME_FIELD
							"=%lu,%lu",
							(unsigned long)
							times[ut]->start,
							(unsigned long)
							times[ut]->end);


					for (ut=0; ut<ae->n_event_participants;
					     ut++)
					{
						const char *p=
							ae->event_participants
							[ut].address;
						const char *q=
							ae->event_participants
							[ut].eventid;

						if (!p)
							continue;

						fprintf(nfp,
							"\t"
							PARTICIPANT_FIELD
							"=%s%s%s", p,
							q ? " ":"", q ? q:"");

						/* Sanity check: */

						while (*p)
						{
							if ((int)
							    (unsigned char)*p
							    <= ' ')
							{
								rc= -1;
								errno=EINVAL;
							}
							++p;
						}
					}

					fprintf(nfp, "\n");
				}
				if (ae->booked && !was_booked)
				{
					rc= -1;
					ci->errcode=PCP_ERR_EVENTNOTFOUND;
					ae->booked=0;
					/* Booked event disappeared */
				}

				if (rc == 0 && 
				    (fflush(nfp) < 0 || ferror(nfp)))
					rc= -1;

				lb_destroy(&pi);
			}
			if (fclose(nfp))
				rc= -1;
		}
		if (fclose(fp))
			rc= -1;

		if (rc == 0 && !bookmode)
		{
			if (rename(ae->tmpfile, ae->newfile))
				rc= -1;
		}

		if (rc == 0)
		{
			if (rename(pd->newindexname, pd->indexname))
				rc=-1;
		}

		if (rc == 0 && !bookmode)
		{
			if (deleted_event)
				unlink(deleted_event);
			free(ae->tmpfile);
			ae->tmpfile=0;
			ae->booked=0;
		}
		if (rc == 0 && bookmode)
			ae->booked=1;
	}
	else if (fp)
		fclose(fp);

	if (deleted_event)
		free(deleted_event);
	markchanged(pd);
	release_dotlock(pd, dotlock);
	free(times);
	return (rc);
}

/*
** Spring cleaning.
*/

struct cleanup_filename_list {
	struct cleanup_filename_list *next;
	char *filename;
} ;

static int event_expired(struct line_buffer *, time_t);

static int cleanup(struct PCPdir *pd)
{
	char *dotlock;
	FILE *fp;
	FILE *nfp;
	int rc;
	struct stat stat_buf;
	struct cleanup_filename_list *list=NULL;
	time_t now;

	dotlock=acquire_dotlock(pd);

	if (!dotlock)
		return (-1);

	/*
	** Read the current index.
	** Remove entries for events whose files don't exist.
	**
	** Save list of all files that are indexed.
	*/

	time(&now);
	rc= -1;
	fp=fopen(pd->indexname, "a+");
	if (fp && fseek(fp, 0L, SEEK_SET) >= 0)
	{
		nfp=fopen(pd->newindexname, "w");
		if (nfp)
		{
			struct line_buffer pi;

			if (lb_init(&pi) == 0)
			{
				rc= 0;
				while (lb_read(fp, &pi) == 0)
				{
					char *filename=lb_filename(pd, &pi);
					struct cleanup_filename_list *l;
					time_t booked_time;

					if (!filename)
					{
						rc= -1;
						break;
					}

					if (!*filename)
					{
						free(filename);
						continue;
					}

					if (lb_is_booked(&pi, &booked_time))
					{
						/* Expire books after 1 hr */

						if (booked_time < now - 60*60)
						{
							free(filename);
							continue;
						}
					}
					else if (stat(filename, &stat_buf))
					{
						if (errno != ENOENT)
						{
							free(filename);
							rc= -1;
							break;
						}
						free(filename);
						continue;
					}
					else if ( event_expired(&pi,
								now -
								CALENDARPURGE *
								60 * 60 * 24))
					{
						free(filename);
						continue;
						/*
						** cleanup loop below will
						** take care of the event file
						*/
					}

					l=malloc(sizeof(*list));
					l->next=list;
					l->filename=filename;
					list=l;

					fprintf(nfp, "%s\n", pi.buffer);
				}
				lb_destroy(&pi);
			}

			if (rc == 0 && (ferror(fp) || ferror(nfp)
					|| fflush(nfp)))
				rc= -1;

			if (fclose(nfp))
				rc= -1;
		}
		if (fclose(fp))
			rc= -1;

		if (rename(pd->newindexname, pd->indexname))
			rc= -1;
	}
	else if (fp)
		fclose(fp);

	if (rc == 0)	/* Time to scan the directory */
	{
		DIR *dirp=opendir(pd->dirname);
		struct dirent *de;
		const char *p;
		time_t now;

		time(&now);

		while (dirp && (de=readdir(dirp)) != NULL)
		{
			char *filename=malloc(strlen(pd->dirname)+2+
					      strlen(de->d_name));
			if (!filename)
			{
				rc= -1;
				break;
			}
			strcat(strcat(strcpy(filename, pd->dirname),
				      "/"), de->d_name);
			if (isdigit((int)(unsigned char)de->d_name[0]))
			{
				struct cleanup_filename_list *l;
				for (l=list; l; l=l->next)
					if (strcmp(l->filename, filename) == 0)
						break;

				if (!l)
				{
					unlink(filename);
				}
			}
			else
			{
				p=strrchr(filename, '.');
				if (p && (strcmp(p, ".tmp") == 0 ||
					  strcmp(p, ".lock") == 0) &&
				    stat(filename, &stat_buf) == 0 &&
				    stat_buf.st_mtime < now - 36 * 60 * 60)
					unlink(filename);
			}
			free(filename);
		}
		if (dirp)
			closedir(dirp);
	}
	release_dotlock(pd, dotlock);

	while (list)
	{
		struct cleanup_filename_list *l=list;

		list=l->next;
		free(l->filename);
		free(l);
	}
	return (rc);
}

static int parse_time_field(const char *, time_t *, time_t *);

static int event_expired(struct line_buffer *pi, time_t when)
{
	const char *p;

	for (p=lb_first_field(pi); p; p=lb_next_field(pi))
	{
		time_t start_time;
		time_t end_time;

		if (!lb_is_field(p, TIME_FIELD))
			continue;

		if (parse_time_field(lb_field_value(p),
				     &start_time, &end_time))
			continue;
		if (end_time > when)
			return (0);
	}
	return (1);
}

/* Check if existing event conflicts with new event */

static int parse_time_field(const char *p, time_t *start_time,
			    time_t *end_time)
{
	if (!p)
		return (-1);

	*start_time=0;
	*end_time=0;

	while (*p && isdigit((int)(unsigned char)*p))
	{
		*start_time=*start_time * 10 + (*p-'0');
		++p;
	}
	if (*p != ',')
		return (-1);

	++p;
	while (*p && isdigit((int)(unsigned char)*p))
	{
		*end_time=*end_time * 10 + (*p-'0');
		++p;
	}
	return (0);
}

static int pcp_is_conflict(struct line_buffer *lb,
			   const struct PCP_event_time **t, unsigned n,
			   struct PCP_commit *ae,
			   struct PCP_uncancel *un)
{
	unsigned i;
	const char *p;
	int rc=0;

	char *event_id=lb_event_id(lb);

	if (!event_id)
		return (-1);

	for (p=lb_first_field(lb); p; p=lb_next_field(lb))
	{
		time_t start_time;
		time_t end_time;

		if (!lb_is_field(p, TIME_FIELD))
			continue;

		if (parse_time_field(lb_field_value(p),
				     &start_time, &end_time))
			continue;

		for (i=0; i<n; i++)
		{
			time_t com_start;
			time_t com_end;

			if (start_time >= t[i]->end)
				continue;
			if (t[i]->start >= end_time)
				continue;

			com_start=start_time;
			com_end=end_time;
			if (t[i]->start > com_start)
				com_start=t[i]->start;
			if (t[i]->end < com_end)
				com_end=t[i]->end;

			rc= -1;
			if (ae)
			{
				ae->errcode=PCP_ERR_CONFLICT;
				if (ae->add_conflict_callback)
				{
					rc=(*ae->add_conflict_callback)
						(event_id, com_start,
						 com_end,
						 "@",
						 ae->add_conflict_callback_ptr
						 );
					if (rc)
					{
						ae->errcode=0;
						break;
					}
					rc= -1;
				}
			}

			if (un)
			{
				un->errcode=PCP_ERR_CONFLICT;
				if (un->uncancel_conflict_callback)
				{
					rc=(*un->uncancel_conflict_callback)
						(event_id, com_start,
						 com_end, "@",
						 un->uncancel_conflict_callback_ptr
						 );
					if (rc)
					{
						un->errcode=0;
						break;
					}
					rc= -1;
				}
			}
		}

	}
	free(event_id);
	return (rc);
}

static int listallevents(struct PCPdir *pd, struct PCP_list_all *li)
{
	FILE *fp;
	struct line_buffer lb;
	int rc=0;
	const char *p;

	if ((fp=fopen(pd->indexname, "r")) == NULL)
		return (0);	/* Empty calendar */

	if (lb_init(&lb) == 0)
	{
		while (lb_read(fp, &lb) == 0)
		{
			char *event_id=lb_event_id(&lb);

			if (!event_id)
			{
				rc= -1;
				break;
			}

			li->event_id=event_id;

			for (p=lb_first_field(&lb); p; p=lb_next_field(&lb))
			{
				if (lb_is_field(p, TIME_FIELD))
				{
					time_t start_time;
					time_t end_time;

					if (parse_time_field(lb_field_value(p),
							     &start_time,
							     &end_time))
						continue;

					if (li->list_from
					    && li->list_from == li->list_to)
					{
						if (li->list_from < start_time
						    || li->list_to >= end_time)
							continue;
					}
					else
					{
						if (li->list_from &&
						    end_time <= li->list_from)
							continue;

						if (li->list_to &&
						    start_time >= li->list_to)
							continue;
					}
					li->event_from=start_time;
					li->event_to=end_time;

					if ((rc= (*li->callback_func)
					     (li, li->callback_arg)) != 0)
						break;
				}
				if (rc) break;

			}

			free(event_id);
			if (rc)
				break;
		}
		lb_destroy(&lb);
	}
	fclose(fp);
	return (rc);
}

static int read_event_times(struct line_buffer *pi,
			    struct PCP_event_time **time_ret,
			    unsigned *time_n_ret)
{
	const char *c;
	unsigned n;
	time_t start_time, end_time;

	*time_n_ret=0;
	*time_ret=NULL;

	for (c=lb_first_field(pi); c; c=lb_next_field(pi))
		if (lb_is_field(c, TIME_FIELD) &&
		    parse_time_field(lb_field_value(c),
				     &start_time, &end_time) == 0)
			++ *time_n_ret;

	if (!*time_n_ret)
		return (0);

	if ( (*time_ret=(struct PCP_event_time *)malloc
	      (sizeof(struct PCP_event_time)* *time_n_ret)) == NULL)
		return (-1);

	n=0;
	for (c=lb_first_field(pi); c; c=lb_next_field(pi))
		if (lb_is_field(c, TIME_FIELD) &&
		    parse_time_field(lb_field_value(c),
				     &start_time, &end_time) == 0)
		{
			(*time_ret)[n].start=start_time;
			(*time_ret)[n].end=end_time;
			++n;
		}
	return (0);
}

static int cancelevent(struct PCPdir *pd, const char *event, int *errcode)
{
	char *dotlock;
	FILE *fp;
	FILE *nfp;
	int rc;
	int found=0;

	if (errcode) *errcode=0;
	dotlock=acquire_dotlock(pd);

	if (!dotlock)
		return (-1);

	rc= -1;
	fp=fopen(pd->indexname, "a+");
	if (fp && fseek(fp, 0L, SEEK_SET) >= 0)
	{
		nfp=fopen(pd->newindexname, "w");
		if (nfp)
		{
			struct line_buffer pi;

			if (lb_init(&pi) == 0)
			{
				rc= 0;
				while (lb_read(fp, &pi) == 0)
				{
					if (!lb_is_eventid(&pi, event))
					{
						fprintf(nfp, "%s\n",
							pi.buffer);
						continue;
					}
					found=1;
					if (lb_is_cancelled(&pi))
					{
						/* Already canned */
						fprintf(nfp, "%s\n",
							pi.buffer);
						continue;
					}

					fprintf(nfp, "%s\t"
						CANCELLED_FIELD
						"\n", pi.buffer);
				}
				lb_destroy(&pi);
				if (!found)
				{
					if (errcode)
						*errcode=PCP_ERR_EVENTNOTFOUND;
					errno=ENOENT;
					rc= -1;
				}
			}
			if (ferror(nfp) || fflush(nfp))
				rc= -1;
			if (fclose(nfp))
				rc= -1;
		}
		fclose(fp);

		if (rc == 0 && rename(pd->newindexname, pd->indexname))
			rc= -1;
	}
	else if (fp)
		fclose(fp);
	release_dotlock(pd, dotlock);
	return (rc);
}

static int uncancelevent(struct PCPdir *pd, const char *event, int flags,
			 struct PCP_uncancel *info)
{
	char *dotlock;
	FILE *fp;
	FILE *nfp;
	int rc;
	int found=0;
	struct PCP_event_time *old_times=NULL;
	const struct PCP_event_time **old_times_sorted=NULL;
	unsigned n_old_times;

	if (info) info->errcode=0;
	dotlock=acquire_dotlock(pd);

	if (!dotlock)
		return (-1);

	rc= -1;
	fp=fopen(pd->indexname, "a+");
	if (fp && fseek(fp, 0L, SEEK_SET) >= 0)
	{
		struct line_buffer pi;

		/* First, find the existing booked times */

		if (lb_init(&pi) == 0)
		{
			if (info)
				info->errcode=PCP_ERR_EVENTNOTFOUND;
			while (lb_read(fp, &pi) == 0)
			{
				if (!lb_is_eventid(&pi, event))
					continue;
				if (read_event_times(&pi, &old_times,
						     &n_old_times) == 0)
				{
					if (fseek(fp, 0L, SEEK_SET) >= 0)
						rc=0;
					break;
				}
			}
			lb_destroy(&pi);

			if (rc == 0 && info)
				info->errcode=0;
		}

		if (rc == 0 && n_old_times &&
		    (old_times_sorted=
		     pcp_add_sort_times(old_times, n_old_times)) == NULL)
			rc= -1;

		nfp=rc == 0 ? fopen(pd->newindexname, "w"):NULL;
		if (nfp)
		{
			struct line_buffer pi;

			if (lb_init(&pi) == 0)
			{
				rc= 0;
				while (lb_read(fp, &pi) == 0)
				{
					char *id=lb_event_id(&pi);

					if (!id)
					{
						rc= -1;
						break;
					}

					if (!lb_is_eventid(&pi, event))
					{
						if ((flags & PCP_OK_CONFLICT)
						    == 0 &&
						    !lb_is_cancelled(&pi))
						{
							rc= pcp_is_conflict
								(&pi,
								 old_times_sorted,
								 n_old_times,
								 NULL,
								 info);
						}
						fprintf(nfp, "%s\n",
							pi.buffer);
						continue;
					}
					found=1;
					if (!lb_is_cancelled(&pi))
					{
						/* Already uncanned */
						fprintf(nfp, "%s\n",
							pi.buffer);
						continue;
					}

					if (lb_remove_field(&pi, nfp,
							    CANCELLED_FIELD,
							    NULL,
							    NULL))
						rc= -1;
				}
				lb_destroy(&pi);
				if (!found)
				{
					if (info)
						info->errcode=PCP_ERR_EVENTNOTFOUND;
					errno=ENOENT;
					rc= -1;
				}
			}
			if (ferror(nfp) || fflush(nfp))
				rc= -1;
			if (fclose(nfp))
				rc= -1;
		}
		fclose(fp);

		if (rc == 0 && rename(pd->newindexname, pd->indexname))
			rc= -1;
	}
	else if (fp)
		fclose(fp);
	if (old_times_sorted)
		free (old_times_sorted);
	if (old_times)
		free (old_times);
	release_dotlock(pd, dotlock);
	return (rc);
}

static int deleteevent(struct PCPdir *pd, struct PCP_delete *del)
{
	char *dotlock;
	FILE *fp;
	FILE *nfp;
	int rc;
	char *deleted_event=0;

	del->errcode=0;
	dotlock=acquire_dotlock(pd);

	if (!dotlock)
		return (-1);

	rc= -1;
	fp=fopen(pd->indexname, "a+");
	if (fp && fseek(fp, 0L, SEEK_SET) >= 0)
	{
		nfp=fopen(pd->newindexname, "w");
		if (nfp)
		{
			struct line_buffer pi;

			if (lb_init(&pi) == 0)
			{
				rc= 0;
				while (lb_read(fp, &pi) == 0)
				{
					if (!lb_is_eventid(&pi, del->id))
					{
						fprintf(nfp, "%s\n",
							pi.buffer);
						continue;
					}

					if (deleted_event) /* ??? */
						free(deleted_event);
					deleted_event=lb_filename(pd, &pi);
					if (!deleted_event)
						rc= -1;
				}
				lb_destroy(&pi);
				if (!deleted_event)
				{
					del->errcode=PCP_ERR_EVENTNOTFOUND;
					errno=ENOENT;
					rc= -1;
				}
			}
			if (ferror(nfp) || fflush(nfp))
				rc= -1;
			if (fclose(nfp))
				rc= -1;
		}
		fclose(fp);

		if (rc == 0 && rename(pd->newindexname, pd->indexname))
			rc= -1;
	}
	else if (fp)
		fclose(fp);
	if (deleted_event && rc == 0)
		unlink(deleted_event);
	if (deleted_event)
		free(deleted_event);
	markchanged(pd);
	release_dotlock(pd, dotlock);
	return (rc);
}

static int retrheaders(struct PCPdir *, struct PCP_retr *, const char *);
static int retrrfc822(struct PCPdir *, struct PCP_retr *, const char *);

static int retrevent(struct PCPdir *pd, struct PCP_retr *ri)
{
	FILE *fp;
	int rc;

	rc= -1;

	fp=fopen(pd->indexname, "r");
	if (fp && fseek(fp, 0L, SEEK_SET) >= 0)
	{
		struct line_buffer pi;

		if (lb_init(&pi) == 0)
		{
			rc= 0;
			while (lb_read(fp, &pi) == 0)
			{
				const char *c;
				char *filename;
				unsigned x;
				int status;

				for (x=0; ri->event_id_list[x]; x++)
					if (lb_is_eventid(&pi,
							  ri->event_id_list[x])
					    )
						break;
				if (!(ri->event_id=ri->event_id_list[x]))
					continue;

				filename=lb_filename(pd, &pi);
				if (!filename)
				{
					rc= -1;
					break;
				}
				if (!*filename)
				{
					free(filename);
					continue;
				}

				status=0;
				if (lb_is_cancelled(&pi))
					status |= LIST_CANCELLED;
				if (lb_is_booked(&pi, NULL))
					status |= LIST_BOOKED;
				if (lb_is_proxy(&pi))
					status |= LIST_PROXY;

				if (ri->callback_retr_status)
				{
					rc= (*ri->callback_retr_status)
						(ri, status,
						 ri->callback_arg);
					if (rc)
						break;
				}

				for (c=lb_first_field(&pi); c;
				     c=lb_next_field(&pi))
				{
					if (lb_is_field(c, TIME_FIELD))
					{
						time_t from, to;

						if (!ri->callback_retr_date)
							continue;

						if (parse_time_field
						    (lb_field_value(c),
						     &from, &to))
							continue;

						rc=(*ri->
						    callback_retr_date)
							(ri, from, to,
							 ri->callback_arg);
						if (rc)
							break;
						continue;
					}

					if (lb_is_field(c, PARTICIPANT_FIELD))
					{
						char *pp, *qq;;
						if (ri->
						    callback_retr_participants
						    == NULL)
							continue;

						pp=lb_field_value_buf(c);

						if (!pp)
						{
							rc= -1;
							break;
						}

						if ((qq=strchr(pp, ' ')) != 0)
							*qq++=0;

						rc=(*ri->
						    callback_retr_participants)
							(ri, pp, qq,
							 ri->callback_arg);
						free(pp);
						if (rc)
							break;
						continue;
					}
				}

				if (rc)
				{
					free(filename);
					break;
				}

				if (ri->callback_headers_func == NULL &&
				    ri->callback_rfc822_func == NULL)
					continue;

				if (ri->callback_begin_func)
				{
					rc= (*ri->callback_begin_func)
						(ri, ri->callback_arg);
					if (rc)
					{
						free(filename);
						break;
					}
				}

				if (ri->callback_rfc822_func)
					rc=retrrfc822(pd, ri, filename);
				else if (ri->callback_headers_func)
					rc=retrheaders(pd, ri, filename);

				if (rc == 0 && ri->callback_end_func)
					rc= (*ri->callback_end_func)
						(ri, ri->callback_arg);

				free(filename);
				if (rc)
					break;
			}
			lb_destroy(&pi);
		}
		fclose(fp);
	}
	else if (fp)
		fclose(fp);
	return (rc);
}

static int retrheaders(struct PCPdir *pd, struct PCP_retr *ri,
		       const char *filename)
{
	FILE *fp=fopen(filename, "r");
	struct rfc822hdr h;
	int rc=0;

	if (!fp)
		return (errno == ENOENT ? 0:-1);

	rfc822hdr_init(&h, 8192);

	while (rfc822hdr_read(&h, fp, NULL, 0) == 0)
	{
		if ((rc= (*ri->callback_headers_func)(ri, h.header,
						      h.value,
						      ri->callback_arg)) != 0)
			break;
	}
	if (rc == 0 && ferror(fp))
		rc= -1;
	rfc822hdr_free(&h);
	fclose(fp);
	return (0);
}

static int retrrfc822(struct PCPdir *pd, struct PCP_retr *ri,
		      const char *filename)
{
	int fd=open(filename, O_RDONLY);
	char buf[BUFSIZ];
	int n;
	int rc=0;

	if (fd < 0)
		return (errno == ENOENT ? 0:-1);

	while ((n=read(fd, buf, sizeof(buf))) > 0)
		if ((rc= (*ri->callback_rfc822_func)(ri, buf, n,
						     ri->callback_arg)) != 0)
			break;
	if (rc == 0 && n < 0)
		rc= -1;
	close(fd);
	return (rc);
}

static int setacl(struct PCPdir *pd, const char *who, int flags)
{
	char *dotlock;
	FILE *fp;
	FILE *nfp;
	int rc;
	char *aclname;
	char *newaclname;
	char buf[1024];

	if (strchr(who, '\r') || strchr(who, '\n') || strlen(who) > 512)
	{
		errno=EINVAL;
		return (-1);
	}

	aclname=malloc(strlen(pd->dirname)+sizeof("/acl"));

	if (!aclname)
		return (-1);

	newaclname=malloc(strlen(pd->dirname)+sizeof("/acl.new"));

	if (!newaclname)
	{
		free(aclname);
		return (-1);
	}

	dotlock=acquire_dotlock(pd);

	if (!dotlock)
	{
		free(newaclname);
		free(aclname);
		return (-1);
	}

	strcat(strcpy(aclname, pd->dirname), "/acl");
	strcat(strcpy(newaclname, pd->dirname), "/acl.new");

	rc= -1;
	nfp=fopen(newaclname, "w");
	if (nfp)
	{
		int l=strlen(who);

		fp=fopen(aclname, "r");
		if (fp)
		{
			while (fgets(buf, sizeof(buf), fp))
			{
				if (strncmp(buf, who, l) == 0 &&
				    isspace((int)(unsigned char)buf[l]))
					continue;
				fprintf(nfp, "%s", buf);
			}
			fclose(fp);
		}
		if (flags)
			fprintf(nfp, "%s\t%d\n", who, flags);
		rc=0;
		if (fflush(nfp) || ferror(nfp))
			rc= -1;
		if (fclose(nfp) || rename(newaclname, aclname))
			rc= -1;
	}
	release_dotlock(pd, dotlock);
	free(newaclname);
	free(aclname);
	return (rc);
}

static int listacl(struct PCPdir *pd, int (*func)(const char *, int, void *),
		   void *arg)
{
	char *aclname;
	char buf[1024];
	FILE *fp;

	aclname=malloc(strlen(pd->dirname)+sizeof("/acl"));

	if (!aclname)
		return (-1);

	strcat(strcpy(aclname, pd->dirname), "/acl");

	fp=fopen(aclname, "r");
	free(aclname);

	if (fp)
	{
		while (fgets(buf, sizeof(buf), fp))
		{
			char *p;
			int rc;

			for (p=buf; *p; p++)
				if (isspace((int)(unsigned char)*p))
				{
					*p++=0;
					break;
				}

			rc= (*func)(buf, atoi(p), arg);
			if (rc)
			{
				fclose(fp);
				return (rc);
			}
		}
		fclose(fp);
	}
	return (0);
}
