2 * Copyright 2016 Eduardo Chappa
5 /* ========================================================================
6 * Copyright 2009 Mark Crispin
7 * ========================================================================
11 * Program: Mail utility
13 * Author: Mark Crispin
15 * Date: 2 February 1994
16 * Last Edited: 14 May 2009
18 * Previous versions of this file were
20 * Copyright 1988-2008 University of Washington
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
26 * http://www.apache.org/licenses/LICENSE-2.0
33 extern int errno
; /* just in case */
35 #ifdef SYSCONFIG /* defined in env_unix.h */
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 */
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";
67 char *stdsw
= "Standard switches valid with any command:\n\t[-d[ebug]] [-v[erbose]] [-u[ser] userid] [--]";
69 char *stdsw
= "Standard switches valid with any command:\n\t[-d[ebug]] [-v[erbose]]";
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
,
92 long mm_append (MAILSTREAM
*stream
,void *data
,char **flags
,char **date
,
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 */
108 /* Message string driver for message stringstructs */
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 */
116 /* Initialize file string structure for file stringstruct
117 * Accepts: string structure
118 * pointer to message data structure
122 void ms_init (STRING
*s
,void *data
,unsigned long size
)
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
);
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 */
138 /* Get next character from file stringstruct
139 * Accepts: string structure
140 * Returns: character, string structure chunk refreshed
143 char ms_next (STRING
*s
)
145 char c
= *s
->curpos
++; /* get next byte */
146 SETPOS (s
,GETPOS (s
)); /* move to next chunk */
147 return c
; /* return the byte */
151 /* Set string pointer position for file stringstruct
152 * Accepts: string structure
156 void ms_setpos (STRING
*s
,unsigned long i
)
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 */
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 */
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... */
174 /* initial position and size */
175 s
->curpos
= s
->chunk
+ (i
-= s
->offset
);
176 s
->cursize
= s
->chunksize
- i
;
181 int main (int argc
,char *argv
[])
183 MAILSTREAM
*source
= NIL
;
184 MAILSTREAM
*dest
= NIL
;
186 char c
,*s
,*dp
,*t
,*t1
,tmp
[MAILTMPLEN
],mbx
[MAILTMPLEN
];
187 unsigned long m
,len
,curlen
,start
,last
;
195 char *pgm
= argc
? argv
[0] : "mailutil";
197 for (i
= 1; i
< argc
; i
++) {
198 s
= argv
[i
]; /* pick up argument */
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]) {
211 suffix
= cpystr (s
+7);
214 printf ("unknown merge option: %s\n",s
);
220 else if ((!strcmp (s
,"-user") || !strcmp (s
,"-u")) && (++i
< argc
)) {
221 struct passwd
*pw
= getpwnam (s
= argv
[i
]);
223 printf ("unknown user id: %s\n",argv
[i
]);
226 else if (setuid (pw
->pw_uid
)) {
227 perror ("unable to change user id");
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
);
236 /* -- means no more switches, so mailbox
237 name can start with "-" */
238 else if ((s
[1] == '-') && !s
[2]) moreswitchp
= NIL
;
240 printf ("unknown switch: %s\n",s
);
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 */
248 printf ("unknown argument: %s\n",s
);
252 if (kwcopyp
&& ignorep
) {
253 puts ("-kwcopy and -ignore are mutually exclusive");
256 if (!cmd
) cmd
= ""; /* prevent SEGV */
258 if(!strcmp(cmd
, "dedup")){
259 if (!src
) src
= "INBOX";
260 if (dst
|| merge
|| rwcopyp
|| kwcopyp
|| ignorep
)
262 else if(mailutil_dedup(src
, debugp
? OP_DEBUG
: NIL
))
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
))
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
))
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
))
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
))
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;
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;
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 */
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 */
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 */
359 else puts ("No matching messages, so nothing purged");
360 source
= mail_close (source
);
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");
374 if (verbosep
) puts ("Listing mailboxes...");
375 if (dest
) strcpy (strchr (strcpy (tmp
,dest
->mailbox
),'}') + 1,
376 dp
= strchr (dst
,'}') + 1);
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 */
387 if (source
) strcpy (strchr (strcpy (tmp
,source
->mailbox
),'}') + 1,
388 strchr (src
,'}') + 1);
389 else strcpy (tmp
,src
);
390 mail_list (source
,tmp
,"*");
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
;
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
;
408 else while (*t1
) *t
++ = *t1
++;
411 printf ("Copying %s\n => %s\n",tmp
+1,mbx
);
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
);
419 else printf ("can't open source mailbox %s\n",tmp
+1);
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");
450 if (source
) mail_close (source
);
451 if (dest
) mail_close (dest
);
453 return retcode
; /* stupid compilers */
456 char *mailutil_string_sequence(MAILSTREAM
*m
)
459 unsigned long i
, j
, count
;
464 if(m
== NULL
|| m
->nmsgs
== 0L) return NULL
;
465 for(i
= 1L; i
<= m
->nmsgs
;){
466 if(mail_elt(m
, i
)->sequence
){
467 for(j
= i
+1; j
<= m
->nmsgs
&& mail_elt(m
, j
)->sequence
; j
++)
469 mailutil_add_sequence(&rv
, &len
, i
, j
-1, m
->nmsgs
);
477 void mailutil_add_sequence(char **sequence
, size_t *len
, unsigned long i
, unsigned long j
, unsigned long nmsgs
)
479 #define SEQ_BUF_LEN 256
480 char tmp
[MAILTMPLEN
];
487 sprintf(tmp
, "%s%lu", *len
== 0L ? "" : ",", i
);
489 sprintf(tmp
, "%s%lu:*", *len
== 0L ? "" : ",", i
);
491 sprintf(tmp
, "%s%lu:%lu", *len
== 0L ? "" : ",", i
, j
);
492 tmp
[sizeof(tmp
)-1]='\0';
494 needed
= strlen(*sequence
? *sequence
: "") + strlen(tmp
) + 1;
496 fs_resize((void **) sequence
, (needed
+ SEQ_BUF_LEN
)*sizeof(char));
498 (*sequence
)[0] = '\0';
499 *len
= needed
+ SEQ_BUF_LEN
;
501 strcat(*sequence
+ strlen(*sequence
), tmp
);
505 int mailutil_dedup (char *mailbox
, long options
)
510 if((m
= mail_open(NIL
, mailbox
, options
)) == NULL
)
512 else if(m
->nmsgs
> 1L){
513 unsigned long i
, count
= 0L;
516 ENVELOPE
*env1
, *env2
;
519 fprintf(stdout
, "Opened folder \"%s\" with %lu messages\n", mailbox
, m
->nmsgs
);
520 mail_fetch_overview_sequence(m
, "1:*", NIL
);
521 for(i
= 1L; i
<= m
->nmsgs
; i
++)
522 mail_elt(m
, i
)->sequence
= 0;
523 mc
= fs_get(m
->nmsgs
*sizeof(MESSAGECACHE
*));
524 for(i
= 1L; i
<= m
->nmsgs
; i
++)
525 mc
[i
-1] = mail_elt(m
, i
);
526 qsort((void *)mc
, (size_t) m
->nmsgs
, sizeof(MESSAGECACHE
*), mailutil_compare_message_id
);
527 for(i
= 1L; i
< m
->nmsgs
;){
532 env1
= mail_fetch_structure(m
, mc
[i
-1]->msgno
, NIL
, NIL
);
533 if(env1
->message_id
== NULL
){
537 for(j
= i
+1; j
<= m
->nmsgs
; j
++){
538 env2
= mail_fetch_structure(m
, mc
[j
-1]->msgno
, NIL
, NIL
);
539 if(env2
->message_id
== NULL
){
542 if(strcmp(env1
->message_id
, env2
->message_id
))
545 fprintf(stdout
, "Message %lu and %lu are duplicates\n",
546 mc
[i
-1]->msgno
, mc
[j
-1]->msgno
);
549 for(k
= i
+1; k
<= j
- 1; k
++){
550 mc
[k
-1]->sequence
= T
;
556 fprintf(stdout
, "Found %lu extra duplicate messages\n", count
);
557 if((sequence
= mailutil_string_sequence(m
)) != NULL
){
558 mail_flag(m
, sequence
, "\\DELETED", ST_SET
);
559 if(count
&& mail_expunge_full(m
, "1:*", NIL
))
560 fprintf (stdout
, "Expunged %lu duplicate messages\n", count
);
563 fs_give((void *)&sequence
);
570 int mailutil_compare_message_id (const void *mcp1
, const void *mcp2
)
572 MESSAGECACHE
*mc1
, *mc2
;
573 ENVELOPE
*env1
, *env2
;
575 mc1
= *(MESSAGECACHE
**) mcp1
;
576 mc2
= *(MESSAGECACHE
**) mcp2
;
578 env1
= mc1
->private.msg
.env
; /* aarrggh direct access inside message cache */
579 env2
= mc2
->private.msg
.env
;
582 return env2
== NULL
? 0 : -1;
583 else if (env2
== NULL
)
585 else if(env1
->message_id
== NULL
)
586 return env2
->message_id
== NULL
? 0 : -1;
587 else if(env2
->message_id
== NULL
)
589 return strcmp(env1
->message_id
, env2
->message_id
);
592 /* Pruning criteria, somewhat extended from mail_criteria()
594 * Returns: search program if parse successful, else NIL
597 SEARCHPGM
*prune_criteria (char *criteria
)
599 SEARCHPGM
*pgm
= NIL
;
600 char *criterion
,*r
,tmp
[MAILTMPLEN
];
602 if (criteria
) { /* only if criteria defined */
603 /* make writeable copy of criteria */
604 criteria
= cpystr (criteria
);
605 /* for each criterion */
606 for (pgm
= mail_newsearchpgm (), criterion
= strtok_r (criteria
," ",&r
);
607 criterion
; (criterion
= strtok_r (NIL
," ",&r
))) {
608 f
= NIL
; /* init then scan the criterion */
609 switch (*ucase (criterion
)) {
610 case 'A': /* possible ALL, ANSWERED */
611 if (!strcmp (criterion
+1,"LL")) f
= T
;
612 else if (!strcmp (criterion
+1,"NSWERED")) f
= pgm
->answered
= T
;
614 case 'B': /* possible BCC, BEFORE, BODY */
615 if (!strcmp (criterion
+1,"CC"))
616 f
= mail_criteria_string (&pgm
->bcc
,&r
);
617 else if (!strcmp (criterion
+1,"EFORE"))
618 f
= mail_criteria_date (&pgm
->before
,&r
);
619 else if (!strcmp (criterion
+1,"ODY"))
620 f
= mail_criteria_string (&pgm
->body
,&r
);
622 case 'C': /* possible CC */
623 if (!strcmp (criterion
+1,"C")) f
= mail_criteria_string (&pgm
->cc
,&r
);
625 case 'D': /* possible DELETED, DRAFT */
626 if (!strcmp (criterion
+1,"ELETED")) f
= pgm
->deleted
= T
;
627 else if (!strcmp (criterion
+1,"RAFT")) f
= pgm
->draft
= T
;
629 case 'F': /* possible FLAGGED, FROM */
630 if (!strcmp (criterion
+1,"LAGGED")) f
= pgm
->flagged
= T
;
631 else if (!strcmp (criterion
+1,"ROM"))
632 f
= mail_criteria_string (&pgm
->from
,&r
);
634 case 'K': /* possible KEYWORD */
635 if (!strcmp (criterion
+1,"EYWORD"))
636 f
= mail_criteria_string (&pgm
->keyword
,&r
);
638 case 'L': /* possible LARGER */
639 if (!strcmp (criterion
+1,"ARGER"))
640 f
= criteria_number (&pgm
->larger
,&r
);
642 case 'N': /* possible NEW */
643 if (!strcmp (criterion
+1,"EW")) f
= pgm
->recent
= pgm
->unseen
= T
;
645 case 'O': /* possible OLD, ON */
646 if (!strcmp (criterion
+1,"LD")) f
= pgm
->old
= T
;
647 else if (!strcmp (criterion
+1,"N"))
648 f
= mail_criteria_date (&pgm
->on
,&r
);
650 case 'R': /* possible RECENT */
651 if (!strcmp (criterion
+1,"ECENT")) f
= pgm
->recent
= T
;
653 case 'S': /* possible SEEN, SENT*, SINCE, SMALLER,
655 if (!strcmp (criterion
+1,"EEN")) f
= pgm
->seen
= T
;
656 else if (!strncmp (criterion
+1,"ENT",3)) {
657 if (!strcmp (criterion
+4,"BEFORE"))
658 f
= mail_criteria_date (&pgm
->sentbefore
,&r
);
659 else if (!strcmp (criterion
+4,"ON"))
660 f
= mail_criteria_date (&pgm
->senton
,&r
);
661 else if (!strcmp (criterion
+4,"SINCE"))
662 f
= mail_criteria_date (&pgm
->sentsince
,&r
);
664 else if (!strcmp (criterion
+1,"INCE"))
665 f
= mail_criteria_date (&pgm
->since
,&r
);
666 else if (!strcmp (criterion
+1,"MALLER"))
667 f
= criteria_number (&pgm
->smaller
,&r
);
668 else if (!strcmp (criterion
+1,"UBJECT"))
669 f
= mail_criteria_string (&pgm
->subject
,&r
);
671 case 'T': /* possible TEXT, TO */
672 if (!strcmp (criterion
+1,"EXT"))
673 f
= mail_criteria_string (&pgm
->text
,&r
);
674 else if (!strcmp (criterion
+1,"O"))
675 f
= mail_criteria_string (&pgm
->to
,&r
);
677 case 'U': /* possible UN* */
678 if (criterion
[1] == 'N') {
679 if (!strcmp (criterion
+2,"ANSWERED")) f
= pgm
->unanswered
= T
;
680 else if (!strcmp (criterion
+2,"DELETED")) f
= pgm
->undeleted
= T
;
681 else if (!strcmp (criterion
+2,"DRAFT")) f
= pgm
->undraft
= T
;
682 else if (!strcmp (criterion
+2,"FLAGGED")) f
= pgm
->unflagged
= T
;
683 else if (!strcmp (criterion
+2,"KEYWORD"))
684 f
= mail_criteria_string (&pgm
->unkeyword
,&r
);
685 else if (!strcmp (criterion
+2,"SEEN")) f
= pgm
->unseen
= T
;
688 default: /* we will barf below */
692 if (!f
) { /* if can't identify criterion */
693 sprintf (tmp
,"Unknown search criterion: %.30s",criterion
);
695 mail_free_searchpgm (&pgm
);
699 /* no longer need copy of criteria */
700 fs_give ((void **) &criteria
);
707 * Accepts: pointer to integer to return
708 * pointer to strtok state
709 * Returns: T if successful, else NIL
712 int criteria_number (unsigned long *number
,char **r
)
716 /* parse the date and return fn if OK */
717 int ret
= (mail_criteria_string (&s
,r
) &&
718 (*number
= strtoul ((char *) s
->text
.data
,&t
,10)) && !*t
) ?
720 if (s
) mail_free_stringlist (&s
);
725 * Accepts: stream open on source
726 * halfopen stream for destination or NIL
727 * destination mailbox name
728 * non-zero to create destination mailbox
729 * non-zero to delete messages from source after copying
731 * Returns: T if success, NIL if error
734 int mbxcopy (MAILSTREAM
*source
,MAILSTREAM
*dest
,char *dst
,int create
,int del
,
737 char *s
,tmp
[MAILTMPLEN
];
742 trycreate
= NIL
; /* no TRYCREATE yet */
743 if (create
) while (!mail_create (dest
,dst
) && (mode
!= mAPPEND
)) {
745 case mPROMPT
: /* prompt user for new name */
747 while (!tmp
[0]) { /* read name */
748 fputs ("alternative name: ",stdout
);
750 fgets (tmp
,MAILTMPLEN
-1,stdin
);
751 if ((s
= strchr (tmp
,'\n')) != NULL
) *s
= '\0';
753 if (ndst
) fs_give ((void **) &ndst
);
756 case mSUFFIX
: /* try again with new suffix */
757 if (ndst
) fs_give ((void **) &ndst
);
758 sprintf (ndst
= (char *) fs_get (strlen (dst
) + strlen (suffix
) + 1),
760 printf ("retry to create %s\n",ndst
);
761 mode
= mPROMPT
; /* switch to prompt mode if name fails */
763 case NIL
: /* not merging */
766 if (ndst
) dst
= ndst
; /* if alternative name given, use it */
772 char *dummymsg
= "Date: Thu, 18 May 2006 00:00 -0700\r\nFrom: dummy@example.com\r\nSubject: dummy\r\n\r\ndummy\r\n";
773 for (i
= 0,len
= 0; i
< NUSERFLAGS
; ++i
)
774 if (source
->user_flags
[i
]) len
+= strlen (source
->user_flags
[i
]) + 1;
775 if (len
) { /* easy if no user flags to copy... */
777 char *tail
= "\\Deleted)";
778 char *flags
= (char *) fs_get (1 + len
+ strlen (tail
) + 1);
779 s
= flags
; *s
++ = '(';
780 for (i
= 0; i
< NUSERFLAGS
; ++i
) if ((t
= source
->user_flags
[i
]) != NULL
) {
781 while (*t
) *s
++ = *t
++;
784 strcpy (s
,tail
); /* terminate flags list */
785 if ((dst
[0] == '#') && ((dst
[1] == 'D') || (dst
[1] == 'd')) &&
786 ((dst
[2] == 'R') || (dst
[2] == 'r')) &&
787 ((dst
[3] == 'I') || (dst
[3] == 'i')) &&
788 ((dst
[4] == 'V') || (dst
[4] == 'v')) &&
789 ((dst
[5] == 'E') || (dst
[5] == 'e')) &&
790 ((dst
[6] == 'R') || (dst
[6] == 'r')) && (dst
[7] == '.') &&
791 (t
= strchr (dst
+8,'/'))) ++t
;
793 INIT (&st
,mail_string
,dummymsg
,strlen (dummymsg
));
794 if (!(mail_append (dest
,dst
,&st
) &&
795 (dest
= mail_open (dest
,t
,debugp
? OP_DEBUG
: NIL
)))) {
796 fs_give ((void **) &flags
);
799 mail_setflag (dest
,"*",flags
);
801 fs_give ((void **) &flags
);
805 if (source
->nmsgs
) { /* non-empty source */
806 if (verbosep
) printf ("%s [%lu message(s)] => %s\n",
807 source
->mailbox
,source
->nmsgs
,dst
);
808 ap
.stream
= source
; /* prepare append package */
810 ap
.msgmax
= source
->nmsgs
;
811 ap
.flags
= ap
.date
= NIL
;
813 /* make sure we have all messages */
814 sprintf (tmp
,"1:%lu",ap
.msgmax
);
815 mail_fetchfast (source
,tmp
);
816 if (mail_append_multiple (dest
,dst
,mm_append
,(void *) &ap
)) {
817 --ap
.msgno
; /* make sure user knows it won */
818 if (verbosep
) printf ("[Ok %lu messages(s)]\n",ap
.msgno
);
819 if (del
&& ap
.msgno
) { /* delete source messages */
820 sprintf (tmp
,"1:%lu",ap
.msgno
);
821 mail_flag (source
,tmp
,"\\Deleted",ST_SET
);
822 /* flush moved messages */
823 mail_expunge (source
);
827 else if ((mode
== mAPPEND
) && trycreate
)
828 ret
= mbxcopy (source
,dest
,dst
,create
,del
,mPROMPT
);
829 else if (verbosep
) puts ("[Failed]");
831 else { /* empty source */
832 if (verbosep
) printf ("%s [empty] => %s\n",source
->mailbox
,dst
);
835 if (ndst
) fs_give ((void **) &ndst
);
840 * Accepts: mail stream
842 * pointer to return flags
843 * pointer to return date
844 * pointer to return message stringstruct
845 * Returns: T on success
848 long mm_append (MAILSTREAM
*stream
,void *data
,char **flags
,char **date
,
851 char *t
,*t1
,tmp
[MAILTMPLEN
];
854 APPENDPACKAGE
*ap
= (APPENDPACKAGE
*) data
;
855 *flags
= *date
= NIL
; /* assume no flags or date */
856 if (ap
->flags
) fs_give ((void **) &ap
->flags
);
857 if (ap
->date
) fs_give ((void **) &ap
->date
);
858 mail_gc (ap
->stream
,GC_TEXTS
);
859 if (++ap
->msgno
<= ap
->msgmax
) {
860 /* initialize flag string */
861 memset (t
= tmp
,0,MAILTMPLEN
);
862 /* output system flags */
863 if ((elt
= mail_elt (ap
->stream
,ap
->msgno
))->seen
) strcat (t
," \\Seen");
864 if (elt
->deleted
) strcat (t
," \\Deleted");
865 if (elt
->flagged
) strcat (t
," \\Flagged");
866 if (elt
->answered
) strcat (t
," \\Answered");
867 if (elt
->draft
) strcat (t
," \\Draft");
868 /* any user flags? */
869 if (!ignorep
&& (u
= elt
->user_flags
)) do
870 if ((t1
= ap
->stream
->user_flags
[find_rightmost_bit (&u
)]) &&
871 (MAILTMPLEN
- ((t
+= strlen (t
)) - tmp
)) > (long) (2 + strlen (t1
))){
872 *t
++ = ' '; /* space delimiter */
873 strcpy (t
,t1
); /* copy the user flag */
875 while (u
); /* until no more user flags */
876 *flags
= ap
->flags
= cpystr (tmp
+ 1);
877 *date
= ap
->date
= cpystr (mail_date (tmp
,elt
));
878 *message
= ap
->message
; /* message stringstruct */
879 INIT (ap
->message
,mstring
,(void *) ap
,elt
->rfc822_size
);
881 else *message
= NIL
; /* all done */
885 /* Co-routines from MAIL library */
888 /* Message matches a search
889 * Accepts: MAIL stream
893 void mm_searched (MAILSTREAM
*stream
,unsigned long msgno
)
899 /* Message exists (i.e. there are that many messages in the mailbox)
900 * Accepts: MAIL stream
904 void mm_exists (MAILSTREAM
*stream
,unsigned long number
)
911 * Accepts: MAIL stream
915 void mm_expunged (MAILSTREAM
*stream
,unsigned long number
)
921 /* Message flags update seen
922 * Accepts: MAIL stream
926 void mm_flags (MAILSTREAM
*stream
,unsigned long number
)
932 * Accepts: MAIL stream
933 * hierarchy delimiter
938 void mm_list (MAILSTREAM
*stream
,int delimiter
,char *name
,long attributes
)
940 /* note destination delimiter */
941 if (ddelim
< 0) ddelim
= delimiter
;
942 /* if got a selectable name */
943 else if (!(attributes
& LATT_NOSELECT
) && *name
)
944 fprintf (f
,"%c%s\n",delimiter
,name
);
948 /* Subscribe mailbox found
949 * Accepts: MAIL stream
950 * hierarchy delimiter
955 void mm_lsub (MAILSTREAM
*stream
,int delimiter
,char *name
,long attributes
)
962 * Accepts: MAIL stream
967 void mm_status (MAILSTREAM
*stream
,char *mailbox
,MAILSTATUS
*status
)
969 if (status
->recent
|| status
->unseen
)
970 printf ("%lu new message(s) (%lu unseen),",status
->recent
,status
->unseen
);
971 else fputs ("No new messages,",stdout
);
972 printf (" %lu total in %s\n",status
->messages
,mailbox
);
975 /* Notification event
976 * Accepts: MAIL stream
981 void mm_notify (MAILSTREAM
*stream
,char *string
,long errflg
)
983 if (!errflg
&& (string
[0] == '[') &&
984 ((string
[1] == 'T') || (string
[1] == 't')) &&
985 ((string
[2] == 'R') || (string
[2] == 'r')) &&
986 ((string
[3] == 'Y') || (string
[3] == 'y')) &&
987 ((string
[4] == 'C') || (string
[4] == 'c')) &&
988 ((string
[5] == 'R') || (string
[5] == 'r')) &&
989 ((string
[6] == 'E') || (string
[6] == 'e')) &&
990 ((string
[7] == 'A') || (string
[7] == 'a')) &&
991 ((string
[8] == 'T') || (string
[8] == 't')) &&
992 ((string
[9] == 'E') || (string
[9] == 'e')) &&
995 mm_log (string
,errflg
); /* just do mm_log action */
999 /* Log an event for the user to see
1000 * Accepts: string to log
1004 void mm_log (char *string
,long errflg
)
1008 case NIL
: /* no error */
1009 if (verbosep
) fprintf (stderr
,"[%s]\n",string
);
1011 case PARSE
: /* parsing problem */
1012 case WARN
: /* warning */
1013 fprintf (stderr
,"warning: %s\n",string
);
1015 case ERROR
: /* error */
1017 fprintf (stderr
,"%s\n",string
);
1023 /* Log an event to debugging telemetry
1024 * Accepts: string to log
1027 void mm_dlog (char *string
)
1029 fprintf (stderr
,"%s\n",string
);
1032 /* Get user name and password for this host
1033 * Accepts: parse of network mailbox name
1034 * where to return user name
1035 * where to return password
1039 void mm_login (NETMBX
*mb
,char *username
,char *password
,long trial
)
1041 char *s
,tmp
[MAILTMPLEN
];
1042 sprintf (s
= tmp
,"{%s/%s",mb
->host
,mb
->service
);
1043 if (*mb
->user
) sprintf (tmp
+strlen (tmp
),"/user=%s",
1044 strcpy (username
,mb
->user
));
1045 if (*mb
->authuser
) sprintf (tmp
+strlen (tmp
),"/authuser=%s",mb
->authuser
);
1046 if (*mb
->user
) strcat (s
= tmp
,"} password:");
1048 printf ("%s} username: ",tmp
);
1049 fgets (username
,NETMAXUSER
-1,stdin
);
1050 username
[NETMAXUSER
-1] = '\0';
1051 if ((s
= strchr (username
,'\n')) != NULL
) *s
= '\0';
1054 if(strlen (s
= getpass (s
)) < MAILTMPLEN
) strcpy (password
,s
);
1058 /* About to enter critical code
1062 void mm_critical (MAILSTREAM
*stream
)
1064 critical
= T
; /* note in critical code */
1068 /* About to exit critical code
1072 void mm_nocritical (MAILSTREAM
*stream
)
1074 critical
= NIL
; /* note not in critical code */
1081 * flag indicating that mailbox may be clobbered
1082 * Returns: T if user wants to abort
1085 long mm_diskerror (MAILSTREAM
*stream
,long errcode
,long serious
)
1091 /* Log a fatal error event
1092 * Accepts: string to log
1095 void mm_fatal (char *string
)
1097 fprintf (stderr
,"FATAL: %s\n",string
);