1 /*
2  * Copyright 2016-2018 Eduardo Chappa
3  */
4 
5 /* ========================================================================
6  * Copyright 2009 Mark Crispin
7  * ========================================================================
8  */
9 
10 /*
11  * Program:	Mail utility
12  *
13  * Author:	Mark Crispin
14  *
15  * Date:	2 February 1994
16  * Last Edited:	14 May 2009
17  *
18  * Previous versions of this file were
19  *
20  * Copyright 1988-2008 University of Washington
21  *
22  * Licensed under the Apache License, Version 2.0 (the "License");
23  * you may not use this file except in compliance with the License.
24  * You may obtain a copy of the License at
25  *
26  *     http://www.apache.org/licenses/LICENSE-2.0
27  *
28  */
29 
30 
31 #include <stdio.h>
32 #include <errno.h>
33 extern int errno;		/* just in case */
34 #include "c-client.h"
35 #ifdef SYSCONFIG		/* defined in env_unix.h */
36 #include <pwd.h>
37 #endif
38 
39 /* Globals */
40 
41 char *version = "17";		/* edit number */
42 int debugp = NIL;		/* flag saying debug */
43 int verbosep = NIL;		/* flag saying verbose */
44 int rwcopyp = NIL;		/* flag saying readwrite copy (for POP) */
45 int kwcopyp = NIL;		/* flag saying keyword copy */
46 int ignorep = NIL;		/* flag saying ignore keywords */
47 int critical = NIL;		/* flag saying in critical code */
48 int trycreate = NIL;		/* [TRYCREATE] seen */
49 char *suffix = NIL;		/* suffer merge mode suffix text */
50 int ddelim = -1;		/* destination delimiter */
51 FILE *f = NIL;
52 
53 /* Usage strings */
54 
55 char *usage2 = "usage: %s %s\n\n%s\n";
56 char *usage3 = "usage: %s %s %s\n\n%s\n";
57 char *usgchk = "check [MAILBOX]";
58 char *usgcre = "create MAILBOX";
59 char *usgdel = "delete MAILBOX";
60 char *usgren = "rename SOURCE DESTINATION";
61 char *usgdup = "dedup [MAILBOX]";
62 char *usgcpymov = "[-rw[copy]] [-kw[copy]] [-ig[nore]] SOURCE DESTINATION";
63 char *usgappdel = "[-rw[copy]] [-kw[copy]] [-ig[nore]] SOURCE DESTINATION";
64 char *usgprn = "prune mailbox SEARCH_CRITERIA";
65 char *usgxfr = "transfer [-rw[copy]] [-kw[copy]] [-ig[nore]] [-m[erge] m] SOURCE DEST";
66 #ifdef SYSCONFIG
67 char *stdsw = "Standard switches valid with any command:\n\t[-d[ebug]] [-v[erbose]] [-u[ser] userid] [--]";
68 #else
69 char *stdsw = "Standard switches valid with any command:\n\t[-d[ebug]] [-v[erbose]]";
70 #endif
71 
72 /* Merge modes */
73 
74 #define mPROMPT 1
75 #define mAPPEND 2
76 #define mSUFFIX 3
77 
78 
79 /* Function prototypes */
80 void mailutil_add_sequence(char **sequence, size_t *len, unsigned long i, unsigned long j, unsigned long nmsgs);
81 char *mailutil_string_sequence(MAILSTREAM *m);
82 int mailutil_compare_message_id (const void *mcp1, const void *mcp2);
83 int mailutil_dedup (char *mailbox, long options);
84 void ms_init (STRING *s,void *data,unsigned long size);
85 char ms_next (STRING *s);
86 void ms_setpos (STRING *s,unsigned long i);
87 int main (int argc,char *argv[]);
88 SEARCHPGM *prune_criteria (char *criteria);
89 int criteria_number (unsigned long *number,char **r);
90 int mbxcopy (MAILSTREAM *source,MAILSTREAM *dest,char *dst,int create,int del,
91 	     int mode);
92 long mm_append (MAILSTREAM *stream,void *data,char **flags,char **date,
93 		STRING **message);
94 
95 
96 /* Append package */
97 
98 typedef struct append_package {
99   MAILSTREAM *stream;		/* source stream */
100   unsigned long msgno;		/* current message number */
101   unsigned long msgmax;		/* maximum message number */
102   char *flags;			/* current flags */
103   char *date;			/* message internal date */
104   STRING *message;		/* stringstruct of message */
105 } APPENDPACKAGE;
106 
107 
108 /* Message string driver for message stringstructs */
109 
110 STRINGDRIVER mstring = {
111   ms_init,			/* initialize string structure */
112   ms_next,			/* get next byte in string structure */
113   ms_setpos			/* set position in string structure */
114 };
115 
116 /* Initialize file string structure for file stringstruct
117  * Accepts: string structure
118  *	    pointer to message data structure
119  *	    size of string
120  */
121 
ms_init(STRING * s,void * data,unsigned long size)122 void ms_init (STRING *s,void *data,unsigned long size)
123 {
124   APPENDPACKAGE *md = (APPENDPACKAGE *) data;
125   s->data = data;		/* note stream/msgno and header length */
126   mail_fetch_header (md->stream,md->msgno,NIL,NIL,&s->data1,
127 		     FT_PREFETCHTEXT|FT_PEEK);
128 #if 0
129   s->size = size;		/* message size */
130 #else	/* This kludge is necessary because of broken IMAP servers (sigh!) */
131   mail_fetch_text (md->stream,md->msgno,NIL,&s->size,FT_PEEK);
132   s->size += s->data1;		/* header + body size */
133 #endif
134   SETPOS (s,0);
135 }
136 
137 
138 /* Get next character from file stringstruct
139  * Accepts: string structure
140  * Returns: character, string structure chunk refreshed
141  */
142 
ms_next(STRING * s)143 char ms_next (STRING *s)
144 {
145   char c = *s->curpos++;	/* get next byte */
146   SETPOS (s,GETPOS (s));	/* move to next chunk */
147   return c;			/* return the byte */
148 }
149 
150 
151 /* Set string pointer position for file stringstruct
152  * Accepts: string structure
153  *	    new position
154  */
155 
ms_setpos(STRING * s,unsigned long i)156 void ms_setpos (STRING *s,unsigned long i)
157 {
158   APPENDPACKAGE *md = (APPENDPACKAGE *) s->data;
159   if (i < s->data1) {		/* want header? */
160     s->chunk = mail_fetch_header (md->stream,md->msgno,NIL,NIL,NIL,FT_PEEK);
161     s->chunksize = s->data1;	/* header length */
162     s->offset = 0;		/* offset is start of message */
163   }
164   else if (i < s->size) {	/* want body */
165     s->chunk = mail_fetch_text (md->stream,md->msgno,NIL,NIL,FT_PEEK);
166     s->chunksize = s->size - s->data1;
167     s->offset = s->data1;	/* offset is end of header */
168   }
169   else {			/* off end of message */
170     s->chunk = NIL;		/* make sure that we crack on this then */
171     s->chunksize = 1;		/* make sure SNX cracks the right way... */
172     s->offset = i;
173   }
174 				/* initial position and size */
175   s->curpos = s->chunk + (i -= s->offset);
176   s->cursize = s->chunksize - i;
177 }
178 
179 /* Main program */
180 
main(int argc,char * argv[])181 int main (int argc,char *argv[])
182 {
183   MAILSTREAM *source = NIL;
184   MAILSTREAM *dest = NIL;
185   SEARCHPGM *criteria;
186   char c,*s,*dp,*t,*t1,tmp[MAILTMPLEN],mbx[MAILTMPLEN];
187   unsigned long m,len,curlen,start,last;
188   int i;
189   int merge = NIL;
190   int retcode = 1;
191   int moreswitchp = T;
192   char *cmd = NIL;
193   char *src = NIL;
194   char *dst = NIL;
195   char *pgm = argc ? argv[0] : "mailutil";
196 #include "linkage.c"
197   for (i = 1; i < argc; i++) {
198     s = argv[i];		/* pick up argument */
199 				/* parse switches */
200     if (moreswitchp && (*s == '-')) {
201       if (!strcmp (s,"-debug") || !strcmp (s,"-d")) debugp = T;
202       else if (!strcmp (s,"-verbose") || !strcmp (s,"-v")) verbosep = T;
203       else if (!strcmp (s,"-rwcopy") || !strcmp (s,"-rw")) rwcopyp = T;
204       else if (!strcmp (s,"-kwcopy") || !strcmp (s,"-kw")) kwcopyp = T;
205       else if (!strcmp (s,"-ignore") || !strcmp (s,"-ig")) ignorep = T;
206       else if ((!strcmp (s,"-merge") || !strcmp (s,"-m")) && (++i < argc)) {
207 	if (!strcmp (s = argv[i],"prompt")) merge = mPROMPT;
208 	else if (!strcmp (s,"append")) merge = mAPPEND;
209 	else if (!strncmp (s,"suffix=",7) && s[7]) {
210 	  merge = mSUFFIX;
211 	  suffix = cpystr (s+7);
212 	}
213 	else {
214 	  printf ("unknown merge option: %s\n",s);
215 	  exit (retcode);
216 	}
217       }
218 
219 #ifdef SYSCONFIG
220       else if ((!strcmp (s,"-user") || !strcmp (s,"-u")) && (++i < argc)) {
221 	struct passwd *pw = getpwnam (s = argv[i]);
222 	if (!pw) {
223 	  printf ("unknown user id: %s\n",argv[i]);
224 	  exit (retcode);
225 	}
226 	else if (setuid (pw->pw_uid)) {
227 	  perror ("unable to change user id");
228 	  exit (retcode);
229 	}
230 				/* become victim in environment */
231 	env_init (argv[1], pw->pw_dir);
232 				/* cancel restrictions since root call */
233 	mail_parameters (NIL,SET_RESTRICTIONS,NIL);
234       }
235 #endif
236 				/* -- means no more switches, so mailbox
237 				   name can start with "-" */
238       else if ((s[1] == '-') && !s[2]) moreswitchp = NIL;
239       else {
240 	printf ("unknown switch: %s\n",s);
241 	exit (retcode);
242       }
243     }
244     else if (!cmd) cmd = s;	/* first non-switch is command */
245     else if (!src) src = s;	/* second non-switch is source */
246     else if (!dst) dst = s;	/* third non-switch is destination */
247     else {
248       printf ("unknown argument: %s\n",s);
249       exit (retcode);
250     }
251   }
252   if (kwcopyp && ignorep) {
253     puts ("-kwcopy and -ignore are mutually exclusive");
254     exit (retcode);
255   }
256   if (!cmd) cmd = "";		/* prevent SEGV */
257 
258   if(!strcmp(cmd, "dedup")){
259     if (!src) src = "INBOX";
260     if (dst || merge || rwcopyp || kwcopyp || ignorep)
261       fprintf (stdout, "%s", usgdup);
262     else if(mailutil_dedup(src, debugp ? OP_DEBUG : NIL))
263       retcode = 0;
264   }
265   else if (!strcmp (cmd,"check")) {	/* check for new messages */
266     if (!src) src = "INBOX";
267     if (dst || merge || rwcopyp || kwcopyp || ignorep)
268       printf (usage2,pgm,usgchk,stdsw);
269     else if (mail_status (source = (*src == '{') ?
270 			  mail_open (NIL,src,OP_HALFOPEN |
271 				     (debugp ? OP_DEBUG : NIL)) : NIL,
272 			  src,SA_MESSAGES | SA_RECENT | SA_UNSEEN))
273       retcode = 0;
274   }
275   else if (!strcmp (cmd,"create")) {
276     if (!src || dst || merge || rwcopyp || kwcopyp || ignorep)
277       printf (usage2,pgm,usgcre,stdsw);
278     else if (mail_create (source = (*src == '{') ?
279 			  mail_open (NIL,src,OP_HALFOPEN |
280 				     (debugp ? OP_DEBUG : NIL)) : NIL,src))
281       retcode = 0;
282   }
283   else if (!strcmp (cmd,"delete")) {
284     if (!src || dst || merge || rwcopyp || kwcopyp || ignorep)
285       printf (usage2,pgm,usgdel,stdsw);
286     else if (mail_delete (source = (*src == '{') ?
287 			  mail_open (NIL,src,OP_HALFOPEN |
288 				     (debugp ? OP_DEBUG : NIL)) : NIL,src))
289       retcode = 0;
290   }
291   else if (!strcmp (cmd,"rename")) {
292     if (!src || !dst || merge || rwcopyp || kwcopyp || ignorep)
293       printf (usage2,pgm,usgren,stdsw);
294     else if (mail_rename (source = (*src == '{') ?
295 			  mail_open (NIL,src,OP_HALFOPEN |
296 				     (debugp ? OP_DEBUG : NIL)) : NIL,src,dst))
297       retcode = 0;
298   }
299 
300   else if ((i = !strcmp (cmd,"move")) || !strcmp (cmd,"copy")) {
301     if (!src || !dst || merge) printf (usage3,pgm,cmd,usgcpymov,stdsw);
302     else if ((source = mail_open (NIL,src,((i || rwcopyp) ? NIL : OP_READONLY) |
303 				 (debugp ? OP_DEBUG : NIL))) != NULL) {
304       dest = NIL;		/* open destination stream if network */
305       if ((*dst != '{') || (dest = mail_open (NIL,dst,OP_HALFOPEN |
306 					      (debugp ? OP_DEBUG : NIL)))) {
307 	if (mbxcopy (source,dest,dst,T,i,merge)) retcode = 0;
308       }
309     }
310   }
311   else if ((i = !strcmp (cmd,"appenddelete")) || !strcmp (cmd,"append")) {
312     if (!src || !dst || merge) printf (usage3,pgm,cmd,usgappdel,stdsw);
313     else if ((source = mail_open (NIL,src,((i || rwcopyp) ? NIL : OP_READONLY) |
314 				 (debugp ? OP_DEBUG : NIL))) != NULL) {
315       dest = NIL;		/* open destination stream if network */
316       if ((*dst != '{') || (dest = mail_open (NIL,dst,OP_HALFOPEN |
317 					      (debugp ? OP_DEBUG : NIL)))) {
318 	if (mbxcopy (source,dest,dst,NIL,i,merge)) retcode = 0;
319       }
320     }
321   }
322 
323   else if (!strcmp (cmd,"prune")) {
324     if (!src || !dst || merge || rwcopyp || kwcopyp || ignorep ||
325 	!(criteria = prune_criteria (dst))) printf (usage2,pgm,usgprn,stdsw);
326     else if ((source = mail_open (NIL,src,(debugp ? OP_DEBUG : NIL))) &&
327 	     mail_search_full (source,NIL,criteria,SE_FREE)) {
328       for (m = 1, s = t = NIL, len = start = last = 0; m <= source->nmsgs; m++)
329 	if (mail_elt (source,m)->searched) {
330 	  if (s) {		/* continuing a range? */
331 	    if (m == last + 1) last = m;
332 	    else {		/* no, end of previous range? */
333 	      if (last != start) sprintf (t,":%lu,%lu",last,m);
334 				/* no, just this message */
335 	      else sprintf (t,",%lu",m);
336 	      start = last = m;	/* either way, start new range */
337 				/* running out of space? */
338 	      if ((len - (curlen = (t += strlen (t)) - s)) < 20) {
339 		fs_resize ((void **) &s,len += MAILTMPLEN);
340 		t = s + curlen;	/* relocate current pointer */
341 	      }
342 	    }
343 	  }
344 	  else {		/* first time, start new buffer */
345 	    s = (char *) fs_get (len = MAILTMPLEN);
346 	    sprintf (s,"%lu",start = last = m);
347 	    t = s + strlen (s);	/* end of buffer */
348 	  }
349 	}
350 				/* finish last range if necessary */
351       if (last != start) sprintf (t,":%lu",last);
352       if (s) {			/* delete/expunge any matching messages */
353 	mail_flag (source,s,"\\Deleted",ST_SET);
354 	m = source->nmsgs;	/* get number of messages before purge */
355 	mail_expunge (source);
356 	printf ("%lu message(s) purged\n",m - source->nmsgs);
357 	fs_give ((void **) &s);	/* flush buffer */
358       }
359       else puts ("No matching messages, so nothing purged");
360       source = mail_close (source);
361     }
362   }
363 
364   else if (!strcmp (cmd,"transfer")) {
365     if (!src || !dst) printf (usage2,pgm,usgxfr,stdsw);
366     else if ((*src == '{') &&	/* open source mailbox */
367 	     !(source = mail_open (NIL,src,OP_HALFOPEN |
368 				   (debugp ? OP_DEBUG : NIL))));
369     else if ((*dst == '{') &&	/* open destination server */
370 	     !(dest = mail_open (NIL,dst,OP_HALFOPEN |
371 				 (debugp ? OP_DEBUG : NIL))));
372     else if (!(f = tmpfile ())) puts ("can't open temporary file");
373     else {
374       if (verbosep) puts ("Listing mailboxes...");
375       if (dest) strcpy (strchr (strcpy (tmp,dest->mailbox),'}') + 1,
376 			dp = strchr (dst,'}') + 1);
377       else {
378 	dp = dst;
379 	tmp[0] = '\0';
380       }
381       mail_list (dest,tmp,"");
382       rewind (f);		/* list all mailboxes matching prefix */
383       if (ddelim < 0) {		/* if server failed to give delimiter */
384 	puts ("warning: unable to get destination hierarchy delimiter!");
385 	ddelim = 0;		/* default to none */
386       }
387       if (source) strcpy (strchr (strcpy (tmp,source->mailbox),'}') + 1,
388 			  strchr (src,'}') + 1);
389       else strcpy (tmp,src);
390       mail_list (source,tmp,"*");
391       rewind (f);
392 				/* read back mailbox names */
393       for (retcode = 0; !retcode && (fgets (tmp,MAILTMPLEN-1,f)); ) {
394 	if ((t = strchr (tmp+1,'\n')) != NULL) *t = '\0';
395 	for (t = mbx,t1 = dest ? dest->mailbox : "",c = NIL; (c != '}') && *t1;
396 	     *t++ = c= *t1++);
397 	for (t1 = dp; *t1; *t++ = *t1++);
398 				/* point to name without delim or netspec */
399 	t1 = source ? (strchr (tmp+1,'}') + 1) : tmp + 1;
400 				/* src and mbx have different delimiters? */
401 	if (ddelim && (ddelim != tmp[0]))
402 	  while ((c = *t1++) != '\0') {	/* swap delimiters then */
403 	    if (c == ddelim) c = tmp[0] ? tmp[0] : 'x';
404 	    else if (c == tmp[0]) c = ddelim;
405 	    *t++ = c;
406 	  }
407 				/* easy case */
408 	else while (*t1) *t++ = *t1++;
409 	*t++ = '\0';
410 	if (verbosep) {
411 	  printf ("Copying %s\n  => %s\n",tmp+1,mbx);
412 	  fflush (stdout);
413 	}
414 	if ((source = mail_open (source,tmp+1,(debugp ? OP_DEBUG : NIL) |
415 				(rwcopyp ? NIL : OP_READONLY))) != NULL) {
416 	  if (!mbxcopy (source,dest,mbx,T,NIL,merge)) retcode = 1;
417 	  if (source->dtb->flags & DR_LOCAL) source = mail_close (source);
418 	}
419 	else printf ("can't open source mailbox %s\n",tmp+1);
420       }
421     }
422   }
423 
424   else {
425     printf ("%s version %s.%s\n\n",pgm,CCLIENTVERSION,version);
426     printf (usage2,pgm,"command [switches] arguments",stdsw);
427     printf ("\nCommands:\n %s\n",usgchk);
428     puts   ("   ;; report number of messages and new messages");
429     printf (" %s\n",usgdup);
430     puts   ("   ;; removes duplicate messages from a mailbox");
431     printf (" %s\n",usgcre);
432     puts   ("   ;; create new mailbox");
433     printf (" %s\n",usgdel);
434     puts   ("   ;; delete existing mailbox");
435     printf (" %s\n",usgren);
436     puts   ("   ;; rename mailbox to a new name");
437     printf (" copy %s\n",usgcpymov);
438     printf (" move %s\n",usgcpymov);
439     puts   ("   ;; create new mailbox and copy/move messages");
440     printf (" append %s\n",usgappdel);
441     printf (" appenddelete %s\n",usgappdel);
442     puts   ("   ;; copy/move messages to existing mailbox");
443     printf (" %s\n",usgprn);
444     puts   ("   ;; prune mailbox of messages matching criteria");
445     printf (" %s\n",usgxfr);
446     puts   ("   ;; copy source hierarchy to destination");
447     puts   ("   ;;  -merge modes are prompt, append, or suffix=xxxx");
448   }
449 				/* close streams */
450   if (source) mail_close (source);
451   if (dest) mail_close (dest);
452   exit (retcode);
453   return retcode;		/* stupid compilers */
454 }
455 
mailutil_string_sequence(MAILSTREAM * m)456 char *mailutil_string_sequence(MAILSTREAM *m)
457 {
458   char *rv = NULL;
459   unsigned long i, j;
460   size_t len = 0;
461 
462 
463   if(m == NULL || m->nmsgs == 0L) return NULL;
464   for(i = 1L; i <= m->nmsgs;){
465      if(mail_elt(m, i)->sequence){
466 	for(j = i+1; j <= m->nmsgs && mail_elt(m, j)->sequence; j++)
467 	   ;
468 	mailutil_add_sequence(&rv, &len, i, j-1, m->nmsgs);
469 	i = j;
470      }
471      else i++;
472   }
473   return rv;
474 }
475 
mailutil_add_sequence(char ** sequence,size_t * len,unsigned long i,unsigned long j,unsigned long nmsgs)476 void mailutil_add_sequence(char **sequence, size_t *len, unsigned long i, unsigned long j, unsigned long nmsgs)
477 {
478 #define SEQ_BUF_LEN 256
479   char tmp[MAILTMPLEN];
480   size_t needed;
481 
482   if(sequence == NULL)
483     return;
484 
485   if(i == j)
486     sprintf(tmp, "%s%lu", *len == 0L ? "" : ",", i);
487   else if(j == nmsgs)
488     sprintf(tmp, "%s%lu:*", *len == 0L ? "" : ",", i);
489   else
490     sprintf(tmp, "%s%lu:%lu", *len == 0L ? "" : ",", i, j);
491   tmp[sizeof(tmp)-1]='\0';
492 
493   needed = strlen(*sequence ? *sequence : "") + strlen(tmp) + 1;
494   if(needed > *len){
495      fs_resize((void **) sequence, (needed + SEQ_BUF_LEN)*sizeof(char));
496      if(*len == 0L)
497         (*sequence)[0] = '\0';
498      *len = needed + SEQ_BUF_LEN;
499   }
500   strcat(*sequence + strlen(*sequence), tmp);
501 }
502 
503 
mailutil_dedup(char * mailbox,long options)504 int mailutil_dedup (char *mailbox, long options)
505 {
506   int rv = 0;
507   MAILSTREAM *m;
508 
509   if((m = mail_open(NIL, mailbox, options)) == NULL)
510     rv = -1;
511   else if(m->nmsgs > 1L){
512     unsigned long i, count = 0L;
513     char *sequence;
514     MESSAGECACHE **mc;
515     ENVELOPE *env1, *env2;
516 
517     if(verbosep)
518       fprintf(stdout, "Opened folder \"%s\" with %lu messages\n", mailbox, m->nmsgs);
519     mail_fetch_overview_sequence(m, "1:*", NIL);
520     for(i = 1L; i <= m->nmsgs; i++)
521        mail_elt(m, i)->sequence = 0;
522     mc = fs_get(m->nmsgs*sizeof(MESSAGECACHE *));
523     for(i = 1L; i <= m->nmsgs; i++)
524       mc[i-1] = mail_elt(m, i);
525     qsort((void *)mc, (size_t) m->nmsgs, sizeof(MESSAGECACHE *), mailutil_compare_message_id);
526     for(i = 1L; i < m->nmsgs;){
527  	unsigned long j, k;
528 	int done;
529 
530 	done = 0;
531 	env1 = mail_fetch_structure(m, mc[i-1]->msgno, NIL, NIL);
532 	if(env1->message_id == NULL){
533 	  i++;
534 	  continue;
535 	}
536 	for(j = i+1; j <= m->nmsgs; j++){
537 	   env2 = mail_fetch_structure(m, mc[j-1]->msgno, NIL, NIL);
538 	   if(env2->message_id == NULL){
539 	      j--; break;
540 	   }
541 	   if(strcmp(env1->message_id, env2->message_id))
542 	      break;
543 	   else if(verbosep)
544 	     fprintf(stdout, "Message %lu and %lu are duplicates\n",
545 				mc[i-1]->msgno, mc[j-1]->msgno);
546 	}
547 
548 	for(k = i+1; k <= j - 1; k++){
549 	   mc[k-1]->sequence = T;
550 	   count++;
551 	}
552 	i = j;
553     }
554     if(verbosep)
555       fprintf(stdout, "Found %lu extra duplicate messages\n", count);
556     if((sequence = mailutil_string_sequence(m)) != NULL){
557       mail_flag(m, sequence, "\\DELETED", ST_SET);
558       if(count && mail_expunge_full(m, "1:*", NIL))
559         fprintf (stdout, "Expunged %lu duplicate messages\n", count);
560       else
561         rv = -1;
562       fs_give((void *)&sequence);
563     }
564     else rv = -1;
565   }
566   return rv;
567 }
568 
mailutil_compare_message_id(const void * mcp1,const void * mcp2)569 int mailutil_compare_message_id (const void *mcp1, const void *mcp2)
570 {
571   MESSAGECACHE *mc1, *mc2;
572   ENVELOPE *env1, *env2;
573 
574   mc1 = *(MESSAGECACHE **) mcp1;
575   mc2 = *(MESSAGECACHE **) mcp2;
576 
577   env1 = mc1->private.msg.env;	/* aarrggh direct access inside message cache */
578   env2 = mc2->private.msg.env;
579 
580   if(env1 == NULL)
581     return env2 == NULL ? 0 : -1;
582   else if (env2 == NULL)
583     return 1;
584   else if(env1->message_id == NULL)
585     return env2->message_id == NULL ? 0 : -1;
586   else if(env2->message_id == NULL)
587     return 1;
588   return strcmp(env1->message_id, env2->message_id);
589 }
590 
591 /* Pruning criteria, somewhat extended from mail_criteria()
592  * Accepts: criteria
593  * Returns: search program if parse successful, else NIL
594  */
595 
prune_criteria(char * criteria)596 SEARCHPGM *prune_criteria (char *criteria)
597 {
598   SEARCHPGM *pgm = NIL;
599   char *criterion,*r,tmp[MAILTMPLEN];
600   int f;
601   if (criteria) {		/* only if criteria defined */
602 				/* make writeable copy of criteria */
603     criteria = cpystr (criteria);
604 				/* for each criterion */
605     for (pgm = mail_newsearchpgm (), criterion = strtok_r (criteria," ",&r);
606 	 criterion; (criterion = strtok_r (NIL," ",&r))) {
607       f = NIL;			/* init then scan the criterion */
608       switch (*ucase (criterion)) {
609       case 'A':			/* possible ALL, ANSWERED */
610 	if (!strcmp (criterion+1,"LL")) f = T;
611 	else if (!strcmp (criterion+1,"NSWERED")) f = pgm->answered = T;
612 	break;
613       case 'B':			/* possible BCC, BEFORE, BODY */
614 	if (!strcmp (criterion+1,"CC"))
615 	  f = mail_criteria_string (&pgm->bcc,&r);
616 	else if (!strcmp (criterion+1,"EFORE"))
617 	  f = mail_criteria_date (&pgm->before,&r);
618 	else if (!strcmp (criterion+1,"ODY"))
619 	  f = mail_criteria_string (&pgm->body,&r);
620 	break;
621       case 'C':			/* possible CC */
622 	if (!strcmp (criterion+1,"C")) f = mail_criteria_string (&pgm->cc,&r);
623 	break;
624       case 'D':			/* possible DELETED, DRAFT */
625 	if (!strcmp (criterion+1,"ELETED")) f = pgm->deleted = T;
626 	else if (!strcmp (criterion+1,"RAFT")) f = pgm->draft = T;
627 	break;
628       case 'F':			/* possible FLAGGED, FROM */
629 	if (!strcmp (criterion+1,"LAGGED")) f = pgm->flagged = T;
630 	else if (!strcmp (criterion+1,"ROM"))
631 	  f = mail_criteria_string (&pgm->from,&r);
632 	break;
633       case 'K':			/* possible KEYWORD */
634 	if (!strcmp (criterion+1,"EYWORD"))
635 	  f = mail_criteria_string (&pgm->keyword,&r);
636 	break;
637       case 'L':			/* possible LARGER */
638 	if (!strcmp (criterion+1,"ARGER"))
639 	  f = criteria_number (&pgm->larger,&r);
640 
641       case 'N':			/* possible NEW */
642 	if (!strcmp (criterion+1,"EW")) f = pgm->recent = pgm->unseen = T;
643 	break;
644       case 'O':			/* possible OLD, ON */
645 	if (!strcmp (criterion+1,"LD")) f = pgm->old = T;
646 	else if (!strcmp (criterion+1,"N"))
647 	  f = mail_criteria_date (&pgm->on,&r);
648 	break;
649       case 'R':			/* possible RECENT */
650 	if (!strcmp (criterion+1,"ECENT")) f = pgm->recent = T;
651 	break;
652       case 'S':			/* possible SEEN, SENT*, SINCE, SMALLER,
653 				   SUBJECT */
654 	if (!strcmp (criterion+1,"EEN")) f = pgm->seen = T;
655 	else if (!strncmp (criterion+1,"ENT",3)) {
656 	  if (!strcmp (criterion+4,"BEFORE"))
657 	    f = mail_criteria_date (&pgm->sentbefore,&r);
658 	  else if (!strcmp (criterion+4,"ON"))
659 	    f = mail_criteria_date (&pgm->senton,&r);
660 	  else if (!strcmp (criterion+4,"SINCE"))
661 	    f = mail_criteria_date (&pgm->sentsince,&r);
662 	}
663 	else if (!strcmp (criterion+1,"INCE"))
664 	  f = mail_criteria_date (&pgm->since,&r);
665 	else if (!strcmp (criterion+1,"MALLER"))
666 	  f = criteria_number (&pgm->smaller,&r);
667 	else if (!strcmp (criterion+1,"UBJECT"))
668 	  f = mail_criteria_string (&pgm->subject,&r);
669 	break;
670       case 'T':			/* possible TEXT, TO */
671 	if (!strcmp (criterion+1,"EXT"))
672 	  f = mail_criteria_string (&pgm->text,&r);
673 	else if (!strcmp (criterion+1,"O"))
674 	  f = mail_criteria_string (&pgm->to,&r);
675 	break;
676       case 'U':			/* possible UN* */
677 	if (criterion[1] == 'N') {
678 	  if (!strcmp (criterion+2,"ANSWERED")) f = pgm->unanswered = T;
679 	  else if (!strcmp (criterion+2,"DELETED")) f = pgm->undeleted = T;
680 	  else if (!strcmp (criterion+2,"DRAFT")) f = pgm->undraft = T;
681 	  else if (!strcmp (criterion+2,"FLAGGED")) f = pgm->unflagged = T;
682 	  else if (!strcmp (criterion+2,"KEYWORD"))
683 	    f = mail_criteria_string (&pgm->unkeyword,&r);
684 	  else if (!strcmp (criterion+2,"SEEN")) f = pgm->unseen = T;
685 	}
686 	break;
687       default:			/* we will barf below */
688 	break;
689       }
690 
691       if (!f) {			/* if can't identify criterion */
692 	sprintf (tmp,"Unknown search criterion: %.30s",criterion);
693 	MM_LOG (tmp,ERROR);
694 	mail_free_searchpgm (&pgm);
695 	break;
696       }
697     }
698 				/* no longer need copy of criteria */
699     fs_give ((void **) &criteria);
700   }
701   return pgm;
702 }
703 
704 
705 /* Parse a number
706  * Accepts: pointer to integer to return
707  *	    pointer to strtok state
708  * Returns: T if successful, else NIL
709  */
710 
criteria_number(unsigned long * number,char ** r)711 int criteria_number (unsigned long *number,char **r)
712 {
713   char *t;
714   STRINGLIST *s = NIL;
715 				/* parse the date and return fn if OK */
716   int ret = (mail_criteria_string (&s,r) &&
717 	     (*number = strtoul ((char *) s->text.data,&t,10)) && !*t) ?
718 	       T : NIL;
719   if (s) mail_free_stringlist (&s);
720   return ret;
721 }
722 
723 /* Copy mailbox
724  * Accepts: stream open on source
725  *	    halfopen stream for destination or NIL
726  *	    destination mailbox name
727  *	    non-zero to create destination mailbox
728  *	    non-zero to delete messages from source after copying
729  *	    merge mode
730  * Returns: T if success, NIL if error
731  */
732 
mbxcopy(MAILSTREAM * source,MAILSTREAM * dest,char * dst,int create,int del,int mode)733 int mbxcopy (MAILSTREAM *source,MAILSTREAM *dest,char *dst,int create,int del,
734 	     int mode)
735 {
736   char *s,tmp[MAILTMPLEN];
737   APPENDPACKAGE ap;
738   STRING st;
739   char *ndst = NIL;
740   int ret = NIL;
741   trycreate = NIL;		/* no TRYCREATE yet */
742   if (create) while (!mail_create (dest,dst) && (mode != mAPPEND)) {
743     switch (mode) {
744     case mPROMPT:		/* prompt user for new name */
745       tmp[0] = '\0';
746       while (!tmp[0]) {		/* read name */
747 	fputs ("alternative name: ",stdout);
748 	fflush (stdout);
749 	fgets (tmp,MAILTMPLEN-1,stdin);
750 	if ((s = strchr (tmp,'\n')) != NULL) *s = '\0';
751       }
752       if (ndst) fs_give ((void **) &ndst);
753       ndst = cpystr (tmp);
754       break;
755     case mSUFFIX:		/* try again with new suffix */
756       if (ndst) fs_give ((void **) &ndst);
757       sprintf (ndst = (char *) fs_get (strlen (dst) + strlen (suffix) + 1),
758 	       "%s%s",dst,suffix);
759       printf ("retry to create %s\n",ndst);
760       mode = mPROMPT;		/* switch to prompt mode if name fails */
761       break;
762     case NIL:			/* not merging */
763       return NIL;
764     }
765     if (ndst) dst = ndst;	/* if alternative name given, use it */
766   }
767 
768   if (kwcopyp) {
769     int i;
770     size_t len;
771     char *dummymsg = "Date: Thu, 18 May 2006 00:00 -0700\r\nFrom: dummy@example.com\r\nSubject: dummy\r\n\r\ndummy\r\n";
772     for (i = 0,len = 0; i < NUSERFLAGS; ++i)
773       if (source->user_flags[i]) len += strlen (source->user_flags[i]) + 1;
774     if (len) {			/* easy if no user flags to copy... */
775       char *t;
776       char *tail = "\\Deleted)";
777       char *flags = (char *) fs_get (1 + len + strlen (tail) + 1);
778       s = flags; *s++ = '(';
779       for (i = 0; i < NUSERFLAGS; ++i) if ((t = source->user_flags[i]) != NULL) {
780 	while (*t) *s++ = *t++;
781 	*s++ = ' ';
782       }
783       strcpy (s,tail);		/* terminate flags list */
784       if ((dst[0] == '#') && ((dst[1] == 'D') || (dst[1] == 'd')) &&
785 	  ((dst[2] == 'R') || (dst[2] == 'r')) &&
786 	  ((dst[3] == 'I') || (dst[3] == 'i')) &&
787 	  ((dst[4] == 'V') || (dst[4] == 'v')) &&
788 	  ((dst[5] == 'E') || (dst[5] == 'e')) &&
789 	  ((dst[6] == 'R') || (dst[6] == 'r')) && (dst[7] == '.') &&
790 	  (t = strchr (dst+8,'/'))) ++t;
791       else t = dst;
792       INIT (&st,mail_string,dummymsg,strlen (dummymsg));
793       if (!(mail_append (dest,dst,&st) &&
794 	    (dest = mail_open (dest,t,debugp ? OP_DEBUG : NIL)))) {
795 	fs_give ((void **) &flags);
796 	return NIL;
797       }
798       mail_setflag (dest,"*",flags);
799       mail_expunge (dest);
800       fs_give ((void **) &flags);
801     }
802   }
803 
804   if (source->nmsgs) {		/* non-empty source */
805     if (verbosep) printf ("%s [%lu message(s)] => %s\n",
806 			      source->mailbox,source->nmsgs,dst);
807     ap.stream = source;		/* prepare append package */
808     ap.msgno = 0;
809     ap.msgmax = source->nmsgs;
810     ap.flags = ap.date = NIL;
811     ap.message = &st;
812 				/* make sure we have all messages */
813     sprintf (tmp,"1:%lu",ap.msgmax);
814     mail_fetchfast (source,tmp);
815     if (mail_append_multiple (dest,dst,mm_append,(void *) &ap)) {
816       --ap.msgno;		/* make sure user knows it won */
817       if (verbosep) printf ("[Ok %lu messages(s)]\n",ap.msgno);
818       if (del && ap.msgno) {	/* delete source messages */
819 	sprintf (tmp,"1:%lu",ap.msgno);
820 	mail_flag (source,tmp,"\\Deleted",ST_SET);
821 				/* flush moved messages */
822 	mail_expunge (source);
823       }
824       ret = T;
825     }
826     else if ((mode == mAPPEND) && trycreate)
827       ret = mbxcopy (source,dest,dst,create,del,mPROMPT);
828     else if (verbosep) puts ("[Failed]");
829   }
830   else {			/* empty source */
831     if (verbosep) printf ("%s [empty] => %s\n",source->mailbox,dst);
832     ret = T;
833   }
834   if (ndst) fs_give ((void **) &ndst);
835   return ret;
836 }
837 
838 /* Append callback
839  * Accepts: mail stream
840  *	    append package
841  *	    pointer to return flags
842  *	    pointer to return date
843  *	    pointer to return message stringstruct
844  * Returns: T on success
845  */
846 
mm_append(MAILSTREAM * stream,void * data,char ** flags,char ** date,STRING ** message)847 long mm_append (MAILSTREAM *stream,void *data,char **flags,char **date,
848 		STRING **message)
849 {
850   char *t,*t1,tmp[MAILTMPLEN];
851   unsigned long u;
852   MESSAGECACHE *elt;
853   APPENDPACKAGE *ap = (APPENDPACKAGE *) data;
854   *flags = *date = NIL;		/* assume no flags or date */
855   if (ap->flags) fs_give ((void **) &ap->flags);
856   if (ap->date) fs_give ((void **) &ap->date);
857   mail_gc (ap->stream,GC_TEXTS);
858   if (++ap->msgno <= ap->msgmax) {
859 				/* initialize flag string */
860     memset (t = tmp,0,MAILTMPLEN);
861 				/* output system flags */
862     if ((elt = mail_elt (ap->stream,ap->msgno))->seen) strcat (t," \\Seen");
863     if (elt->deleted) strcat (t," \\Deleted");
864     if (elt->flagged) strcat (t," \\Flagged");
865     if (elt->answered) strcat (t," \\Answered");
866     if (elt->draft) strcat (t," \\Draft");
867 				/* any user flags? */
868     if (!ignorep && (u = elt->user_flags)) do
869       if ((t1 = ap->stream->user_flags[find_rightmost_bit (&u)]) &&
870 	  (MAILTMPLEN - ((t += strlen (t)) - tmp)) > (long) (2 + strlen (t1))){
871 	*t++ = ' ';		/* space delimiter */
872 	strcpy (t,t1);		/* copy the user flag */
873       }
874     while (u);			/* until no more user flags */
875     *flags = ap->flags = cpystr (tmp + 1);
876     *date = ap->date = cpystr (mail_date (tmp,elt));
877     *message = ap->message;	/* message stringstruct */
878     INIT (ap->message,mstring,(void *) ap,elt->rfc822_size);
879   }
880   else *message = NIL;		/* all done */
881   return LONGT;
882 }
883 
884 /* Co-routines from MAIL library */
885 
886 
887 /* Message matches a search
888  * Accepts: MAIL stream
889  *	    message number
890  */
891 
mm_searched(MAILSTREAM * stream,unsigned long msgno)892 void mm_searched (MAILSTREAM *stream,unsigned long msgno)
893 {
894 				/* dummy routine */
895 }
896 
897 
898 /* Message exists (i.e. there are that many messages in the mailbox)
899  * Accepts: MAIL stream
900  *	    message number
901  */
902 
mm_exists(MAILSTREAM * stream,unsigned long number)903 void mm_exists (MAILSTREAM *stream,unsigned long number)
904 {
905 				/* dummy routine */
906 }
907 
908 
909 /* Message expunged
910  * Accepts: MAIL stream
911  *	    message number
912  */
913 
mm_expunged(MAILSTREAM * stream,unsigned long number)914 void mm_expunged (MAILSTREAM *stream,unsigned long number)
915 {
916 				/* dummy routine */
917 }
918 
919 
920 /* Message flags update seen
921  * Accepts: MAIL stream
922  *	    message number
923  */
924 
mm_flags(MAILSTREAM * stream,unsigned long number)925 void mm_flags (MAILSTREAM *stream,unsigned long number)
926 {
927 				/* dummy routine */
928 }
929 
930 /* Mailbox found
931  * Accepts: MAIL stream
932  *	    hierarchy delimiter
933  *	    mailbox name
934  *	    mailbox attributes
935  */
936 
mm_list(MAILSTREAM * stream,int delimiter,char * name,long attributes)937 void mm_list (MAILSTREAM *stream,int delimiter,char *name,long attributes)
938 {
939 				/* note destination delimiter */
940   if (ddelim < 0) ddelim = delimiter;
941 				/* if got a selectable name */
942   else if (!(attributes & LATT_NOSELECT) && *name)
943     fprintf (f,"%c%s\n",delimiter,name);
944 }
945 
946 
947 /* Subscribe mailbox found
948  * Accepts: MAIL stream
949  *	    hierarchy delimiter
950  *	    mailbox name
951  *	    mailbox attributes
952  */
953 
mm_lsub(MAILSTREAM * stream,int delimiter,char * name,long attributes)954 void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes)
955 {
956 				/* dummy routine */
957 }
958 
959 
960 /* Mailbox status
961  * Accepts: MAIL stream
962  *	    mailbox name
963  *	    mailbox status
964  */
965 
mm_status(MAILSTREAM * stream,char * mailbox,MAILSTATUS * status)966 void mm_status (MAILSTREAM *stream,char *mailbox,MAILSTATUS *status)
967 {
968   if (status->recent || status->unseen)
969     printf ("%lu new message(s) (%lu unseen),",status->recent,status->unseen);
970   else fputs ("No new messages,",stdout);
971   printf (" %lu total in %s\n",status->messages,mailbox);
972 }
973 
974 /* Notification event
975  * Accepts: MAIL stream
976  *	    string to log
977  *	    error flag
978  */
979 
mm_notify(MAILSTREAM * stream,char * string,long errflg)980 void mm_notify (MAILSTREAM *stream,char *string,long errflg)
981 {
982   if (!errflg && (string[0] == '[') &&
983       ((string[1] == 'T') || (string[1] == 't')) &&
984       ((string[2] == 'R') || (string[2] == 'r')) &&
985       ((string[3] == 'Y') || (string[3] == 'y')) &&
986       ((string[4] == 'C') || (string[4] == 'c')) &&
987       ((string[5] == 'R') || (string[5] == 'r')) &&
988       ((string[6] == 'E') || (string[6] == 'e')) &&
989       ((string[7] == 'A') || (string[7] == 'a')) &&
990       ((string[8] == 'T') || (string[8] == 't')) &&
991       ((string[9] == 'E') || (string[9] == 'e')) &&
992       (string[10] == ']'))
993     trycreate = T;
994   mm_log (string,errflg);	/* just do mm_log action */
995 }
996 
997 
998 /* Log an event for the user to see
999  * Accepts: string to log
1000  *	    error flag
1001  */
1002 
mm_log(char * string,long errflg)1003 void mm_log (char *string,long errflg)
1004 {
1005   switch (errflg) {
1006   case BYE:
1007   case NIL:			/* no error */
1008     if (verbosep) fprintf (stderr,"[%s]\n",string);
1009     break;
1010   case PARSE:			/* parsing problem */
1011   case WARN:			/* warning */
1012     fprintf (stderr,"warning: %s\n",string);
1013     break;
1014   case ERROR:			/* error */
1015   default:
1016     fprintf (stderr,"%s\n",string);
1017     break;
1018   }
1019 }
1020 
1021 
1022 /* Log an event to debugging telemetry
1023  * Accepts: string to log
1024  */
1025 
mm_dlog(char * string)1026 void mm_dlog (char *string)
1027 {
1028   fprintf (stderr,"%s\n",string);
1029 }
1030 
1031 /* Get user name and password for this host
1032  * Accepts: parse of network mailbox name
1033  *	    where to return user name
1034  *	    where to return password
1035  *	    trial count
1036  */
1037 
mm_login(NETMBX * mb,char * username,char ** password,long trial)1038 void mm_login (NETMBX *mb,char *username,char **password,long trial)
1039 {
1040   char *s,tmp[MAILTMPLEN];
1041   sprintf (s = tmp,"{%s/%s",mb->host,mb->service);
1042   if (*mb->user) sprintf (tmp+strlen (tmp),"/user=%s",
1043 			  strcpy (username,mb->user));
1044   if (*mb->authuser) sprintf (tmp+strlen (tmp),"/authuser=%s",mb->authuser);
1045   if (*mb->user) strcat (s = tmp,"} password:");
1046   else {
1047     printf ("%s} username: ",tmp);
1048     fgets (username,NETMAXUSER-1,stdin);
1049     username[NETMAXUSER-1] = '\0';
1050     if ((s = strchr (username,'\n')) != NULL) *s = '\0';
1051     s = "password: ";
1052   }
1053   if(strlen (s = getpass (s)) < MAILTMPLEN) *password = cpystr(s);
1054 }
1055 
mm_login_method(NETMBX * mb,char * username,void * login,long trial,char * method)1056 void mm_login_method (NETMBX *mb,char *username, void *login,long trial, char *method)
1057 {
1058   if(method == NULL) return;
1059   if(strcmp(method, "XOAUTH2") == 0){
1060     OAUTH2_S *muinfo = NULL;	/* mail util info */
1061     char *s,tmp[MAILTMPLEN];
1062     sprintf (s = tmp,"{%s/%s",mb->host,mb->service);
1063     if (*mb->user) sprintf (tmp+strlen (tmp),"/user=%s",
1064 			  strcpy (username,mb->user));
1065     if (*mb->user) strcat (s = tmp,"} access token: ");
1066     else {
1067       printf ("%s} username: ",tmp);
1068       fgets (username,NETMAXUSER-1,stdin);
1069       username[NETMAXUSER-1] = '\0';
1070       if ((s = strchr (username,'\n')) != NULL) *s = '\0';
1071       printf ("%s} access token: ", tmp);
1072     }
1073     fgets (s,MAILTMPLEN/2-1,stdin);
1074     tmp[MAILTMPLEN/2-1] = '\0';
1075     if ((s = strchr (tmp,'\n')) != NULL) *s = '\0';
1076     if(tmp[0]){
1077        muinfo = fs_get(sizeof(OAUTH2_S));
1078        memset((void *) muinfo, 0, sizeof(OAUTH2_S));
1079        muinfo->access_token = cpystr(tmp);
1080 	/* STILL MISSING: get expires in info */
1081     }
1082     login = (void *) muinfo;
1083   }
1084 }
1085 
1086 
1087 /* About to enter critical code
1088  * Accepts: stream
1089  */
1090 
mm_critical(MAILSTREAM * stream)1091 void mm_critical (MAILSTREAM *stream)
1092 {
1093   critical = T;			/* note in critical code */
1094 }
1095 
1096 
1097 /* About to exit critical code
1098  * Accepts: stream
1099  */
1100 
mm_nocritical(MAILSTREAM * stream)1101 void mm_nocritical (MAILSTREAM *stream)
1102 {
1103   critical = NIL;		/* note not in critical code */
1104 }
1105 
1106 
1107 /* Disk error found
1108  * Accepts: stream
1109  *	    system error code
1110  *	    flag indicating that mailbox may be clobbered
1111  * Returns: T if user wants to abort
1112  */
1113 
mm_diskerror(MAILSTREAM * stream,long errcode,long serious)1114 long mm_diskerror (MAILSTREAM *stream,long errcode,long serious)
1115 {
1116   return T;
1117 }
1118 
1119 
1120 /* Log a fatal error event
1121  * Accepts: string to log
1122  */
1123 
mm_fatal(char * string)1124 void mm_fatal (char *string)
1125 {
1126   fprintf (stderr,"FATAL: %s\n",string);
1127 }
1128