2 * Copyright 2016-2022 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
)
261 fprintf (stdout
, "%s", usgdup
);
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
)
463 if(m
== NULL
|| m
->nmsgs
== 0L) return NULL
;
464 for(i
= 1L; i
<= m
->nmsgs
;){
465 if(mail_elt(m
, i
)->sequence
){
466 for(j
= i
+1; j
<= m
->nmsgs
&& mail_elt(m
, j
)->sequence
; j
++)
468 mailutil_add_sequence(&rv
, &len
, i
, j
-1, m
->nmsgs
);
476 void mailutil_add_sequence(char **sequence
, size_t *len
, unsigned long i
, unsigned long j
, unsigned long nmsgs
)
478 #define SEQ_BUF_LEN 256
479 char tmp
[MAILTMPLEN
];
486 sprintf(tmp
, "%s%lu", *len
== 0L ? "" : ",", i
);
488 sprintf(tmp
, "%s%lu:*", *len
== 0L ? "" : ",", i
);
490 sprintf(tmp
, "%s%lu:%lu", *len
== 0L ? "" : ",", i
, j
);
491 tmp
[sizeof(tmp
)-1]='\0';
493 needed
= strlen(*sequence
? *sequence
: "") + strlen(tmp
) + 1;
495 fs_resize((void **) sequence
, (needed
+ SEQ_BUF_LEN
)*sizeof(char));
497 (*sequence
)[0] = '\0';
498 *len
= needed
+ SEQ_BUF_LEN
;
500 strcat(*sequence
+ strlen(*sequence
), tmp
);
504 int mailutil_dedup (char *mailbox
, long options
)
509 if((m
= mail_open(NIL
, mailbox
, options
)) == NULL
)
511 else if(m
->nmsgs
> 1L){
512 unsigned long i
, count
= 0L;
515 ENVELOPE
*env1
, *env2
;
518 fprintf(stdout
, "Opened folder \"%s\" with %lu messages\n", mailbox
, m
->nmsgs
);
519 mail_fetch_overview_sequence(m
, "1:*", NIL
);
520 for(i
= 1L; i
<= m
->nmsgs
; i
++)
521 mail_elt(m
, i
)->sequence
= 0;
522 mc
= fs_get(m
->nmsgs
*sizeof(MESSAGECACHE
*));
523 for(i
= 1L; i
<= m
->nmsgs
; i
++)
524 mc
[i
-1] = mail_elt(m
, i
);
525 qsort((void *)mc
, (size_t) m
->nmsgs
, sizeof(MESSAGECACHE
*), mailutil_compare_message_id
);
526 for(i
= 1L; i
< m
->nmsgs
;){
531 env1
= mail_fetch_structure(m
, mc
[i
-1]->msgno
, NIL
, NIL
);
532 if(env1
->message_id
== NULL
){
536 for(j
= i
+1; j
<= m
->nmsgs
; j
++){
537 env2
= mail_fetch_structure(m
, mc
[j
-1]->msgno
, NIL
, NIL
);
538 if(env2
->message_id
== NULL
){
541 if(strcmp(env1
->message_id
, env2
->message_id
))
544 fprintf(stdout
, "Message %lu and %lu are duplicates\n",
545 mc
[i
-1]->msgno
, mc
[j
-1]->msgno
);
548 for(k
= i
+1; k
<= j
- 1; k
++){
549 mc
[k
-1]->sequence
= T
;
555 fprintf(stdout
, "Found %lu extra duplicate messages\n", count
);
556 if((sequence
= mailutil_string_sequence(m
)) != NULL
){
557 mail_flag(m
, sequence
, "\\DELETED", ST_SET
);
558 if(count
&& mail_expunge_full(m
, "1:*", NIL
))
559 fprintf (stdout
, "Expunged %lu duplicate messages\n", count
);
562 fs_give((void *)&sequence
);
569 int mailutil_compare_message_id (const void *mcp1
, const void *mcp2
)
571 MESSAGECACHE
*mc1
, *mc2
;
572 ENVELOPE
*env1
, *env2
;
574 mc1
= *(MESSAGECACHE
**) mcp1
;
575 mc2
= *(MESSAGECACHE
**) mcp2
;
577 env1
= mc1
->private.msg
.env
; /* aarrggh direct access inside message cache */
578 env2
= mc2
->private.msg
.env
;
581 return env2
== NULL
? 0 : -1;
582 else if (env2
== NULL
)
584 else if(env1
->message_id
== NULL
)
585 return env2
->message_id
== NULL
? 0 : -1;
586 else if(env2
->message_id
== NULL
)
588 return strcmp(env1
->message_id
, env2
->message_id
);
591 /* Pruning criteria, somewhat extended from mail_criteria()
593 * Returns: search program if parse successful, else NIL
596 SEARCHPGM
*prune_criteria (char *criteria
)
598 SEARCHPGM
*pgm
= NIL
;
599 char *criterion
,*r
,tmp
[MAILTMPLEN
];
601 if (criteria
) { /* only if criteria defined */
602 /* make writeable copy of criteria */
603 criteria
= cpystr (criteria
);
604 /* for each criterion */
605 for (pgm
= mail_newsearchpgm (), criterion
= strtok_r (criteria
," ",&r
);
606 criterion
; (criterion
= strtok_r (NIL
," ",&r
))) {
607 f
= NIL
; /* init then scan the criterion */
608 switch (*ucase (criterion
)) {
609 case 'A': /* possible ALL, ANSWERED */
610 if (!strcmp (criterion
+1,"LL")) f
= T
;
611 else if (!strcmp (criterion
+1,"NSWERED")) f
= pgm
->answered
= T
;
613 case 'B': /* possible BCC, BEFORE, BODY */
614 if (!strcmp (criterion
+1,"CC"))
615 f
= mail_criteria_string (&pgm
->bcc
,&r
);
616 else if (!strcmp (criterion
+1,"EFORE"))
617 f
= mail_criteria_date (&pgm
->before
,&r
);
618 else if (!strcmp (criterion
+1,"ODY"))
619 f
= mail_criteria_string (&pgm
->body
,&r
);
621 case 'C': /* possible CC */
622 if (!strcmp (criterion
+1,"C")) f
= mail_criteria_string (&pgm
->cc
,&r
);
624 case 'D': /* possible DELETED, DRAFT */
625 if (!strcmp (criterion
+1,"ELETED")) f
= pgm
->deleted
= T
;
626 else if (!strcmp (criterion
+1,"RAFT")) f
= pgm
->draft
= T
;
628 case 'F': /* possible FLAGGED, FROM */
629 if (!strcmp (criterion
+1,"LAGGED")) f
= pgm
->flagged
= T
;
630 else if (!strcmp (criterion
+1,"ROM"))
631 f
= mail_criteria_string (&pgm
->from
,&r
);
633 case 'K': /* possible KEYWORD */
634 if (!strcmp (criterion
+1,"EYWORD"))
635 f
= mail_criteria_string (&pgm
->keyword
,&r
);
637 case 'L': /* possible LARGER */
638 if (!strcmp (criterion
+1,"ARGER"))
639 f
= criteria_number (&pgm
->larger
,&r
);
641 case 'N': /* possible NEW */
642 if (!strcmp (criterion
+1,"EW")) f
= pgm
->recent
= pgm
->unseen
= T
;
644 case 'O': /* possible OLD, ON */
645 if (!strcmp (criterion
+1,"LD")) f
= pgm
->old
= T
;
646 else if (!strcmp (criterion
+1,"N"))
647 f
= mail_criteria_date (&pgm
->on
,&r
);
649 case 'R': /* possible RECENT */
650 if (!strcmp (criterion
+1,"ECENT")) f
= pgm
->recent
= T
;
652 case 'S': /* possible SEEN, SENT*, SINCE, SMALLER,
654 if (!strcmp (criterion
+1,"EEN")) f
= pgm
->seen
= T
;
655 else if (!strncmp (criterion
+1,"ENT",3)) {
656 if (!strcmp (criterion
+4,"BEFORE"))
657 f
= mail_criteria_date (&pgm
->sentbefore
,&r
);
658 else if (!strcmp (criterion
+4,"ON"))
659 f
= mail_criteria_date (&pgm
->senton
,&r
);
660 else if (!strcmp (criterion
+4,"SINCE"))
661 f
= mail_criteria_date (&pgm
->sentsince
,&r
);
663 else if (!strcmp (criterion
+1,"INCE"))
664 f
= mail_criteria_date (&pgm
->since
,&r
);
665 else if (!strcmp (criterion
+1,"MALLER"))
666 f
= criteria_number (&pgm
->smaller
,&r
);
667 else if (!strcmp (criterion
+1,"UBJECT"))
668 f
= mail_criteria_string (&pgm
->subject
,&r
);
670 case 'T': /* possible TEXT, TO */
671 if (!strcmp (criterion
+1,"EXT"))
672 f
= mail_criteria_string (&pgm
->text
,&r
);
673 else if (!strcmp (criterion
+1,"O"))
674 f
= mail_criteria_string (&pgm
->to
,&r
);
676 case 'U': /* possible UN* */
677 if (criterion
[1] == 'N') {
678 if (!strcmp (criterion
+2,"ANSWERED")) f
= pgm
->unanswered
= T
;
679 else if (!strcmp (criterion
+2,"DELETED")) f
= pgm
->undeleted
= T
;
680 else if (!strcmp (criterion
+2,"DRAFT")) f
= pgm
->undraft
= T
;
681 else if (!strcmp (criterion
+2,"FLAGGED")) f
= pgm
->unflagged
= T
;
682 else if (!strcmp (criterion
+2,"KEYWORD"))
683 f
= mail_criteria_string (&pgm
->unkeyword
,&r
);
684 else if (!strcmp (criterion
+2,"SEEN")) f
= pgm
->unseen
= T
;
687 default: /* we will barf below */
691 if (!f
) { /* if can't identify criterion */
692 sprintf (tmp
,"Unknown search criterion: %.30s",criterion
);
694 mail_free_searchpgm (&pgm
);
698 /* no longer need copy of criteria */
699 fs_give ((void **) &criteria
);
706 * Accepts: pointer to integer to return
707 * pointer to strtok state
708 * Returns: T if successful, else NIL
711 int criteria_number (unsigned long *number
,char **r
)
715 /* parse the date and return fn if OK */
716 int ret
= (mail_criteria_string (&s
,r
) &&
717 (*number
= strtoul ((char *) s
->text
.data
,&t
,10)) && !*t
) ?
719 if (s
) mail_free_stringlist (&s
);
724 * Accepts: stream open on source
725 * halfopen stream for destination or NIL
726 * destination mailbox name
727 * non-zero to create destination mailbox
728 * non-zero to delete messages from source after copying
730 * Returns: T if success, NIL if error
733 int mbxcopy (MAILSTREAM
*source
,MAILSTREAM
*dest
,char *dst
,int create
,int del
,
736 char *s
,tmp
[MAILTMPLEN
];
741 trycreate
= NIL
; /* no TRYCREATE yet */
742 if (create
) while (!mail_create (dest
,dst
) && (mode
!= mAPPEND
)) {
744 case mPROMPT
: /* prompt user for new name */
746 while (!tmp
[0]) { /* read name */
747 fputs ("alternative name: ",stdout
);
749 fgets (tmp
,MAILTMPLEN
-1,stdin
);
750 if ((s
= strchr (tmp
,'\n')) != NULL
) *s
= '\0';
752 if (ndst
) fs_give ((void **) &ndst
);
755 case mSUFFIX
: /* try again with new suffix */
756 if (ndst
) fs_give ((void **) &ndst
);
757 sprintf (ndst
= (char *) fs_get (strlen (dst
) + strlen (suffix
) + 1),
759 printf ("retry to create %s\n",ndst
);
760 mode
= mPROMPT
; /* switch to prompt mode if name fails */
762 case NIL
: /* not merging */
765 if (ndst
) dst
= ndst
; /* if alternative name given, use it */
771 char *dummymsg
= "Date: Thu, 18 May 2006 00:00 -0700\r\nFrom: dummy@example.com\r\nSubject: dummy\r\n\r\ndummy\r\n";
772 for (i
= 0,len
= 0; i
< NUSERFLAGS
; ++i
)
773 if (source
->user_flags
[i
]) len
+= strlen (source
->user_flags
[i
]) + 1;
774 if (len
) { /* easy if no user flags to copy... */
776 char *tail
= "\\Deleted)";
777 char *flags
= (char *) fs_get (1 + len
+ strlen (tail
) + 1);
778 s
= flags
; *s
++ = '(';
779 for (i
= 0; i
< NUSERFLAGS
; ++i
) if ((t
= source
->user_flags
[i
]) != NULL
) {
780 while (*t
) *s
++ = *t
++;
783 strcpy (s
,tail
); /* terminate flags list */
784 if ((dst
[0] == '#') && ((dst
[1] == 'D') || (dst
[1] == 'd')) &&
785 ((dst
[2] == 'R') || (dst
[2] == 'r')) &&
786 ((dst
[3] == 'I') || (dst
[3] == 'i')) &&
787 ((dst
[4] == 'V') || (dst
[4] == 'v')) &&
788 ((dst
[5] == 'E') || (dst
[5] == 'e')) &&
789 ((dst
[6] == 'R') || (dst
[6] == 'r')) && (dst
[7] == '.') &&
790 (t
= strchr (dst
+8,'/'))) ++t
;
792 INIT (&st
,mail_string
,dummymsg
,strlen (dummymsg
));
793 if (!(mail_append (dest
,dst
,&st
) &&
794 (dest
= mail_open (dest
,t
,debugp
? OP_DEBUG
: NIL
)))) {
795 fs_give ((void **) &flags
);
798 mail_setflag (dest
,"*",flags
);
800 fs_give ((void **) &flags
);
804 if (source
->nmsgs
) { /* non-empty source */
805 if (verbosep
) printf ("%s [%lu message(s)] => %s\n",
806 source
->mailbox
,source
->nmsgs
,dst
);
807 ap
.stream
= source
; /* prepare append package */
809 ap
.msgmax
= source
->nmsgs
;
810 ap
.flags
= ap
.date
= NIL
;
812 /* make sure we have all messages */
813 sprintf (tmp
,"1:%lu",ap
.msgmax
);
814 mail_fetchfast (source
,tmp
);
815 if (mail_append_multiple (dest
,dst
,mm_append
,(void *) &ap
)) {
816 --ap
.msgno
; /* make sure user knows it won */
817 if (verbosep
) printf ("[Ok %lu messages(s)]\n",ap
.msgno
);
818 if (del
&& ap
.msgno
) { /* delete source messages */
819 sprintf (tmp
,"1:%lu",ap
.msgno
);
820 mail_flag (source
,tmp
,"\\Deleted",ST_SET
);
821 /* flush moved messages */
822 mail_expunge (source
);
826 else if ((mode
== mAPPEND
) && trycreate
)
827 ret
= mbxcopy (source
,dest
,dst
,create
,del
,mPROMPT
);
828 else if (verbosep
) puts ("[Failed]");
830 else { /* empty source */
831 if (verbosep
) printf ("%s [empty] => %s\n",source
->mailbox
,dst
);
834 if (ndst
) fs_give ((void **) &ndst
);
839 * Accepts: mail stream
841 * pointer to return flags
842 * pointer to return date
843 * pointer to return message stringstruct
844 * Returns: T on success
847 long mm_append (MAILSTREAM
*stream
,void *data
,char **flags
,char **date
,
850 char *t
,*t1
,tmp
[MAILTMPLEN
];
853 APPENDPACKAGE
*ap
= (APPENDPACKAGE
*) data
;
854 *flags
= *date
= NIL
; /* assume no flags or date */
855 if (ap
->flags
) fs_give ((void **) &ap
->flags
);
856 if (ap
->date
) fs_give ((void **) &ap
->date
);
857 mail_gc (ap
->stream
,GC_TEXTS
);
858 if (++ap
->msgno
<= ap
->msgmax
) {
859 /* initialize flag string */
860 memset (t
= tmp
,0,MAILTMPLEN
);
861 /* output system flags */
862 if ((elt
= mail_elt (ap
->stream
,ap
->msgno
))->seen
) strcat (t
," \\Seen");
863 if (elt
->deleted
) strcat (t
," \\Deleted");
864 if (elt
->flagged
) strcat (t
," \\Flagged");
865 if (elt
->answered
) strcat (t
," \\Answered");
866 if (elt
->draft
) strcat (t
," \\Draft");
867 /* any user flags? */
868 if (!ignorep
&& (u
= elt
->user_flags
)) do
869 if ((t1
= ap
->stream
->user_flags
[find_rightmost_bit (&u
)]) &&
870 (MAILTMPLEN
- ((t
+= strlen (t
)) - tmp
)) > (long) (2 + strlen (t1
))){
871 *t
++ = ' '; /* space delimiter */
872 strcpy (t
,t1
); /* copy the user flag */
874 while (u
); /* until no more user flags */
875 *flags
= ap
->flags
= cpystr (tmp
+ 1);
876 *date
= ap
->date
= cpystr (mail_date (tmp
,elt
));
877 *message
= ap
->message
; /* message stringstruct */
878 INIT (ap
->message
,mstring
,(void *) ap
,elt
->rfc822_size
);
880 else *message
= NIL
; /* all done */
884 /* Co-routines from MAIL library */
887 /* Message matches a search
888 * Accepts: MAIL stream
892 void mm_searched (MAILSTREAM
*stream
,unsigned long msgno
)
898 /* Message exists (i.e. there are that many messages in the mailbox)
899 * Accepts: MAIL stream
903 void mm_exists (MAILSTREAM
*stream
,unsigned long number
)
910 * Accepts: MAIL stream
914 void mm_expunged (MAILSTREAM
*stream
,unsigned long number
)
920 /* Message flags update seen
921 * Accepts: MAIL stream
925 void mm_flags (MAILSTREAM
*stream
,unsigned long number
)
931 * Accepts: MAIL stream
932 * hierarchy delimiter
937 void mm_list (MAILSTREAM
*stream
,int delimiter
,char *name
,long attributes
)
939 /* note destination delimiter */
940 if (ddelim
< 0) ddelim
= delimiter
;
941 /* if got a selectable name */
942 else if (!(attributes
& LATT_NOSELECT
) && *name
)
943 fprintf (f
,"%c%s\n",delimiter
,name
);
947 /* Subscribe mailbox found
948 * Accepts: MAIL stream
949 * hierarchy delimiter
954 void mm_lsub (MAILSTREAM
*stream
,int delimiter
,char *name
,long attributes
)
961 * Accepts: MAIL stream
966 void mm_status (MAILSTREAM
*stream
,char *mailbox
,MAILSTATUS
*status
)
968 if (status
->recent
|| status
->unseen
)
969 printf ("%lu new message(s) (%lu unseen),",status
->recent
,status
->unseen
);
970 else fputs ("No new messages,",stdout
);
971 printf (" %lu total in %s\n",status
->messages
,mailbox
);
974 /* Notification event
975 * Accepts: MAIL stream
980 void mm_notify (MAILSTREAM
*stream
,char *string
,long errflg
)
982 if (!errflg
&& (string
[0] == '[') &&
983 ((string
[1] == 'T') || (string
[1] == 't')) &&
984 ((string
[2] == 'R') || (string
[2] == 'r')) &&
985 ((string
[3] == 'Y') || (string
[3] == 'y')) &&
986 ((string
[4] == 'C') || (string
[4] == 'c')) &&
987 ((string
[5] == 'R') || (string
[5] == 'r')) &&
988 ((string
[6] == 'E') || (string
[6] == 'e')) &&
989 ((string
[7] == 'A') || (string
[7] == 'a')) &&
990 ((string
[8] == 'T') || (string
[8] == 't')) &&
991 ((string
[9] == 'E') || (string
[9] == 'e')) &&
994 mm_log (string
,errflg
); /* just do mm_log action */
998 /* Log an event for the user to see
999 * Accepts: string to log
1003 void mm_log (char *string
,long errflg
)
1007 case NIL
: /* no error */
1008 if (verbosep
) fprintf (stderr
,"[%s]\n",string
);
1010 case PARSE
: /* parsing problem */
1011 case WARN
: /* warning */
1012 fprintf (stderr
,"warning: %s\n",string
);
1014 case ERROR
: /* error */
1016 fprintf (stderr
,"%s\n",string
);
1022 /* Log an event to debugging telemetry
1023 * Accepts: string to log
1026 void mm_dlog (char *string
)
1028 fprintf (stderr
,"%s\n",string
);
1031 /* Get user name and password for this host
1032 * Accepts: parse of network mailbox name
1033 * where to return user name
1034 * where to return password
1038 void mm_login (NETMBX
*mb
,char *username
,char **password
,long trial
)
1040 char *s
,tmp
[MAILTMPLEN
];
1041 sprintf (s
= tmp
,"{%s/%s",mb
->host
,mb
->service
);
1042 if (*mb
->user
) sprintf (tmp
+strlen (tmp
),"/user=%s",
1043 strcpy (username
,mb
->user
));
1044 if (*mb
->authuser
) sprintf (tmp
+strlen (tmp
),"/authuser=%s",mb
->authuser
);
1045 if (*mb
->user
) strcat (s
= tmp
,"} password:");
1047 printf ("%s} username: ",tmp
);
1048 fgets (username
,NETMAXUSER
-1,stdin
);
1049 username
[NETMAXUSER
-1] = '\0';
1050 if ((s
= strchr (username
,'\n')) != NULL
) *s
= '\0';
1053 if(strlen (s
= getpass (s
)) < MAILTMPLEN
) *password
= cpystr(s
);
1056 void mm_login_method (NETMBX
*mb
,char *username
, void *login
,long trial
, char *method
)
1058 if(method
== NULL
) return;
1059 if(strcmp(method
, "XOAUTH2") == 0){
1060 OAUTH2_S
*muinfo
= NULL
; /* mail util info */
1061 char *s
,tmp
[MAILTMPLEN
];
1062 sprintf (s
= tmp
,"{%s/%s",mb
->host
,mb
->service
);
1063 if (*mb
->user
) sprintf (tmp
+strlen (tmp
),"/user=%s",
1064 strcpy (username
,mb
->user
));
1065 if (*mb
->user
) strcat (s
= tmp
,"} access token: ");
1067 printf ("%s} username: ",tmp
);
1068 fgets (username
,NETMAXUSER
-1,stdin
);
1069 username
[NETMAXUSER
-1] = '\0';
1070 if ((s
= strchr (username
,'\n')) != NULL
) *s
= '\0';
1071 printf ("%s} access token: ", tmp
);
1073 fgets (s
,MAILTMPLEN
/2-1,stdin
);
1074 tmp
[MAILTMPLEN
/2-1] = '\0';
1075 if ((s
= strchr (tmp
,'\n')) != NULL
) *s
= '\0';
1077 muinfo
= fs_get(sizeof(OAUTH2_S
));
1078 memset((void *) muinfo
, 0, sizeof(OAUTH2_S
));
1079 muinfo
->access_token
= cpystr(tmp
);
1080 /* STILL MISSING: get expires in info */
1082 login
= (void *) muinfo
;
1087 /* About to enter critical code
1091 void mm_critical (MAILSTREAM
*stream
)
1093 critical
= T
; /* note in critical code */
1097 /* About to exit critical code
1101 void mm_nocritical (MAILSTREAM
*stream
)
1103 critical
= NIL
; /* note not in critical code */
1110 * flag indicating that mailbox may be clobbered
1111 * Returns: T if user wants to abort
1114 long mm_diskerror (MAILSTREAM
*stream
,long errcode
,long serious
)
1120 /* Log a fatal error event
1121 * Accepts: string to log
1124 void mm_fatal (char *string
)
1126 fprintf (stderr
,"FATAL: %s\n",string
);