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