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