1 /* $Id: c2c.c,v 1.59 2006/03/15 10:21:01 mad Exp $ */
2 /*
3  * Copyright (c) 2003, Alexander Marx
4  * All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  *    - Redistributions of source code must retain the above copyright
11  *      notice, this list of conditions and the following disclaimer.
12  *    - Redistributions in binary form must reproduce the above
13  *      copyright notice, this list of conditions and the following
14  *      disclaimer in the documentation and/or other materials provided
15  *      with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  *
30  *
31  * "This product includes software developed by Computing Services * at
32  *  Carnegie Mellon University (http://www.cmu.edu/computing/)."
33  *
34  * (see http://asg.web.cmu.edu/cyrus/imapd/license.html for more info)
35  *
36 */
37 
38 #include "c2c.h"
39 
40 /* ------------------------------------------------------------------------ */
41 
42 char *cyrus_root=NULL, *courier_root=NULL, *mailbox=NULL,
43 	*cyrus_quota_dir=NULL, *cyrus_subscribe_dir=NULL, *cyrus_seen_dir=NULL;
44 
45 short verbose=0, dovecot=0, hashed_subscribe=0, hashed_quota=0, hashed_seen=0;
46 int sflag=0, qflag=0, xflag=0, eflag=0;
47 
48 struct t_seenfile *seen_file=NULL;
49 
50 
51 /* ------------------------------------------------------------------------ */
52 
main(int argc,char ** argv)53 int main(int argc, char **argv) {
54 
55 	int ch;
56 
57 #ifdef HAVE_GETOPT_LONG
58 	static struct option longopts[] = {
59 		{ "verbose",		no_argument,		0,	'v'},
60 		{ "help",		no_argument,		0,	'h'},
61 		{ "quota-dir",  	required_argument,      0,	'q'},
62 		{ "subscribe-dir",  	required_argument,      0,	's'},
63 		{ "seen-dir",  	        required_argument,      0,	'e'},
64 		{ "hashed",  		required_argument,     	0,	'x'},
65  		{ "dovecot",    	no_argument,    	0,	'd'},
66 		{ "Version",    	no_argument,    	0,	'V'},
67 		{ 0,            	0,                      0,	 0 }
68 	};
69 	while((ch=getopt_long(argc, argv, COMMAND_PARAMETERS, longopts, NULL))!=-1) {
70 #else
71 	while((ch=getopt(argc, argv, COMMAND_PARAMETERS))!=-1) {
72 #endif
73 		switch(ch) {
74 			case 'V':
75 				fprintf(stdout, "cyrus2courier - Version %s\n", C2C_VERSION);
76 				exit(C2C_EXIT_SUCCESS);
77 				break;
78 
79 			case 'v':
80 				verbose++;
81 				break;
82 
83 			case 'd':
84 				dovecot=1;
85 				break;
86 
87 			case 'q':
88 				if(verify_path(optarg)) {
89 					/* XXX: convert relative path to absolute */
90 					cyrus_quota_dir = optarg;
91 					qflag=1;
92 				} else
93 					bail_out(C2C_EXIT_FAIL, "main", "Unable to open Quota-Directory. (%s)", optarg);
94 				break;
95 
96 			case 's':
97 				if(verify_path(optarg)) {
98 					/* XXX: convert relative path to absolute */
99 					cyrus_subscribe_dir = optarg;
100 					sflag = 1;
101 				} else
102 					bail_out(C2C_EXIT_FAIL, "main", "Unable to open Subscribe-Directory. (%s)", optarg);
103 				break;
104 
105 			case 'e':
106 				if(verify_path(optarg)) {
107 					/* XXX: convert relative path to absolute */
108 					cyrus_seen_dir = optarg;
109 					eflag = 1;
110 				} else
111 					bail_out(C2C_EXIT_FAIL, "main", "Unable to open Seen-Directory. (%s)", optarg);
112 				break;
113 
114 			case 'x':
115 				xflag=1;
116 				if(strchr(optarg, 'q'))
117 					hashed_quota=1;
118 				if(strchr(optarg, 's'))
119 					hashed_subscribe=1;
120 				if(strchr(optarg, 'e'))
121 					hashed_seen=1;
122 				break;
123 
124 
125 			case '?':
126 			case 'h':
127 			default:
128 				usage();
129 		}
130 	}
131 	argc -= optind;
132 	argv += optind;
133 
134 	/* verify arguments */
135 	if(argc==3) {
136 		if((qflag && sflag) && (strncmp(cyrus_quota_dir, cyrus_subscribe_dir, MAXBUF)==0)) {
137 			bail_out(C2C_EXIT_FAIL, "main", "Quota-Directory and Subscribe-Directory must differ.");
138 		} else if (qflag && cyrus_quota_dir[0]!='/') {
139 			bail_out(C2C_EXIT_FAIL, "main", "Need absolute Quota-Directoy path.");
140 		} else if (sflag && cyrus_subscribe_dir[0]!='/') {
141 			bail_out(C2C_EXIT_FAIL, "main", "Need absolute Subscribe-Directoy path.");
142 		} else if (!verify_path(argv[argc-3])) {
143 			bail_out(C2C_EXIT_FAIL, "main", "Invalid path for Cyrus-Root-Directory.");
144 		} else if (!verify_path(argv[argc-2])) {
145 			bail_out(C2C_EXIT_FAIL, "main", "Invalid path for Courier-Root-Directory.");
146 		} else if (strncmp(argv[argc-3], argv[argc-2], MAXBUF)==0) {
147 			bail_out(C2C_EXIT_FAIL, "main", "Cyrus-Root-Directory and Courier-Root-Directory must differ.");
148 		} else if (strchr(argv[argc-1], '/')!= NULL) {
149 			bail_out(C2C_EXIT_FAIL, "main", "Invalid Mailbox-Name.");
150 		} else if ((strncmp(argv[argc-1], "..", MAXBUF)==0) ||
151 				(strncmp(argv[argc-1], ".", MAXBUF)==0)) {
152 			bail_out(C2C_EXIT_FAIL, "main", "Invalid Mailbox-Name.");
153 		} else if (argv[argc-2][0]!='/' || argv[argc-3][0]!='/') {
154 			bail_out(C2C_EXIT_FAIL, "main", "Need absolute Root-Directory paths.");
155 		} else if (xflag && (!hashed_subscribe && !hashed_quota && !hashed_seen)) {
156 			bail_out(C2C_EXIT_FAIL, "main", "option 'hashed' needs 'q' and/or 's' and/or 'e' as parameter.");
157 		} else {
158 			cyrus_root=argv[argc-3];
159 			courier_root=argv[argc-2];
160 			mailbox=argv[argc-1];
161 			chdir(cyrus_root);
162 			traverse(cyrus_root, courier_root, mailbox);
163 			if(cyrus_seen_dir && seen_file) {
164 				flush_seenfile(seen_file);
165 			}
166 		}
167 	} else
168 		usage();
169 
170 	if(verbose)
171 		verbose_print(C2C_DONE, __FUNCTION__, NULL);
172 	return C2C_EXIT_SUCCESS;
173 }
174 
175 
176 /* ------------------------------------------------------------------------ */
177 
178 void usage(void) {
179 
180 	/* usage() is wrong for all platforms without getopt_long */
181 	fprintf(stdout, "\nUsage: cyrus2courier [options] cyrusdir courierdir mailbox\n");
182 	fprintf(stdout, "\n   -V, --Version .............. print version information");
183 	fprintf(stdout, "\n   -v, --verbose .............. be verbose / repeat for more verbosity");
184 	fprintf(stdout, "\n   -q, --quota-dir=<dir> ...... cyrus quota-files directory");
185 	fprintf(stdout, "\n   -s, --subscribe-dir=<dir> .. cyrus subscribe-files directory");
186 	fprintf(stdout, "\n   -e, --seen-dir=<dir> ....... cyrus seen-files directory");
187 	fprintf(stdout, "\n   -x, --hashed=[s][q][e]...... cyrus hashed (s)ubscribe/(q)uota/s(e)en dirs");
188 	fprintf(stdout, "\n   -d, --dovecot .............. write Dovecot-compatible files\n\n");
189 
190 	exit(C2C_EXIT_SUCCESS);
191 }
192 
193 void bail_out(int rc, char *where, char *msg, ...) {
194 	va_list args;
195 
196 	va_start(args, msg);
197 	switch(rc) {
198 		case C2C_EXIT_SUCCESS:
199 			fprintf(stdout, "DONE\t%s\n", mailbox);
200 			break;
201 
202 		case C2C_EXIT_WARN:
203 			fprintf(stdout, "WARN\t %s", mailbox);
204 			fprintf(stdout, "\t\t(%s) ", where);
205 			vfprintf(stdout, msg, args);
206 			break;
207 
208 		case C2C_EXIT_FAIL:
209 		default:
210 			fprintf(stdout, "FAIL\t %s", mailbox);
211 			fprintf(stdout, "\t\t(%s) ", where);
212 			vfprintf(stdout, msg, args);
213 			break;
214 	}
215 	va_end(args);
216 	fprintf(stderr, "\n");
217 
218 	exit(rc);
219 }
220 
221 void verbose_print(int type, const char *where, const char *format, ...) {
222 	va_list args;
223 	char *stype;
224 	static int last=-1;
225 
226 	switch(type) {
227 		case C2C_EXIT_SUCCESS:
228 			stype="DONE";
229 			break;
230 
231 		case C2C_EXIT_WARN:
232 			stype="WARN";
233 			break;
234 
235 		case C2C_EXIT_INFO:
236 			stype="INFO";
237 			break;
238 
239 
240 		case C2C_EXIT_FAIL:
241 		default:
242 			stype="FAIL";
243 			break;
244 	}
245 
246 	va_start(args, format);
247 	if(type!=last) {
248 		fprintf(stdout, "%s\t %s\n", stype, mailbox);
249 		last=type;
250 	}
251 	if(format) {
252 		fprintf(stdout, "\t(%s) ", where);
253 		vfprintf(stdout, format, args);
254 		fprintf(stdout, "\n");
255 	}
256 	va_end(args);
257 }
258 
259 int verify_path(const char *mypath) {
260 	DIR *dirp=NULL;
261 	int rc=0;
262 
263 	if(dirp=opendir(mypath)) {
264 		closedir(dirp);
265 		rc=1;
266 	}
267 	return rc;
268 }
269 
270 int traverse(char *cy_root, char *co_root, char *dir) {
271 	static char spc[MAXBUF] = "";
272 	static char spd[MAXBUF] = "";
273 	static char spe[MAXBUF] = "";
274 	static int is_rootfolder=1;
275 	DIR  *dirp;
276 	struct dirent *entry;
277 	struct stat buf;
278 	int  filetype;
279 	char name[MAXNAME];
280 	char *cc=NULL;
281 	eSTR *s;
282 	int rc=0;
283 
284 
285 	if(is_rootfolder) {
286 		if(!verify_path(dir)) {
287 			if(verbose) {
288 				verbose_print(C2C_FAIL, __FUNCTION__,
289 					"%s is not a mailbox.", dir);
290 			}
291 			exit(C2C_EXIT_FAIL);
292 
293 		}
294 		if(cyrus_seen_dir) {
295 			eSTR *sf;
296 			if(!hashed_seen) {
297 				sf=va_newSTR(cyrus_seen_dir, "/", mailbox, ".seen", eSTR_END);
298 			} else {
299 				char h[4];
300 				h[0]='/';
301 				h[1]=mailbox[0];
302 				h[2]='/';
303 				h[3]='\0';
304 				sf=va_newSTR(cyrus_seen_dir, h, mailbox, ".seen", eSTR_END);
305 			}
306 			seen_file=read_seenfile(strSTR(sf), C2C_SEENTYPE_DB);
307 			if(!seen_file) {
308 				eflag=-1;
309 				if(verbose)
310 					verbose_print(C2C_WARN, __FUNCTION__,
311 						"%s.seen file not found.", mailbox);
312 			}
313 			freeSTR(sf);
314 		}
315 	}
316 
317 	filetype=((stat(dir, &buf) ? 0 : (buf.st_mode&S_IFMT)==S_IFDIR))
318 				? 'd' : 'f';
319 
320 	if (filetype == 'd') {
321 		/* dirp points to a directory  */
322 
323 		/* building path infos */
324 
325 		if(strlen(spc)!=0) {
326 			strcat(spc, "/");
327 		}
328 
329 		strcat(spc, dir);
330 		/* use . to separate sub-mailboxes */
331 		if(strlen(spd)!=0) {
332 			strcat(spd, ".");
333 		}
334 		strcat(spd, dir);
335 		/* append / to root mailbox */
336 		if (!strcmp(spd, dir)) {
337 		  strcat(spd, "/");
338 		}
339 		spe[0]='\0';
340 		strcat(spe, cy_root);
341 		strcat(spe, "/");
342 		strcat(spe, spc);
343 
344 		s=va_newSTR(co_root, "/", spd, eSTR_END);
345 
346 		/* create courier directory structure */
347 		courier_mkdir(co_root, spd, is_rootfolder);
348 
349 		/* migrate quota-information */
350 		if(cyrus_quota_dir) {
351 			courier_quota(co_root, spd, is_rootfolder);
352 		}
353 
354 		/* migrate folder-subscription info */
355 		if(cyrus_subscribe_dir) {
356 			courier_subscribe(co_root, spd, is_rootfolder);
357 		}
358 		/* copy/migrate the mailfiles */
359 		cydump(spe, strSTR(s),is_rootfolder);
360 
361 		if(is_rootfolder) {
362 			is_rootfolder=0;
363 		}
364 
365 		freeSTR(s);
366 
367 		/* traverse directory tree */
368 		if(chdir(dir)==0) {
369 			if(dirp=opendir(".")) {
370 				while(entry=readdir(dirp)) {
371 					strcpy(name, entry->d_name);
372 					if(strcmp(name, ".") &&
373 							strcmp(name, ".."))
374 						traverse(cy_root, co_root, name);
375 				}
376 				closedir(dirp);
377 			}
378 			if(cc=strrchr(spc, '/')) *cc='\0';
379 			if(cc=strrchr(spd, '.')) *cc='\0';
380 			chdir("..");
381 		}
382 
383 	}
384 	return rc;
385 }
386 
387 int courier_quota(char *root, char *dir, int is_root) {
388 	eSTR *s, *q, *t;
389 	long quota=-1, qused=-1;
390 	FILE *fi, *fo;
391 
392 	/* quotafiles only exist in the mailbox-rootfolder */
393 
394 	if(!is_root)
395 		return 1;
396 
397 	s=va_newSTR(root, "/", dir, "/maildirsize", eSTR_END);
398 	q=va_newSTR(cyrus_quota_dir, "/", eSTR_END);
399 
400 	if(hashed_quota) {
401 		t=newSTR(mailbox);
402 		t->str[1]='\0';
403 		va_catSTR(q, strSTR(t), "/", eSTR_END);
404 		freeSTR(t);
405 	}
406 
407 	va_catSTR(q, "user.", mailbox, eSTR_END);
408 
409 	/*
410 	** read  "$(BYTES USED)\n$(kBYTES MAX)"
411 	** write "$(BYTES MAX)S\n$(BYTES USED)"
412 	**
413 	** XXX: count-quotas are not supported
414 	*/
415 	if(fi=fopen(strSTR(q), "r")) {
416 		if(t=getlineSTR(fi)) {
417 			qused=atol(strSTR(t));
418 			freeSTR(t);
419 		}
420 		if(t=getlineSTR(fi)) {
421 			quota=atol(strSTR(t))*1024;
422 			freeSTR(t);
423 		}
424 		if(fo=xfopen(strSTR(s), "w")) {
425 			char buf[14];
426 
427 			/* courier likes padded quota-files */
428 			sprintf(buf, "%ldS", quota);
429 			fprintf(fo, "%-13s\n", buf);
430 			sprintf(buf, "%ld", qused);
431 			fprintf(fo, "%-13s\n", buf);
432 			fclose(fo);
433 		}
434 		fclose(fi);
435 	} else {
436 		if(verbose)
437 			verbose_print(C2C_WARN, __FUNCTION__, "cyrus quota-file missing");
438 	}
439 	freeSTR(s);
440 	return 1;
441 }
442 
443 int courier_subscribe(char *root, char *dir, int is_root) {
444 	eSTR *s, *q, *t;
445 	int len;
446 	FILE *fi, *fo;
447 
448 	/* subscribefiles only exist in mailbox-rootfolder */
449 
450 	if(!is_root)
451 		return 1;
452 
453 
454 	s=va_newSTR(root, "/", dir, dovecot ? "/.subscriptions" :
455 		    "/courierimapsubscribed", eSTR_END);
456 	q=va_newSTR(cyrus_subscribe_dir, "/", eSTR_END);
457 
458 	if(hashed_subscribe) {
459 		t=newSTR(mailbox);
460 		t->str[1]='\0';
461 		va_catSTR(q, strSTR(t), "/", eSTR_END);
462 		freeSTR(t);
463 	}
464 
465 	va_catSTR(q, mailbox, ".sub", eSTR_END);
466 
467 	len=strlen("user.")+strlen(mailbox)+1;
468 
469 	/*
470 	** readline  "user.$(MAILBOXNAME.folder.folder..."
471 	** writeline "INBOX.folder.folder..."
472 	*/
473 	if(fi=fopen(strSTR(q), "r")) {
474 		if(fo=xfopen(strSTR(s), "w")) {
475 			while(t=getlineSTR(fi)) {
476 				if(lenSTR(t)>0) {
477 					char *p=strSTR(t);
478 					if(strlen(&p[len])!=0) {
479 						char *e=strrchr(&p[len], '\t');
480 						if(e) *e='\0';
481 						if(dovecot)
482 							fprintf(fo, "%s\n", &p[len]);
483 						else
484 							fprintf(fo, "INBOX.%s\n", &p[len]);
485 					}
486 				}
487 				freeSTR(t);
488 			}
489 			fclose(fo);
490 		}
491 		fclose(fi);
492 	} else {
493 		if(verbose>1)
494 			verbose_print(C2C_WARN, __FUNCTION__, "cyrus-subscribe file missing");
495 	}
496 	freeSTR(s);
497 	freeSTR(q);
498 	return 1;
499 }
500 
501 int courier_mkdir(char *root, char *dir, int is_root) {
502 	eSTR *s, *c, *n, *t, *f;
503 
504 	s=va_newSTR(root, "/", dir, eSTR_END);
505 	c=va_newSTR(strSTR(s), "/cur", eSTR_END);
506 	n=va_newSTR(strSTR(s), "/new", eSTR_END);
507 	t=va_newSTR(strSTR(s), "/tmp", eSTR_END);
508 	f=va_newSTR(strSTR(s), "/maildirfolder", eSTR_END);
509 
510 	/* create the necessary folders */
511 	mkdir(strSTR(s), S_IRWXU);
512 	mkdir(strSTR(c), S_IRWXU);
513 	mkdir(strSTR(n), S_IRWXU);
514 	mkdir(strSTR(t), S_IRWXU);
515 
516 	/* foreach subfolder we need to touch a "maildirfolder"
517 	* tells an MTA how to find the correct root-folder for
518 	* quota calculations during deliveries
519 	*/
520 	if(!is_root) {
521 		fclose(xfopen(strSTR(f), "w"));
522 	}
523 
524 	freeSTR(s);
525 	freeSTR(c);
526 	freeSTR(n);
527 	freeSTR(t);
528 	freeSTR(f);
529 
530 	return 1;
531 }
532 
533 int courier_cpmail(char *src, unsigned long size, char *dst_a, char *dst_b, unsigned long *rsize) {
534 	eSTR *m, *d;
535 	char is[32];
536 	struct stat s;
537 	struct timeval tv[2];
538 	int i;
539 
540 	/* read the mailfile */
541 	m=infileSTR(src);
542 
543 	/* strip away the carriage returns */
544 	i=tounixSTR(m);
545 
546 	*rsize=size-i;
547 	sprintf(is, "%lu", size-i);
548 
549 	/* build filename */
550 	d=va_newSTR(dst_a, is, dst_b, eSTR_END);
551 
552 	/* write the stripped file */
553 	outfileSTR(m, d->str);
554 
555 	/* fix file modification time (=imap received time) */
556 	stat(src, &s);
557 	tv[0].tv_sec=s.st_atime;
558 	tv[1].tv_sec=s.st_mtime;
559 	utimes(d->str, tv);
560 
561 
562 	freeSTR(m);
563 	freeSTR(d);
564 
565 	return i;
566 }
567 
568 int cydump(char *path, char *dest, int is_root)
569 {
570 	FILE *fh, *fuid=NULL, *fuidpop3=NULL;
571 	eSTR *s, *i, *r;
572 	int cs_len=0;
573 	char src[MAXBUF]="";
574 	char dst_a[MAXBUF]="";
575 	char dst_b[MAXBUF]="";
576 	struct index_header header;
577 	struct index_entry entry;
578 	unsigned long version, rsize;
579 
580 	r=newSTR(path);
581 	if(verbose>2)
582 		verbose_print(C2C_INFO, __FUNCTION__,
583 				"converting %s", strSTR(r));
584 
585 	i=va_newSTR(strSTR(r), "/cyrus.index", eSTR_END);
586 	s=va_newSTR(strSTR(r), "/cyrus.seen", eSTR_END);
587 
588 	if(fh = fopen(strSTR(i), "r")) {
589 		/* minor_version in header tells us which cyrus-version this is */
590 		memset(&header, 0, sizeof(header));
591 		fread(&header, 3*4, 1, fh);
592 
593 		version = ntohl(header.minor_version);
594 		fseek(fh, 0, SEEK_SET);
595 
596 		switch (version) {
597 			case 2:
598 				fread(&header, 3*4 + 8*4, 1, fh);
599 				break;
600 			case 3:
601 				fread(&header, 3*4 + 8*4 + 3*4, 1, fh);
602 				break;
603 			case 4:
604 				fread(&header, 3*4 + 8*4 + 3*4 + 5*4, 1, fh);
605 				break;
606 			default:
607 				bail_out(C2C_EXIT_FAIL, "cydump",
608 					"Header version %lu not supported", version);
609 		}
610 
611 		sprintf(dst_a, "%s/%s", dest, dovecot ? "dovecot-uidlist" :
612 			"courierimapuiddb");
613 		fuid = xfopen(dst_a, "w");
614 
615 		fprintf(fuid, "1 %lu %lu\n",
616 			(unsigned long)ntohl(header.uidvalidity),
617 			(unsigned long)ntohl(header.last_uid)+1);
618 		/* WARNING this will generate a version v1 pop3dsizelistefile
619 		 * as used by courier-imap 2.0.0 - later versions will use v2
620 		 * which is currently NOT supported! */
621 		if (!dovecot && (is_root)) {
622 			sprintf(dst_a, "%s/%s", dest, "courierpop3dsizelist");
623 			fuidpop3=xfopen(dst_a, "w");
624 			/* it looks like that we want the NEXT uid here so just add 1 */
625 			fprintf(fuidpop3, "/1 %lu\n",
626 				(unsigned long)ntohl(header.last_uid)+1);
627 		}
628 
629 		/* analyze cyrus.seen file */
630 		if(cyrus_seen_dir) {
631 			eSTR *ns=dupSTR(r);
632 			unsigned int cl=strlen(cyrus_root);
633 			char *nns=strSTR(ns)+cl;
634 
635 			for(cl=0; cl<strlen(nns); cl++) {
636 				if(nns[cl]=='/')  {
637 					nns[cl]='.';
638 				}
639 			}
640 			nns++;
641 
642 			setfolder_seenfile(seen_file, nns, (unsigned long)ntohl(header.uidvalidity));
643 
644 			freeSTR(ns);
645 		} else {
646 			seen_file=read_seenfile(strSTR(s), C2C_SEENTYPE_FILES);
647 			if(!seen_file) {
648 				/* no cyrus.seen file found -> keep all as unseen */
649 				if(verbose>1)
650 					verbose_print(C2C_WARN, __FUNCTION__,
651 						"cyrus.seen file missing; keeping all mails as unseen.\n\t\t--> %s",
652 							strSTR(s));
653 			}
654 			setfolder_seenfile(seen_file, NULL, 0);
655 		}
656 
657 
658 		/* foreach entry copy/migrate a mailfile */
659 		while(fread(&entry, sizeof(struct index_entry), 1, fh)) {
660 
661 			sprintf(dst_a, "%s/cur/%lu.%lu,S=",
662 				dest,
663 				(unsigned long)ntohl(entry.internaldate),
664 				(unsigned long)ntohl(entry.uid));
665 
666 			/* maildir-flags must be in ascii sort order! */
667 
668 			dst_b[0]='\0';
669 			strcat(dst_b, ":2,");
670 			if((ntohl(entry.system_flags)&FLAG_DRAFT))
671 				 strcat(dst_b, "D");
672 
673 			if((ntohl(entry.system_flags)&FLAG_FLAGGED))
674 				 strcat(dst_b, "F");
675 
676 			if((ntohl(entry.system_flags)&FLAG_ANSWERED))
677 				 strcat(dst_b, "R");
678 
679 			if(cs_len!=-1) {
680 				if(isseen_seenfile(seen_file, (unsigned long)(ntohl(entry.uid)))) {
681 					 strcat(dst_b, "S");
682 				} else {
683 					 /* XXX: experimental new-folder support for "unseen"-mails */
684 					 /*
685 					 sprintf(dst_a, "%s/new/%lu.%lu,S=",
686 							 dest,
687 							 (unsigned long)ntohl(entry.internaldate),
688 							 (unsigned long)ntohl(entry.uid));
689 					 */
690 				}
691 			}
692 
693 			if((ntohl(entry.system_flags)&FLAG_DELETED))
694 				 strcat(dst_b, "T");
695 
696 			sprintf(src, "%s/%lu.", path,
697 				(unsigned long)ntohl(entry.uid));
698 
699 			/* copy/migrate the mailfile */
700 			courier_cpmail(src, (unsigned long)ntohl(entry.size), dst_a, dst_b, &rsize);
701 
702 			/* build courierpop3dsizelistfile */
703 			if (!dovecot && (is_root)) {
704 				fprintf(fuidpop3,"%s%lu%s %lu %lu\n",
705 					strrchr(dst_a, '/')+1, rsize, dst_b,
706 					(unsigned long)ntohl(entry.size),
707 					(unsigned long)ntohl(entry.uid));
708 			}
709 
710 			/* filenames in uiddb-files are w/o flags, dunno why. */
711 			fprintf(fuid, "%lu %s%lu\n",
712 				(unsigned long)ntohl(entry.uid),
713 				strrchr(dst_a, '/')+1, rsize);
714 
715 
716 		}
717 
718 		if(!cyrus_seen_dir) {
719 			/* release seen_file resources */
720 			flush_seenfile(seen_file);
721 			seen_file=NULL;
722 		}
723 
724 		fclose(fh);
725 		fclose(fuid);
726 		if (!dovecot && (is_root))
727 			fclose(fuidpop3);
728 	} else {
729 		/* no index file?! .. */
730 
731 		/* checking whether folder contains mails or not */
732 		DIR *dirp;
733 		struct dirent *dp;
734 		int is_empty=1;
735 
736 		if(dirp=opendir(strSTR(r))) {
737 			while ((dp=readdir(dirp))) {
738 				if(dp->d_name[strlen(dp->d_name)-1] == '.' && dp->d_name[0] != '.') {
739 					/* found some mail-files! */
740 					is_empty=0;
741 				}
742 			}
743 			closedir(dirp);
744 		}
745 
746 		if(is_empty) {
747 			if(verbose>1) {
748 				verbose_print(C2C_WARN, __FUNCTION__,
749 					"cyrus.index file missing; folder contains no mails; skipping\n\t\t-->%s",
750 					strSTR(r));
751 			}
752 		} else if (verbose) {
753 			verbose_print(C2C_WARN, __FUNCTION__,
754 				"cyrus.index file missing; folder contains files(possibly mails!); FIX MANUALLY\n\t\t==>%s",
755 				strSTR(r));
756 		}
757 	}
758 	freeSTR(s);
759 	freeSTR(i);
760 	freeSTR(r);
761 
762 	return 0;
763 }
764 
765 FILE *xfopen(const char *_p, const char *_m) {
766 	FILE *fh=NULL;
767 
768 	if(_p && _m) {
769 		if(fh=fopen(_p, _m))
770 			return fh;
771 	}
772 	bail_out(C2C_EXIT_FAIL, "xfopen",
773 		"Unable to open file. (%s w/ mode %s)", _p, _m);
774 	/* will never be reached - just to keep gcc happy */
775 	return fh;
776 }
777 
778 struct t_seenfile *read_seenfile(const char *_file, const int _type) {
779 	FILE *fh=NULL;
780 	struct t_seenfile *sf;
781 	eSTR *line;
782 	char *tok;
783 
784 	int i=0;
785 
786 	if(_file) {
787 		if(!(fh=fopen(_file, "r")))
788 			return NULL;
789 	}
790 
791 
792 	if(sf=malloc(sizeof(struct t_seenfile))) {
793 		sf->type=_type; i=-1;
794 		sf->len=-1;
795 		sf->cline=-1;
796 		while(line=getlineSTR(fh)) {
797 			if(lenSTR(line))
798 				i++;
799 			else {
800 				freeSTR(line);
801 				continue;
802 			}
803 
804 			if(_type==C2C_SEENTYPE_DB) {
805 				/* parse seen files */
806 				/* uniqueid fuid timestamp luid timestamp %d:%d,%d:%d,... */
807 
808 				tok=tokSTR(line, " \t"); /* uniqueid */
809 				sf->seen[i].id=newSTR(tok);
810 				tok=tokSTR(NULL, " \t"); /* fuid */
811 				tok=tokSTR(NULL, " \t"); /* timestamp */
812 				tok=tokSTR(NULL, " \t"); /* luid */
813 				tok=tokSTR(NULL, " \t"); /* timestamp */
814 				tok=tokSTR(NULL, " \t"); /* timestamp */
815 				if(tok) {
816 					sf->seen[i].seen=newSTR(tok);
817 				} else
818 					sf->seen[i].seen=newSTR("");
819 			} else {
820 				/* parse seen files */
821 				/* mailbox timestamp lastuid timestamp %d:%d,%d:%d,... */
822 
823 				tok=tokSTR(line, " \t"); /* skip mailbox */
824 				sf->seen[i].id=newSTR(tok);
825 				tok=tokSTR(NULL, " \t"); /* skip timestamp */
826 				tok=tokSTR(NULL, " \t"); /* skip lastuid */
827 				tok=tokSTR(NULL, " \t"); /* skip timestamp */
828 				tok=tokSTR(NULL, " \t"); /* seen */
829 				if(tok) {
830 					sf->seen[i].seen=newSTR(tok);
831 				} else
832 					sf->seen[i].seen=newSTR("");
833 			}
834 			freeSTR(line);
835 		}
836 		if(i>=0) {
837 			sf->len=i+1;
838 		}
839 		fclose(fh);
840 	} else {
841 		fclose(fh);
842 		return NULL;
843 	}
844 	return sf;
845 }
846 
847 
848 int setfolder_seenfile(struct t_seenfile *_sf, const char *_mailbox,
849 					const unsigned long _uidvalidity) {
850 
851 	eSTR *mbox=va_newSTR("user.", _mailbox, eSTR_END);
852 	char uniqueid[8+8+1];
853 
854 	int i, idx;
855 	eSTR *tuplestr, *tup, *astr, *bstr;
856 	char *tok;
857 
858 	if(!_sf) {
859 		freeSTR(mbox);
860 		return 0;
861 	}
862 	if(_sf->type==C2C_SEENTYPE_DB) {
863 
864 		/* calc uniqueid */
865 		mailbox_make_uniqueid(strSTR(mbox), _uidvalidity, uniqueid);
866 
867 		/* find seenline */
868 		_sf->cline=-1;
869 		for(i=0; i<_sf->len; i++) {
870 			if(strcmp(uniqueid, strSTR(_sf->seen[i].id))==0) {
871 				_sf->cline=i;
872 			}
873 		}
874 	} else
875 		_sf->cline=0;
876 
877 	if(_sf->cline>=0 && _sf->len!=-1) {
878 		/* loads the seen tuplestr seencache array */
879 		if(tuplestr=dupSTR(_sf->seen[_sf->cline].seen)) {
880 			stripSTR(tuplestr, eSTR_ALL, " \t");
881 
882 			tok=tokSTR(tuplestr, ","); /* initialize tok w/ tuplestr */
883 
884 			i=0;
885 			while(tok) {
886 				/* sanity check */
887 				assert(i<CYRUS_SEENMAX);
888 
889 				tup=newSTR(tok);
890 				idx=subcSTR(tup, ':');
891 
892 				if(idx==-1) {
893 					/* not found */
894 					astr=dupSTR(tup);
895 					bstr=dupSTR(tup);
896 				} else {
897 					/* : found */
898 					astr=leftSTR(tup, idx);
899 					bstr=rightSTR(tup, idx);
900 				}
901 				_sf->cache[i].left=atol(astr->str);
902 				_sf->cache[i].right=atol(bstr->str);
903 
904 				i++;
905 
906 				freeSTR(astr);
907 				freeSTR(bstr);
908 				freeSTR(tup);
909 
910 				tok=tokSTR(NULL, ",");	/* next tok from tuplestr */
911 			}
912 			freeSTR(tuplestr);
913 			_sf->clen=i;
914 		} else {
915 			_sf->clen=-1;
916 		}
917 	}
918 	freeSTR(mbox);
919 	return _sf->clen;
920 }
921 
922 int isseen_seenfile(struct t_seenfile *_sf, unsigned long _uid) {
923 	int i;
924 
925 	if(!_sf)
926 		return 0;
927 
928 	if(_sf->cline==-1)
929 		return 0;
930 
931 	for(i=0; i<_sf->clen; i++) {
932 		if(_uid>=_sf->cache[i].left && _uid <=_sf->cache[i].right)
933 			return 1;
934 	}
935 	return 0;
936 }
937 
938 void flush_seenfile(struct t_seenfile *_sf) {
939 	int i;
940 
941 	if(_sf) {
942 		for(i=0; i<_sf->len; i++) {
943 			freeSTR(_sf->seen[i].id);
944 			freeSTR(_sf->seen[i].seen);
945 		}
946 		free(_sf);
947 	}
948 }
949