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