/* $Id: c2c.c,v 1.54 2003/07/29 21:40:54 mad Exp $ */
/*
 * Copyright (c) 2003, Alexander Marx
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *    - Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer. 
 *    - Redistributions in binary form must reproduce the above
 *      copyright notice, this list of conditions and the following
 *      disclaimer in the documentation and/or other materials provided
 *      with the distribution. 
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * "This product includes software developed by Computing Services * at 
 *  Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * (see http://asg.web.cmu.edu/cyrus/imapd/license.html for more info)
 *
*/

#include "c2c.h"

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

char *cyrus_root=NULL, *courier_root=NULL, *mailbox=NULL, 
	*cyrus_quota_dir=NULL, *cyrus_subscribe_dir=NULL, *cyrus_seen_dir=NULL;

short verbose=0, dovecot=0, hashed_subscribe=0, hashed_quota=0, hashed_seen=0;
int sflag=0, qflag=0, xflag=0, eflag=0;

struct t_seenfile *seen_file=NULL;


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

int main(int argc, char **argv) {

	int ch;
	 
#ifdef HAVE_GETOPT_LONG
	static struct option longopts[] = {
		{ "verbose",		no_argument,		0,	'v'},
		{ "help",		no_argument,		0,	'h'},
		{ "quota-dir",  	required_argument,      0,	'q'},
		{ "subscribe-dir",  	required_argument,      0,	's'},
		{ "seen-dir",  	        required_argument,      0,	'e'},
		{ "hashed",  		required_argument,     	0,	'x'},
 		{ "dovecot",    	no_argument,    	0,	'd'},
		{ "Version",    	no_argument,    	0,	'V'},
		{ 0,            	0,                      0,	 0 }
	};
	while((ch=getopt_long(argc, argv, COMMAND_PARAMETERS, longopts, NULL))!=-1) {
#else
	while((ch=getopt(argc, argv, COMMAND_PARAMETERS))!=-1) {
#endif
		switch(ch) {
			case 'V':
				fprintf(stdout, "cyrus2courier - Version %s\n", C2C_VERSION);
				exit(C2C_EXIT_SUCCESS);
				break;

			case 'v':
				verbose++;
				break;

			case 'd':
				dovecot=1;
				break;

			case 'q':
				if(verify_path(optarg)) {
					/* XXX: convert relative path to absolute */
					cyrus_quota_dir = optarg;
					qflag=1;
				} else
					bail_out(C2C_EXIT_FAIL, "main", "Unable to open Quota-Directory. (%s)", optarg);
				break;

			case 's':
				if(verify_path(optarg)) {
					/* XXX: convert relative path to absolute */
					cyrus_subscribe_dir = optarg;
					sflag = 1;	
				} else
					bail_out(C2C_EXIT_FAIL, "main", "Unable to open Subscribe-Directory. (%s)", optarg);
				break;

			case 'e':
				if(verify_path(optarg)) {
					/* XXX: convert relative path to absolute */
					cyrus_seen_dir = optarg;
					eflag = 1;	
				} else
					bail_out(C2C_EXIT_FAIL, "main", "Unable to open Seen-Directory. (%s)", optarg);
				break;

			case 'x':
				xflag=1;
				if(strchr(optarg, 'q'))
					hashed_quota=1;
				if(strchr(optarg, 's'))
					hashed_subscribe=1;
				if(strchr(optarg, 'e'))
					hashed_seen=1;
				break;


			case '?':
			case 'h':
			default:
				usage();	
		}
	}
	argc -= optind;
	argv += optind;

	/* verify arguments */
	if(argc==3) {
		if((qflag && sflag) && (strncmp(cyrus_quota_dir, cyrus_subscribe_dir, MAXBUF)==0)) {
			bail_out(C2C_EXIT_FAIL, "main", "Quota-Directory and Subscribe-Directory must differ.");
		} else if (qflag && cyrus_quota_dir[0]!='/') {
			bail_out(C2C_EXIT_FAIL, "main", "Need absolute Quota-Directoy path.");
		} else if (sflag && cyrus_subscribe_dir[0]!='/') {
			bail_out(C2C_EXIT_FAIL, "main", "Need absolute Subscribe-Directoy path.");
		} else if (!verify_path(argv[argc-3])) {
			bail_out(C2C_EXIT_FAIL, "main", "Invalid path for Cyrus-Root-Directory.");
		} else if (!verify_path(argv[argc-2])) {
			bail_out(C2C_EXIT_FAIL, "main", "Invalid path for Courier-Root-Directory.");
		} else if (strncmp(argv[argc-3], argv[argc-2], MAXBUF)==0) {
			bail_out(C2C_EXIT_FAIL, "main", "Cyrus-Root-Directory and Courier-Root-Directory must differ.");
		} else if (strchr(argv[argc-1], '/')!= NULL) {
			bail_out(C2C_EXIT_FAIL, "main", "Invalid Mailbox-Name.");
		} else if ((strncmp(argv[argc-1], "..", MAXBUF)==0) || 
				(strncmp(argv[argc-1], ".", MAXBUF)==0)) {
			bail_out(C2C_EXIT_FAIL, "main", "Invalid Mailbox-Name.");
		} else if (argv[argc-2][0]!='/' || argv[argc-3][0]!='/') {
			bail_out(C2C_EXIT_FAIL, "main", "Need absolute Root-Directory paths.");
		} else if (xflag && (!hashed_subscribe && !hashed_quota && !hashed_seen)) {
			bail_out(C2C_EXIT_FAIL, "main", "option 'hashed' needs 'q' and/or 's' and/or 'e' as parameter.");
		} else {	
			cyrus_root=argv[argc-3];
			courier_root=argv[argc-2];
			mailbox=argv[argc-1];
			chdir(cyrus_root);
			traverse(cyrus_root, courier_root, mailbox);
			if(cyrus_seen_dir && seen_file) {
				flush_seenfile(seen_file);
			}
		}
	} else
		usage();

	if(verbose)
		verbose_print(C2C_DONE, __FUNCTION__, NULL);
	return C2C_EXIT_SUCCESS;
}


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

void usage(void) {
	
	/* usage() is wrong for all platforms without getopt_long */
	fprintf(stdout, "\nUsage: cyrus2courier [options] cyrusdir courierdir mailbox\n");
	fprintf(stdout, "\n   -V, --Version .............. print version information");
	fprintf(stdout, "\n   -v, --verbose .............. be verbose / repeat for more verbosity");
	fprintf(stdout, "\n   -q, --quota-dir=<dir> ...... cyrus quota-files directory");
	fprintf(stdout, "\n   -s, --subscribe-dir=<dir> .. cyrus subscribe-files directory");
	fprintf(stdout, "\n   -e, --seen-dir=<dir> ....... cyrus seen-files directory");
	fprintf(stdout, "\n   -x, --hashed=[s][q][e]...... cyrus hashed (s)ubscribe/(q)uota/s(e)en dirs");
	fprintf(stdout, "\n   -d, --dovecot .............. write Dovecot-compatible files\n\n");

	exit(C2C_EXIT_SUCCESS);
}

void bail_out(int rc, char *where, char *msg, ...) {
	va_list args;

	va_start(args, msg);
	switch(rc) {
		case C2C_EXIT_SUCCESS:
			fprintf(stdout, "DONE\t%s\n", mailbox);
			break;

		case C2C_EXIT_WARN:
			fprintf(stdout, "WARN\t %s", mailbox);
			fprintf(stdout, "\t\t(%s) ", where);
			vfprintf(stdout, msg, args);
			break;

		case C2C_EXIT_FAIL:
		default:
			fprintf(stdout, "FAIL\t %s", mailbox);
			fprintf(stdout, "\t\t(%s) ", where);
			vfprintf(stdout, msg, args);
			break;
	}
	va_end(args);
	fprintf(stderr, "\n");

	exit(rc);
}

void verbose_print(int type, const char *where, const char *format, ...) {
	va_list args;
	char *stype;
	static int last=-1;

	switch(type) {
		case C2C_EXIT_SUCCESS:
			stype="DONE";
			break;

		case C2C_EXIT_WARN:
			stype="WARN";
			break;

		case C2C_EXIT_INFO:
			stype="INFO";
			break;


		case C2C_EXIT_FAIL:
		default:
			stype="FAIL";
			break;
	}

	va_start(args, format);
	if(type!=last) {
		fprintf(stdout, "%s\t %s\n", stype, mailbox);
		last=type;
	}
	if(format) {
		fprintf(stdout, "\t(%s) ", where);
		vfprintf(stdout, format, args);
		fprintf(stdout, "\n");
	}
	va_end(args);
}

int verify_path(const char *mypath) {
	DIR *dirp=NULL;
	int rc=0;
	
	if(dirp=opendir(mypath)) {
		closedir(dirp);
		rc=1;
	}
	return rc;
}

int traverse(char *cy_root, char *co_root, char *dir) {
	static char spc[MAXBUF] = "";
	static char spd[MAXBUF] = "";
	static char spe[MAXBUF] = "";
	static int is_rootfolder=1;
	DIR  *dirp;
	struct dirent *entry;
	struct stat buf;
	int  filetype;
	char name[MAXNAME];
	char *cc=NULL;
	eSTR *s;
	int rc=0;


	if(is_rootfolder) {
		if(!verify_path(dir)) {
			if(verbose) {
				verbose_print(C2C_FAIL, __FUNCTION__, 
					"%s is not a mailbox.", dir);
			}
			exit(C2C_EXIT_FAIL);
				
		}
		if(cyrus_seen_dir) {
			eSTR *sf;
			if(!hashed_seen) {
				sf=va_newSTR(cyrus_seen_dir, "/", mailbox, ".seen", eSTR_END);
			} else {
				char h[4];
				h[0]='/';
				h[1]=mailbox[0];
				h[2]='/';
				h[3]='\0';
				sf=va_newSTR(cyrus_seen_dir, h, mailbox, ".seen", eSTR_END);
			}
			seen_file=read_seenfile(strSTR(sf), C2C_SEENTYPE_DB);
			if(!seen_file) {
				eflag=-1;
				if(verbose)
					verbose_print(C2C_WARN, __FUNCTION__, 
						"%s.seen file not found.", mailbox);
			}
			freeSTR(sf);
		}
	}

	filetype=((stat(dir, &buf) ? 0 : (buf.st_mode&S_IFMT)==S_IFDIR)) 
				? 'd' : 'f';

	if (filetype == 'd') {
		/* dirp points to a directory  */

		/* building path infos */

		if(strlen(spc)!=0) {
			strcat(spc, "/");
		}

		strcat(spc, dir);
		/* use . to separate sub-mailboxes */
		if(strlen(spd)!=0) {
			strcat(spd, ".");
		}
		strcat(spd, dir);
		/* append / to root mailbox */ 
		if (!strcmp(spd, dir)) {
		  strcat(spd, "/");
		}
		spe[0]='\0';
		strcat(spe, cy_root);
		strcat(spe, "/");
		strcat(spe, spc);

		s=va_newSTR(co_root, "/", spd, eSTR_END);

		/* create courier directory structure */
		courier_mkdir(co_root, spd, is_rootfolder);

		/* migrate quota-information */
		if(cyrus_quota_dir) {
			courier_quota(co_root, spd, is_rootfolder);
		}

		/* migrate folder-subscription info */
		if(cyrus_subscribe_dir) {
			courier_subscribe(co_root, spd, is_rootfolder);
		}
		/* copy/migrate the mailfiles */
		cydump(spe, strSTR(s),is_rootfolder);

		if(is_rootfolder) {
			is_rootfolder=0;
		}

		freeSTR(s);

		/* traverse directory tree */
		if(chdir(dir)==0) {
			if(dirp=opendir(".")) {
				while(entry=readdir(dirp)) {
					strcpy(name, entry->d_name);
					if(strcmp(name, ".") && 
							strcmp(name, ".."))
						traverse(cy_root, co_root, name);
				}
				closedir(dirp);
			} 
			if(cc=strrchr(spc, '/')) *cc='\0';
			if(cc=strrchr(spd, '.')) *cc='\0';
			chdir("..");
		}
		
	}
	return rc;
}

int courier_quota(char *root, char *dir, int is_root) {
	eSTR *s, *q, *t;
	long quota=-1, qused=-1;
	FILE *fi, *fo;
	
	/* quotafiles only exist in the mailbox-rootfolder */

	if(!is_root)
		return 1;

	s=va_newSTR(root, "/", dir, "/maildirsize", eSTR_END);
	q=va_newSTR(cyrus_quota_dir, "/", eSTR_END);

	if(hashed_quota) {
		t=newSTR(mailbox);
		t->str[1]='\0';
		va_catSTR(q, strSTR(t), "/", eSTR_END);
		freeSTR(t);
	}

	va_catSTR(q, "user.", mailbox, eSTR_END);

	/* 
	** read  "$(BYTES USED)\n$(kBYTES MAX)"
	** write "$(BYTES MAX)S\n$(BYTES USED)"
	**
	** XXX: count-quotas are not supported
	*/
	if(fi=fopen(strSTR(q), "r")) {
		if(t=getlineSTR(fi)) {
			qused=atol(strSTR(t));
			freeSTR(t);
		}
		if(t=getlineSTR(fi)) {
			quota=atol(strSTR(t))*1024;
			freeSTR(t);
		}
		if(fo=xfopen(strSTR(s), "w")) {
			char buf[14];
			
			/* courier likes padded quota-files */
			sprintf(buf, "%ldS", quota);
			fprintf(fo, "%-13s\n", buf);
			sprintf(buf, "%ld", qused);
			fprintf(fo, "%-13s\n", buf);
			fclose(fo);
		}
		fclose(fi);
	} else {
		if(verbose)
			verbose_print(C2C_WARN, __FUNCTION__, "cyrus quota-file missing");
	}
	freeSTR(s);
	return 1;
}

int courier_subscribe(char *root, char *dir, int is_root) {
	eSTR *s, *q, *t;
	int len;
	FILE *fi, *fo;
	
	/* subscribefiles only exist in mailbox-rootfolder */

	if(!is_root)
		return 1;


	s=va_newSTR(root, "/", dir, dovecot ? "/.subscriptions" :
		    "/courierimapsubscribed", eSTR_END);
	q=va_newSTR(cyrus_subscribe_dir, "/", eSTR_END);

	if(hashed_subscribe) {
		t=newSTR(mailbox);
		t->str[1]='\0';
		va_catSTR(q, strSTR(t), "/", eSTR_END);
		freeSTR(t);
	}

	va_catSTR(q, mailbox, ".sub", eSTR_END);

	len=strlen("user.")+strlen(mailbox)+1;

	/* 
	** readline  "user.$(MAILBOXNAME.folder.folder..."
	** writeline "INBOX.folder.folder..." 
	*/
	if(fi=fopen(strSTR(q), "r")) {
		if(fo=xfopen(strSTR(s), "w")) {
			while(t=getlineSTR(fi)) {
				if(lenSTR(t)>0) {
					char *p=strSTR(t);
					if(strlen(&p[len])!=0) {
						char *e=strrchr(&p[len], '\t');
						if(e) *e='\0';
						fprintf(fo, "INBOX.%s\n", &p[len]);
					}
				}
				freeSTR(t);
			}
			fclose(fo);
		}
		fclose(fi);
	} else {
		if(verbose>1)
			verbose_print(C2C_WARN, __FUNCTION__, "cyrus-subscribe file missing");
	}
	freeSTR(s);
	freeSTR(q);
	return 1;
}

int courier_mkdir(char *root, char *dir, int is_root) {
	eSTR *s, *c, *n, *t, *f;
	
	s=va_newSTR(root, "/", dir, eSTR_END);
	c=va_newSTR(strSTR(s), "/cur", eSTR_END);
	n=va_newSTR(strSTR(s), "/new", eSTR_END);
	t=va_newSTR(strSTR(s), "/tmp", eSTR_END);
	f=va_newSTR(strSTR(s), "/maildirfolder", eSTR_END);

	/* create the necessary folders */
	mkdir(strSTR(s), S_IRWXU);
	mkdir(strSTR(c), S_IRWXU);
	mkdir(strSTR(n), S_IRWXU);
	mkdir(strSTR(t), S_IRWXU);

	/* foreach subfolder we need to touch a "maildirfolder"
	* tells an MTA how to find the correct root-folder for 
	* quota calculations during deliveries
	*/
	if(!is_root) {
		fclose(xfopen(strSTR(f), "w"));
	}
		
	freeSTR(s);
	freeSTR(c);
	freeSTR(n);
	freeSTR(t);
	freeSTR(f);

	return 1;
}

int courier_cpmail(char *src, unsigned long size, char *dst_a, char *dst_b, unsigned long *rsize) {
	eSTR *m, *d;
	char is[32];
	int i;

	/* read the mailfile */
	m=infileSTR(src);

	/* strip away the carriage returns */
	i=tounixSTR(m);

	*rsize=size-i;
	sprintf(is, "%lu", size-i);

	/* build filename */
	d=va_newSTR(dst_a, is, dst_b, eSTR_END);

	/* write the stripped file */
	outfileSTR(m, d->str);

	freeSTR(m);
	freeSTR(d);

	return i;
}

int cydump(char *path, char *dest, int is_root)
{
	FILE *fh, *fuid=NULL, *fuidpop3=NULL;
	eSTR *s, *i, *r;
	int cs_len=0;
	char src[MAXBUF]="";
	char dst_a[MAXBUF]="";
	char dst_b[MAXBUF]="";
	struct index_header header;
	struct index_entry entry;
	unsigned long version, rsize;

	r=newSTR(path);
	if(verbose>2)
		verbose_print(C2C_INFO, __FUNCTION__,
				"converting %s", strSTR(r));

	i=va_newSTR(strSTR(r), "/cyrus.index", eSTR_END);
	s=va_newSTR(strSTR(r), "/cyrus.seen", eSTR_END);

	if(fh = fopen(strSTR(i), "r")) {
		/* minor_version in header tells us which cyrus-version this is */
		memset(&header, 0, sizeof(header));
		fread(&header, 3*4, 1, fh);

		version = ntohl(header.minor_version);
		fseek(fh, 0, SEEK_SET);

		switch (version) {
			case 2:
				fread(&header, 3*4 + 8*4, 1, fh);
				break;
			case 3:
				fread(&header, 3*4 + 8*4 + 3*4, 1, fh);
				break;
			case 4:
				fread(&header, 3*4 + 8*4 + 3*4 + 5*4, 1, fh);
				break;
			default:
				bail_out(C2C_EXIT_FAIL, "cydump", 
					"Header version %lu not supported", version);
		}

		sprintf(dst_a, "%s/%s", dest, dovecot ? "dovecot-uidlist" :
			"courierimapuiddb");
		fuid = xfopen(dst_a, "w");

		fprintf(fuid, "1 %lu %lu\n",
			(unsigned long)ntohl(header.uidvalidity),
			(unsigned long)ntohl(header.last_uid)+1);
		/* WARNING this will generate a version v1 pop3dsizelistefile
		 * as used by courier-imap 2.0.0 - later versions will use v2 
		 * which is currently NOT supported! */
		if (!dovecot && (is_root)) {
			sprintf(dst_a, "%s/%s", dest, "courierpop3dsizelist");	
			fuidpop3=xfopen(dst_a, "w");	
			/* it looks like that we want the NEXT uid here so just add 1 */	
			fprintf(fuidpop3, "/1 %lu\n", 
				(unsigned long)ntohl(header.last_uid)+1);
		}

		/* analyze cyrus.seen file */
		if(cyrus_seen_dir) {
			eSTR *ns=dupSTR(r);
			unsigned int cl=strlen(cyrus_root);
			char *nns=strSTR(ns)+cl;

			for(cl=0; cl<strlen(nns); cl++) {
				if(nns[cl]=='/')  {
					nns[cl]='.';
				}
			}
			nns++;

			setfolder_seenfile(seen_file, nns, (unsigned long)ntohl(header.uidvalidity));

			freeSTR(ns);
		} else {
			seen_file=read_seenfile(strSTR(s), C2C_SEENTYPE_FILES);
			if(!seen_file) {
				/* no cyrus.seen file found -> keep all as unseen */
				if(verbose>1)
					verbose_print(C2C_WARN, __FUNCTION__, 	
						"cyrus.seen file missing; keeping all mails as unseen.\n\t\t--> %s",
							strSTR(s));
			}
			setfolder_seenfile(seen_file, NULL, 0);
		}	


		/* foreach entry copy/migrate a mailfile */
		while(fread(&entry, sizeof(struct index_entry), 1, fh)) {

			sprintf(dst_a, "%s/cur/%lu.%lu,S=",
				dest, 
				(unsigned long)ntohl(entry.internaldate),
				(unsigned long)ntohl(entry.uid));

			/* maildir-flags must be in ascii sort order! */

			dst_b[0]='\0';
			strcat(dst_b, ":2,");
			if((ntohl(entry.system_flags)&FLAG_DRAFT))
				 strcat(dst_b, "D");

			if((ntohl(entry.system_flags)&FLAG_FLAGGED))
				 strcat(dst_b, "F");

			if((ntohl(entry.system_flags)&FLAG_ANSWERED))
				 strcat(dst_b, "R");

			if(cs_len!=-1) {
				if(isseen_seenfile(seen_file, (unsigned long)(ntohl(entry.uid))))
					 strcat(dst_b, "S");
			}

			if((ntohl(entry.system_flags)&FLAG_DELETED))
				 strcat(dst_b, "T");
		
			sprintf(src, "%s/%lu.", path,
				(unsigned long)ntohl(entry.uid));

			/* copy/migrate the mailfile */
			courier_cpmail(src, (unsigned long)ntohl(entry.size), dst_a, dst_b, &rsize);

			/* build courierpop3dsizelistfile */
			if (!dovecot && (is_root)) {
				fprintf(fuidpop3,"%s%lu%s %lu %lu\n",
					strrchr(dst_a, '/')+1, rsize, dst_b,
					(unsigned long)ntohl(entry.size),
					(unsigned long)ntohl(entry.uid));
			}	

			/* filenames in uiddb-files are w/o flags, dunno why. */
			fprintf(fuid, "%lu %s%lu\n",
				(unsigned long)ntohl(entry.uid),
				strrchr(dst_a, '/')+1, rsize);


		}

		if(!cyrus_seen_dir) {
			/* release seen_file resources */
			flush_seenfile(seen_file);
			seen_file=NULL;
		}

		fclose(fh);
		fclose(fuid);
		if (!dovecot && (is_root))
			fclose(fuidpop3);
	} else {
		/* no index file?! .. */

		/* checking whether folder contains mails or not */
		DIR *dirp;
		struct dirent *dp;
		int is_empty=1;
	
		if(dirp=opendir(strSTR(r))) {
			while ((dp=readdir(dirp))) {
				if(dp->d_name[strlen(dp->d_name)-1] == '.' && dp->d_name[0] != '.') {
					/* found some mail-files! */
					is_empty=0;
				}
			}
			closedir(dirp);
		}

		if(is_empty) {
			if(verbose>1) {
				verbose_print(C2C_WARN, __FUNCTION__, 
					"cyrus.index file missing; folder contains no mails; skipping\n\t\t-->%s", 
					strSTR(r));
			}
		} else if (verbose) {
			verbose_print(C2C_WARN, __FUNCTION__, 
				"cyrus.index file missing; folder contains files(possibly mails!); FIX MANUALLY\n\t\t==>%s", 
				strSTR(r));
		}
	}
	freeSTR(s);
	freeSTR(i);
	freeSTR(r);

	return 0;
}

FILE *xfopen(const char *_p, const char *_m) {
	FILE *fh=NULL;

	if(_p && _m) {
		if(fh=fopen(_p, _m))
			return fh;
	}
	bail_out(C2C_EXIT_FAIL, "xfopen", 
		"Unable to open file. (%s w/ mode %s)", _p, _m);
	/* will never be reached - just to keep gcc happy */	
	return fh;
}

struct t_seenfile *read_seenfile(const char *_file, const int _type) {
	FILE *fh=NULL;
	struct t_seenfile *sf;
	eSTR *line;
	char *tok;

	int i=0;

	if(_file) {
		if(!(fh=fopen(_file, "r")))
			return NULL;
	}


	if(sf=malloc(sizeof(struct t_seenfile))) {
		sf->type=_type; i=-1;
		sf->len=-1;
		sf->cline=-1;
		while(line=getlineSTR(fh)) {
			if(lenSTR(line))
				i++;
			else {
				freeSTR(line);
				continue;
			}

			if(_type==C2C_SEENTYPE_DB) {
				/* parse seen files */
				/* uniqueid fuid timestamp luid timestamp %d:%d,%d:%d,... */

				tok=tokSTR(line, " \t"); /* uniqueid */
				sf->seen[i].id=newSTR(tok);
				tok=tokSTR(NULL, " \t"); /* fuid */
				tok=tokSTR(NULL, " \t"); /* timestamp */ 
				tok=tokSTR(NULL, " \t"); /* luid */
				tok=tokSTR(NULL, " \t"); /* timestamp */
				tok=tokSTR(NULL, " \t"); /* timestamp */
				if(tok) {
					sf->seen[i].seen=newSTR(tok);
				} else
					sf->seen[i].seen=newSTR("");
			} else {
				/* parse seen files */
				/* mailbox timestamp lastuid timestamp %d:%d,%d:%d,... */

				tok=tokSTR(line, " \t"); /* skip mailbox */
				sf->seen[i].id=newSTR(tok);
				tok=tokSTR(NULL, " \t"); /* skip timestamp */
				tok=tokSTR(NULL, " \t"); /* skip lastuid */ 
				tok=tokSTR(NULL, " \t"); /* skip timestamp */
				tok=tokSTR(NULL, " \t"); /* seen */
				if(tok) {
					sf->seen[i].seen=newSTR(tok);
				} else
					sf->seen[i].seen=newSTR("");
			}
			freeSTR(line);
		}
		if(i>=0) {
			sf->len=i+1;
		}
		fclose(fh);
	} else {
		fclose(fh);
		return NULL;
	}
	return sf;
}


int setfolder_seenfile(struct t_seenfile *_sf, const char *_mailbox, 
					const unsigned long _uidvalidity) {

	eSTR *mbox=va_newSTR("user.", _mailbox, eSTR_END);
	char uniqueid[8+8+1];

	int i, idx;
	eSTR *tuplestr, *tup, *astr, *bstr;
	char *tok;

	if(!_sf) {
		freeSTR(mbox);
		return 0;
	}
	if(_sf->type==C2C_SEENTYPE_DB) {

		/* calc uniqueid */
		mailbox_make_uniqueid(strSTR(mbox), _uidvalidity, uniqueid);

		/* find seenline */
		_sf->cline=-1;
		for(i=0; i<_sf->len; i++) {
			if(strcmp(uniqueid, strSTR(_sf->seen[i].id))==0) {
				_sf->cline=i;
			}
		}
	}
	if(_sf->cline>=0) {
		/* loads the seen tuplestr seencache array */
		if(tuplestr=dupSTR(_sf->seen[_sf->cline].seen)) {
			stripSTR(tuplestr, eSTR_ALL, " \t");
			
			tok=tokSTR(tuplestr, ","); /* initialize tok w/ tuplestr */

			i=0;
			while(tok) {
				/* sanity check */
				assert(i<CYRUS_SEENMAX);
				
				tup=newSTR(tok);
				idx=subcSTR(tup, ':');

				if(idx==-1) {
					/* not found */
					astr=dupSTR(tup);
					bstr=dupSTR(tup);
				} else {
					/* : found */
					astr=leftSTR(tup, idx);
					bstr=rightSTR(tup, idx);
				}
				_sf->cache[i].left=atol(astr->str);
				_sf->cache[i].right=atol(bstr->str);

				i++;

				freeSTR(astr);
				freeSTR(bstr);
				freeSTR(tup);

				tok=tokSTR(NULL, ",");	/* next tok from tuplestr */
			}
			freeSTR(tuplestr);
			_sf->clen=i;
		} else {
			_sf->clen=-1;
		}
	}
	freeSTR(mbox);
	return _sf->clen;
}

int isseen_seenfile(struct t_seenfile *_sf, unsigned long _uid) {
	int i;

	if(!_sf)
		return 0;

	if(_sf->cline==-1)
		return 0;

	for(i=0; i<_sf->clen; i++) {
		if(_uid>=_sf->cache[i].left && _uid <=_sf->cache[i].right)
			return 1;
	}
	return 0;
}

void flush_seenfile(struct t_seenfile *_sf) {
	int i;

	if(_sf) {
		for(i=0; i<_sf->len; i++) {
			freeSTR(_sf->seen[i].id);
			freeSTR(_sf->seen[i].seen);
		}
		free(_sf);
	}
}
