* New version 2.26
[alpine.git] / imap / src / mailutil / mailutil.c
blobab161bcfc92cc3b94a7dc7ffb42afd4f1e573c22
1 /*
2 * Copyright 2016-2022 Eduardo Chappa
3 */
5 /* ========================================================================
6 * Copyright 2009 Mark Crispin
7 * ========================================================================
8 */
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
31 #include <stdio.h>
32 #include <errno.h>
33 extern int errno; /* just in case */
34 #include "c-client.h"
35 #ifdef SYSCONFIG /* defined in env_unix.h */
36 #include <pwd.h>
37 #endif
39 /* Globals */
41 char *version = "17"; /* edit number */
42 int debugp = NIL; /* flag saying debug */
43 int verbosep = NIL; /* flag saying verbose */
44 int rwcopyp = NIL; /* flag saying readwrite copy (for POP) */
45 int kwcopyp = NIL; /* flag saying keyword copy */
46 int ignorep = NIL; /* flag saying ignore keywords */
47 int critical = NIL; /* flag saying in critical code */
48 int trycreate = NIL; /* [TRYCREATE] seen */
49 char *suffix = NIL; /* suffer merge mode suffix text */
50 int ddelim = -1; /* destination delimiter */
51 FILE *f = NIL;
53 /* Usage strings */
55 char *usage2 = "usage: %s %s\n\n%s\n";
56 char *usage3 = "usage: %s %s %s\n\n%s\n";
57 char *usgchk = "check [MAILBOX]";
58 char *usgcre = "create MAILBOX";
59 char *usgdel = "delete MAILBOX";
60 char *usgren = "rename SOURCE DESTINATION";
61 char *usgdup = "dedup [MAILBOX]";
62 char *usgcpymov = "[-rw[copy]] [-kw[copy]] [-ig[nore]] SOURCE DESTINATION";
63 char *usgappdel = "[-rw[copy]] [-kw[copy]] [-ig[nore]] SOURCE DESTINATION";
64 char *usgprn = "prune mailbox SEARCH_CRITERIA";
65 char *usgxfr = "transfer [-rw[copy]] [-kw[copy]] [-ig[nore]] [-m[erge] m] SOURCE DEST";
66 #ifdef SYSCONFIG
67 char *stdsw = "Standard switches valid with any command:\n\t[-d[ebug]] [-v[erbose]] [-u[ser] userid] [--]";
68 #else
69 char *stdsw = "Standard switches valid with any command:\n\t[-d[ebug]] [-v[erbose]]";
70 #endif
72 /* Merge modes */
74 #define mPROMPT 1
75 #define mAPPEND 2
76 #define mSUFFIX 3
79 /* Function prototypes */
80 void mailutil_add_sequence(char **sequence, size_t *len, unsigned long i, unsigned long j, unsigned long nmsgs);
81 char *mailutil_string_sequence(MAILSTREAM *m);
82 int mailutil_compare_message_id (const void *mcp1, const void *mcp2);
83 int mailutil_dedup (char *mailbox, long options);
84 void ms_init (STRING *s,void *data,unsigned long size);
85 char ms_next (STRING *s);
86 void ms_setpos (STRING *s,unsigned long i);
87 int main (int argc,char *argv[]);
88 SEARCHPGM *prune_criteria (char *criteria);
89 int criteria_number (unsigned long *number,char **r);
90 int mbxcopy (MAILSTREAM *source,MAILSTREAM *dest,char *dst,int create,int del,
91 int mode);
92 long mm_append (MAILSTREAM *stream,void *data,char **flags,char **date,
93 STRING **message);
96 /* Append package */
98 typedef struct append_package {
99 MAILSTREAM *stream; /* source stream */
100 unsigned long msgno; /* current message number */
101 unsigned long msgmax; /* maximum message number */
102 char *flags; /* current flags */
103 char *date; /* message internal date */
104 STRING *message; /* stringstruct of message */
105 } APPENDPACKAGE;
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
119 * size of string
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);
128 #if 0
129 s->size = size; /* message size */
130 #else /* This kludge is necessary because of broken IMAP servers (sigh!) */
131 mail_fetch_text (md->stream,md->msgno,NIL,&s->size,FT_PEEK);
132 s->size += s->data1; /* header + body size */
133 #endif
134 SETPOS (s,0);
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
153 * new position
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... */
172 s->offset = i;
174 /* initial position and size */
175 s->curpos = s->chunk + (i -= s->offset);
176 s->cursize = s->chunksize - i;
179 /* Main program */
181 int main (int argc,char *argv[])
183 MAILSTREAM *source = NIL;
184 MAILSTREAM *dest = NIL;
185 SEARCHPGM *criteria;
186 char c,*s,*dp,*t,*t1,tmp[MAILTMPLEN],mbx[MAILTMPLEN];
187 unsigned long m,len,curlen,start,last;
188 int i;
189 int merge = NIL;
190 int retcode = 1;
191 int moreswitchp = T;
192 char *cmd = NIL;
193 char *src = NIL;
194 char *dst = NIL;
195 char *pgm = argc ? argv[0] : "mailutil";
196 #include "linkage.c"
197 for (i = 1; i < argc; i++) {
198 s = argv[i]; /* pick up argument */
199 /* parse switches */
200 if (moreswitchp && (*s == '-')) {
201 if (!strcmp (s,"-debug") || !strcmp (s,"-d")) debugp = T;
202 else if (!strcmp (s,"-verbose") || !strcmp (s,"-v")) verbosep = T;
203 else if (!strcmp (s,"-rwcopy") || !strcmp (s,"-rw")) rwcopyp = T;
204 else if (!strcmp (s,"-kwcopy") || !strcmp (s,"-kw")) kwcopyp = T;
205 else if (!strcmp (s,"-ignore") || !strcmp (s,"-ig")) ignorep = T;
206 else if ((!strcmp (s,"-merge") || !strcmp (s,"-m")) && (++i < argc)) {
207 if (!strcmp (s = argv[i],"prompt")) merge = mPROMPT;
208 else if (!strcmp (s,"append")) merge = mAPPEND;
209 else if (!strncmp (s,"suffix=",7) && s[7]) {
210 merge = mSUFFIX;
211 suffix = cpystr (s+7);
213 else {
214 printf ("unknown merge option: %s\n",s);
215 exit (retcode);
219 #ifdef SYSCONFIG
220 else if ((!strcmp (s,"-user") || !strcmp (s,"-u")) && (++i < argc)) {
221 struct passwd *pw = getpwnam (s = argv[i]);
222 if (!pw) {
223 printf ("unknown user id: %s\n",argv[i]);
224 exit (retcode);
226 else if (setuid (pw->pw_uid)) {
227 perror ("unable to change user id");
228 exit (retcode);
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);
235 #endif
236 /* -- means no more switches, so mailbox
237 name can start with "-" */
238 else if ((s[1] == '-') && !s[2]) moreswitchp = NIL;
239 else {
240 printf ("unknown switch: %s\n",s);
241 exit (retcode);
244 else if (!cmd) cmd = s; /* first non-switch is command */
245 else if (!src) src = s; /* second non-switch is source */
246 else if (!dst) dst = s; /* third non-switch is destination */
247 else {
248 printf ("unknown argument: %s\n",s);
249 exit (retcode);
252 if (kwcopyp && ignorep) {
253 puts ("-kwcopy and -ignore are mutually exclusive");
254 exit (retcode);
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))
263 retcode = 0;
265 else if (!strcmp (cmd,"check")) { /* check for new messages */
266 if (!src) src = "INBOX";
267 if (dst || merge || rwcopyp || kwcopyp || ignorep)
268 printf (usage2,pgm,usgchk,stdsw);
269 else if (mail_status (source = (*src == '{') ?
270 mail_open (NIL,src,OP_HALFOPEN |
271 (debugp ? OP_DEBUG : NIL)) : NIL,
272 src,SA_MESSAGES | SA_RECENT | SA_UNSEEN))
273 retcode = 0;
275 else if (!strcmp (cmd,"create")) {
276 if (!src || dst || merge || rwcopyp || kwcopyp || ignorep)
277 printf (usage2,pgm,usgcre,stdsw);
278 else if (mail_create (source = (*src == '{') ?
279 mail_open (NIL,src,OP_HALFOPEN |
280 (debugp ? OP_DEBUG : NIL)) : NIL,src))
281 retcode = 0;
283 else if (!strcmp (cmd,"delete")) {
284 if (!src || dst || merge || rwcopyp || kwcopyp || ignorep)
285 printf (usage2,pgm,usgdel,stdsw);
286 else if (mail_delete (source = (*src == '{') ?
287 mail_open (NIL,src,OP_HALFOPEN |
288 (debugp ? OP_DEBUG : NIL)) : NIL,src))
289 retcode = 0;
291 else if (!strcmp (cmd,"rename")) {
292 if (!src || !dst || merge || rwcopyp || kwcopyp || ignorep)
293 printf (usage2,pgm,usgren,stdsw);
294 else if (mail_rename (source = (*src == '{') ?
295 mail_open (NIL,src,OP_HALFOPEN |
296 (debugp ? OP_DEBUG : NIL)) : NIL,src,dst))
297 retcode = 0;
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");
373 else {
374 if (verbosep) puts ("Listing mailboxes...");
375 if (dest) strcpy (strchr (strcpy (tmp,dest->mailbox),'}') + 1,
376 dp = strchr (dst,'}') + 1);
377 else {
378 dp = dst;
379 tmp[0] = '\0';
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,"*");
391 rewind (f);
392 /* read back mailbox names */
393 for (retcode = 0; !retcode && (fgets (tmp,MAILTMPLEN-1,f)); ) {
394 if ((t = strchr (tmp+1,'\n')) != NULL) *t = '\0';
395 for (t = mbx,t1 = dest ? dest->mailbox : "",c = NIL; (c != '}') && *t1;
396 *t++ = c= *t1++);
397 for (t1 = dp; *t1; *t++ = *t1++);
398 /* point to name without delim or netspec */
399 t1 = source ? (strchr (tmp+1,'}') + 1) : tmp + 1;
400 /* src and mbx have different delimiters? */
401 if (ddelim && (ddelim != tmp[0]))
402 while ((c = *t1++) != '\0') { /* swap delimiters then */
403 if (c == ddelim) c = tmp[0] ? tmp[0] : 'x';
404 else if (c == tmp[0]) c = ddelim;
405 *t++ = c;
407 /* easy case */
408 else while (*t1) *t++ = *t1++;
409 *t++ = '\0';
410 if (verbosep) {
411 printf ("Copying %s\n => %s\n",tmp+1,mbx);
412 fflush (stdout);
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);
424 else {
425 printf ("%s version %s.%s\n\n",pgm,CCLIENTVERSION,version);
426 printf (usage2,pgm,"command [switches] arguments",stdsw);
427 printf ("\nCommands:\n %s\n",usgchk);
428 puts (" ;; report number of messages and new messages");
429 printf (" %s\n",usgdup);
430 puts (" ;; removes duplicate messages from a mailbox");
431 printf (" %s\n",usgcre);
432 puts (" ;; create new mailbox");
433 printf (" %s\n",usgdel);
434 puts (" ;; delete existing mailbox");
435 printf (" %s\n",usgren);
436 puts (" ;; rename mailbox to a new name");
437 printf (" copy %s\n",usgcpymov);
438 printf (" move %s\n",usgcpymov);
439 puts (" ;; create new mailbox and copy/move messages");
440 printf (" append %s\n",usgappdel);
441 printf (" appenddelete %s\n",usgappdel);
442 puts (" ;; copy/move messages to existing mailbox");
443 printf (" %s\n",usgprn);
444 puts (" ;; prune mailbox of messages matching criteria");
445 printf (" %s\n",usgxfr);
446 puts (" ;; copy source hierarchy to destination");
447 puts (" ;; -merge modes are prompt, append, or suffix=xxxx");
449 /* close streams */
450 if (source) mail_close (source);
451 if (dest) mail_close (dest);
452 exit (retcode);
453 return retcode; /* stupid compilers */
456 char *mailutil_string_sequence(MAILSTREAM *m)
458 char *rv = NULL;
459 unsigned long i, j;
460 size_t len = 0;
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);
469 i = j;
471 else i++;
473 return rv;
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];
480 size_t needed;
482 if(sequence == NULL)
483 return;
485 if(i == j)
486 sprintf(tmp, "%s%lu", *len == 0L ? "" : ",", i);
487 else if(j == nmsgs)
488 sprintf(tmp, "%s%lu:*", *len == 0L ? "" : ",", i);
489 else
490 sprintf(tmp, "%s%lu:%lu", *len == 0L ? "" : ",", i, j);
491 tmp[sizeof(tmp)-1]='\0';
493 needed = strlen(*sequence ? *sequence : "") + strlen(tmp) + 1;
494 if(needed > *len){
495 fs_resize((void **) sequence, (needed + SEQ_BUF_LEN)*sizeof(char));
496 if(*len == 0L)
497 (*sequence)[0] = '\0';
498 *len = needed + SEQ_BUF_LEN;
500 strcat(*sequence + strlen(*sequence), tmp);
504 int mailutil_dedup (char *mailbox, long options)
506 int rv = 0;
507 MAILSTREAM *m;
509 if((m = mail_open(NIL, mailbox, options)) == NULL)
510 rv = -1;
511 else if(m->nmsgs > 1L){
512 unsigned long i, count = 0L;
513 char *sequence;
514 MESSAGECACHE **mc;
515 ENVELOPE *env1, *env2;
517 if(verbosep)
518 fprintf(stdout, "Opened folder \"%s\" with %lu messages\n", mailbox, m->nmsgs);
519 mail_fetch_overview_sequence(m, "1:*", NIL);
520 for(i = 1L; i <= m->nmsgs; i++)
521 mail_elt(m, i)->sequence = 0;
522 mc = fs_get(m->nmsgs*sizeof(MESSAGECACHE *));
523 for(i = 1L; i <= m->nmsgs; i++)
524 mc[i-1] = mail_elt(m, i);
525 qsort((void *)mc, (size_t) m->nmsgs, sizeof(MESSAGECACHE *), mailutil_compare_message_id);
526 for(i = 1L; i < m->nmsgs;){
527 unsigned long j, k;
528 int done;
530 done = 0;
531 env1 = mail_fetch_structure(m, mc[i-1]->msgno, NIL, NIL);
532 if(env1->message_id == NULL){
533 i++;
534 continue;
536 for(j = i+1; j <= m->nmsgs; j++){
537 env2 = mail_fetch_structure(m, mc[j-1]->msgno, NIL, NIL);
538 if(env2->message_id == NULL){
539 j--; break;
541 if(strcmp(env1->message_id, env2->message_id))
542 break;
543 else if(verbosep)
544 fprintf(stdout, "Message %lu and %lu are duplicates\n",
545 mc[i-1]->msgno, mc[j-1]->msgno);
548 for(k = i+1; k <= j - 1; k++){
549 mc[k-1]->sequence = T;
550 count++;
552 i = j;
554 if(verbosep)
555 fprintf(stdout, "Found %lu extra duplicate messages\n", count);
556 if((sequence = mailutil_string_sequence(m)) != NULL){
557 mail_flag(m, sequence, "\\DELETED", ST_SET);
558 if(count && mail_expunge_full(m, "1:*", NIL))
559 fprintf (stdout, "Expunged %lu duplicate messages\n", count);
560 else
561 rv = -1;
562 fs_give((void *)&sequence);
564 else rv = -1;
566 return rv;
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;
580 if(env1 == NULL)
581 return env2 == NULL ? 0 : -1;
582 else if (env2 == NULL)
583 return 1;
584 else if(env1->message_id == NULL)
585 return env2->message_id == NULL ? 0 : -1;
586 else if(env2->message_id == NULL)
587 return 1;
588 return strcmp(env1->message_id, env2->message_id);
591 /* Pruning criteria, somewhat extended from mail_criteria()
592 * Accepts: 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];
600 int f;
601 if (criteria) { /* only if criteria defined */
602 /* make writeable copy of criteria */
603 criteria = cpystr (criteria);
604 /* for each criterion */
605 for (pgm = mail_newsearchpgm (), criterion = strtok_r (criteria," ",&r);
606 criterion; (criterion = strtok_r (NIL," ",&r))) {
607 f = NIL; /* init then scan the criterion */
608 switch (*ucase (criterion)) {
609 case 'A': /* possible ALL, ANSWERED */
610 if (!strcmp (criterion+1,"LL")) f = T;
611 else if (!strcmp (criterion+1,"NSWERED")) f = pgm->answered = T;
612 break;
613 case 'B': /* possible BCC, BEFORE, BODY */
614 if (!strcmp (criterion+1,"CC"))
615 f = mail_criteria_string (&pgm->bcc,&r);
616 else if (!strcmp (criterion+1,"EFORE"))
617 f = mail_criteria_date (&pgm->before,&r);
618 else if (!strcmp (criterion+1,"ODY"))
619 f = mail_criteria_string (&pgm->body,&r);
620 break;
621 case 'C': /* possible CC */
622 if (!strcmp (criterion+1,"C")) f = mail_criteria_string (&pgm->cc,&r);
623 break;
624 case 'D': /* possible DELETED, DRAFT */
625 if (!strcmp (criterion+1,"ELETED")) f = pgm->deleted = T;
626 else if (!strcmp (criterion+1,"RAFT")) f = pgm->draft = T;
627 break;
628 case 'F': /* possible FLAGGED, FROM */
629 if (!strcmp (criterion+1,"LAGGED")) f = pgm->flagged = T;
630 else if (!strcmp (criterion+1,"ROM"))
631 f = mail_criteria_string (&pgm->from,&r);
632 break;
633 case 'K': /* possible KEYWORD */
634 if (!strcmp (criterion+1,"EYWORD"))
635 f = mail_criteria_string (&pgm->keyword,&r);
636 break;
637 case 'L': /* possible LARGER */
638 if (!strcmp (criterion+1,"ARGER"))
639 f = criteria_number (&pgm->larger,&r);
641 case 'N': /* possible NEW */
642 if (!strcmp (criterion+1,"EW")) f = pgm->recent = pgm->unseen = T;
643 break;
644 case 'O': /* possible OLD, ON */
645 if (!strcmp (criterion+1,"LD")) f = pgm->old = T;
646 else if (!strcmp (criterion+1,"N"))
647 f = mail_criteria_date (&pgm->on,&r);
648 break;
649 case 'R': /* possible RECENT */
650 if (!strcmp (criterion+1,"ECENT")) f = pgm->recent = T;
651 break;
652 case 'S': /* possible SEEN, SENT*, SINCE, SMALLER,
653 SUBJECT */
654 if (!strcmp (criterion+1,"EEN")) f = pgm->seen = T;
655 else if (!strncmp (criterion+1,"ENT",3)) {
656 if (!strcmp (criterion+4,"BEFORE"))
657 f = mail_criteria_date (&pgm->sentbefore,&r);
658 else if (!strcmp (criterion+4,"ON"))
659 f = mail_criteria_date (&pgm->senton,&r);
660 else if (!strcmp (criterion+4,"SINCE"))
661 f = mail_criteria_date (&pgm->sentsince,&r);
663 else if (!strcmp (criterion+1,"INCE"))
664 f = mail_criteria_date (&pgm->since,&r);
665 else if (!strcmp (criterion+1,"MALLER"))
666 f = criteria_number (&pgm->smaller,&r);
667 else if (!strcmp (criterion+1,"UBJECT"))
668 f = mail_criteria_string (&pgm->subject,&r);
669 break;
670 case 'T': /* possible TEXT, TO */
671 if (!strcmp (criterion+1,"EXT"))
672 f = mail_criteria_string (&pgm->text,&r);
673 else if (!strcmp (criterion+1,"O"))
674 f = mail_criteria_string (&pgm->to,&r);
675 break;
676 case 'U': /* possible UN* */
677 if (criterion[1] == 'N') {
678 if (!strcmp (criterion+2,"ANSWERED")) f = pgm->unanswered = T;
679 else if (!strcmp (criterion+2,"DELETED")) f = pgm->undeleted = T;
680 else if (!strcmp (criterion+2,"DRAFT")) f = pgm->undraft = T;
681 else if (!strcmp (criterion+2,"FLAGGED")) f = pgm->unflagged = T;
682 else if (!strcmp (criterion+2,"KEYWORD"))
683 f = mail_criteria_string (&pgm->unkeyword,&r);
684 else if (!strcmp (criterion+2,"SEEN")) f = pgm->unseen = T;
686 break;
687 default: /* we will barf below */
688 break;
691 if (!f) { /* if can't identify criterion */
692 sprintf (tmp,"Unknown search criterion: %.30s",criterion);
693 MM_LOG (tmp,ERROR);
694 mail_free_searchpgm (&pgm);
695 break;
698 /* no longer need copy of criteria */
699 fs_give ((void **) &criteria);
701 return pgm;
705 /* Parse a number
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)
713 char *t;
714 STRINGLIST *s = NIL;
715 /* parse the date and return fn if OK */
716 int ret = (mail_criteria_string (&s,r) &&
717 (*number = strtoul ((char *) s->text.data,&t,10)) && !*t) ?
718 T : NIL;
719 if (s) mail_free_stringlist (&s);
720 return ret;
723 /* Copy mailbox
724 * Accepts: stream open on source
725 * halfopen stream for destination or NIL
726 * destination mailbox name
727 * non-zero to create destination mailbox
728 * non-zero to delete messages from source after copying
729 * merge mode
730 * Returns: T if success, NIL if error
733 int mbxcopy (MAILSTREAM *source,MAILSTREAM *dest,char *dst,int create,int del,
734 int mode)
736 char *s,tmp[MAILTMPLEN];
737 APPENDPACKAGE ap;
738 STRING st;
739 char *ndst = NIL;
740 int ret = NIL;
741 trycreate = NIL; /* no TRYCREATE yet */
742 if (create) while (!mail_create (dest,dst) && (mode != mAPPEND)) {
743 switch (mode) {
744 case mPROMPT: /* prompt user for new name */
745 tmp[0] = '\0';
746 while (!tmp[0]) { /* read name */
747 fputs ("alternative name: ",stdout);
748 fflush (stdout);
749 fgets (tmp,MAILTMPLEN-1,stdin);
750 if ((s = strchr (tmp,'\n')) != NULL) *s = '\0';
752 if (ndst) fs_give ((void **) &ndst);
753 ndst = cpystr (tmp);
754 break;
755 case mSUFFIX: /* try again with new suffix */
756 if (ndst) fs_give ((void **) &ndst);
757 sprintf (ndst = (char *) fs_get (strlen (dst) + strlen (suffix) + 1),
758 "%s%s",dst,suffix);
759 printf ("retry to create %s\n",ndst);
760 mode = mPROMPT; /* switch to prompt mode if name fails */
761 break;
762 case NIL: /* not merging */
763 return NIL;
765 if (ndst) dst = ndst; /* if alternative name given, use it */
768 if (kwcopyp) {
769 int i;
770 size_t len;
771 char *dummymsg = "Date: Thu, 18 May 2006 00:00 -0700\r\nFrom: dummy@example.com\r\nSubject: dummy\r\n\r\ndummy\r\n";
772 for (i = 0,len = 0; i < NUSERFLAGS; ++i)
773 if (source->user_flags[i]) len += strlen (source->user_flags[i]) + 1;
774 if (len) { /* easy if no user flags to copy... */
775 char *t;
776 char *tail = "\\Deleted)";
777 char *flags = (char *) fs_get (1 + len + strlen (tail) + 1);
778 s = flags; *s++ = '(';
779 for (i = 0; i < NUSERFLAGS; ++i) if ((t = source->user_flags[i]) != NULL) {
780 while (*t) *s++ = *t++;
781 *s++ = ' ';
783 strcpy (s,tail); /* terminate flags list */
784 if ((dst[0] == '#') && ((dst[1] == 'D') || (dst[1] == 'd')) &&
785 ((dst[2] == 'R') || (dst[2] == 'r')) &&
786 ((dst[3] == 'I') || (dst[3] == 'i')) &&
787 ((dst[4] == 'V') || (dst[4] == 'v')) &&
788 ((dst[5] == 'E') || (dst[5] == 'e')) &&
789 ((dst[6] == 'R') || (dst[6] == 'r')) && (dst[7] == '.') &&
790 (t = strchr (dst+8,'/'))) ++t;
791 else t = dst;
792 INIT (&st,mail_string,dummymsg,strlen (dummymsg));
793 if (!(mail_append (dest,dst,&st) &&
794 (dest = mail_open (dest,t,debugp ? OP_DEBUG : NIL)))) {
795 fs_give ((void **) &flags);
796 return NIL;
798 mail_setflag (dest,"*",flags);
799 mail_expunge (dest);
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 */
808 ap.msgno = 0;
809 ap.msgmax = source->nmsgs;
810 ap.flags = ap.date = NIL;
811 ap.message = &st;
812 /* make sure we have all messages */
813 sprintf (tmp,"1:%lu",ap.msgmax);
814 mail_fetchfast (source,tmp);
815 if (mail_append_multiple (dest,dst,mm_append,(void *) &ap)) {
816 --ap.msgno; /* make sure user knows it won */
817 if (verbosep) printf ("[Ok %lu messages(s)]\n",ap.msgno);
818 if (del && ap.msgno) { /* delete source messages */
819 sprintf (tmp,"1:%lu",ap.msgno);
820 mail_flag (source,tmp,"\\Deleted",ST_SET);
821 /* flush moved messages */
822 mail_expunge (source);
824 ret = T;
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);
832 ret = T;
834 if (ndst) fs_give ((void **) &ndst);
835 return ret;
838 /* Append callback
839 * Accepts: mail stream
840 * append package
841 * pointer to return flags
842 * pointer to return date
843 * pointer to return message stringstruct
844 * Returns: T on success
847 long mm_append (MAILSTREAM *stream,void *data,char **flags,char **date,
848 STRING **message)
850 char *t,*t1,tmp[MAILTMPLEN];
851 unsigned long u;
852 MESSAGECACHE *elt;
853 APPENDPACKAGE *ap = (APPENDPACKAGE *) data;
854 *flags = *date = NIL; /* assume no flags or date */
855 if (ap->flags) fs_give ((void **) &ap->flags);
856 if (ap->date) fs_give ((void **) &ap->date);
857 mail_gc (ap->stream,GC_TEXTS);
858 if (++ap->msgno <= ap->msgmax) {
859 /* initialize flag string */
860 memset (t = tmp,0,MAILTMPLEN);
861 /* output system flags */
862 if ((elt = mail_elt (ap->stream,ap->msgno))->seen) strcat (t," \\Seen");
863 if (elt->deleted) strcat (t," \\Deleted");
864 if (elt->flagged) strcat (t," \\Flagged");
865 if (elt->answered) strcat (t," \\Answered");
866 if (elt->draft) strcat (t," \\Draft");
867 /* any user flags? */
868 if (!ignorep && (u = elt->user_flags)) do
869 if ((t1 = ap->stream->user_flags[find_rightmost_bit (&u)]) &&
870 (MAILTMPLEN - ((t += strlen (t)) - tmp)) > (long) (2 + strlen (t1))){
871 *t++ = ' '; /* space delimiter */
872 strcpy (t,t1); /* copy the user flag */
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 */
881 return LONGT;
884 /* Co-routines from MAIL library */
887 /* Message matches a search
888 * Accepts: MAIL stream
889 * message number
892 void mm_searched (MAILSTREAM *stream,unsigned long msgno)
894 /* dummy routine */
898 /* Message exists (i.e. there are that many messages in the mailbox)
899 * Accepts: MAIL stream
900 * message number
903 void mm_exists (MAILSTREAM *stream,unsigned long number)
905 /* dummy routine */
909 /* Message expunged
910 * Accepts: MAIL stream
911 * message number
914 void mm_expunged (MAILSTREAM *stream,unsigned long number)
916 /* dummy routine */
920 /* Message flags update seen
921 * Accepts: MAIL stream
922 * message number
925 void mm_flags (MAILSTREAM *stream,unsigned long number)
927 /* dummy routine */
930 /* Mailbox found
931 * Accepts: MAIL stream
932 * hierarchy delimiter
933 * mailbox name
934 * mailbox attributes
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
950 * mailbox name
951 * mailbox attributes
954 void mm_lsub (MAILSTREAM *stream,int delimiter,char *name,long attributes)
956 /* dummy routine */
960 /* Mailbox status
961 * Accepts: MAIL stream
962 * mailbox name
963 * mailbox status
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
976 * string to log
977 * error flag
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')) &&
992 (string[10] == ']'))
993 trycreate = T;
994 mm_log (string,errflg); /* just do mm_log action */
998 /* Log an event for the user to see
999 * Accepts: string to log
1000 * error flag
1003 void mm_log (char *string,long errflg)
1005 switch (errflg) {
1006 case BYE:
1007 case NIL: /* no error */
1008 if (verbosep) fprintf (stderr,"[%s]\n",string);
1009 break;
1010 case PARSE: /* parsing problem */
1011 case WARN: /* warning */
1012 fprintf (stderr,"warning: %s\n",string);
1013 break;
1014 case ERROR: /* error */
1015 default:
1016 fprintf (stderr,"%s\n",string);
1017 break;
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
1035 * trial count
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:");
1046 else {
1047 printf ("%s} username: ",tmp);
1048 fgets (username,NETMAXUSER-1,stdin);
1049 username[NETMAXUSER-1] = '\0';
1050 if ((s = strchr (username,'\n')) != NULL) *s = '\0';
1051 s = "password: ";
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: ");
1066 else {
1067 printf ("%s} username: ",tmp);
1068 fgets (username,NETMAXUSER-1,stdin);
1069 username[NETMAXUSER-1] = '\0';
1070 if ((s = strchr (username,'\n')) != NULL) *s = '\0';
1071 printf ("%s} access token: ", tmp);
1073 fgets (s,MAILTMPLEN/2-1,stdin);
1074 tmp[MAILTMPLEN/2-1] = '\0';
1075 if ((s = strchr (tmp,'\n')) != NULL) *s = '\0';
1076 if(tmp[0]){
1077 muinfo = fs_get(sizeof(OAUTH2_S));
1078 memset((void *) muinfo, 0, sizeof(OAUTH2_S));
1079 muinfo->access_token = cpystr(tmp);
1080 /* STILL MISSING: get expires in info */
1082 login = (void *) muinfo;
1087 /* About to enter critical code
1088 * Accepts: stream
1091 void mm_critical (MAILSTREAM *stream)
1093 critical = T; /* note in critical code */
1097 /* About to exit critical code
1098 * Accepts: stream
1101 void mm_nocritical (MAILSTREAM *stream)
1103 critical = NIL; /* note not in critical code */
1107 /* Disk error found
1108 * Accepts: stream
1109 * system error code
1110 * flag indicating that mailbox may be clobbered
1111 * Returns: T if user wants to abort
1114 long mm_diskerror (MAILSTREAM *stream,long errcode,long serious)
1116 return T;
1120 /* Log a fatal error event
1121 * Accepts: string to log
1124 void mm_fatal (char *string)
1126 fprintf (stderr,"FATAL: %s\n",string);