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