1 /* fetchmail.c
2 * Get mail using the pop3 protocol.
3 * Format the mail in ascii, or in html.
4 * Unpack attachments.
5 * This file is part of the edbrowse project, released under GPL.
6 */
7
8 #include "eb.h"
9
10 #ifdef _MSC_VER
11 #include "vsprtf.h"
12 #endif
13
14 #define MHLINE 400 /* length of a mail header line */
15 /* headers and other information about an email */
16 struct MHINFO {
17 struct MHINFO *next, *prev;
18 struct listHead components;
19 char *start, *end;
20 char subject[MHLINE + 4];
21 char to[MHLINE];
22 char from[MHLINE];
23 char reply[MHLINE];
24 char date[MHLINE];
25 char boundary[MHLINE];
26 int boundlen;
27 /* recipients and cc recipients */
28 char *tolist, *cclist;
29 int tolen, cclen;
30 char mid[MHLINE]; /* message id */
31 char ref[MHLINE]; /* references */
32 char cfn[MHLINE]; /* content file name */
33 uchar ct, ce; /* content type, content encoding */
34 bool andOthers;
35 bool doAttach;
36 bool atimage;
37 bool pgp;
38 uchar error64;
39 };
40
41 static int nattach; /* number of attachments */
42 static int nimages; /* number of attached images */
43 static char *firstAttach; /* name of first file */
44 static bool mailIsHtml;
45 static char *fm; /* formatted mail string */
46 static int fm_l;
47 static struct MHINFO *lastMailInfo;
48 static char *lastMailText;
49
freeMailInfo(struct MHINFO * w)50 static void freeMailInfo(struct MHINFO *w)
51 {
52 struct MHINFO *v;
53 while (!listIsEmpty(&w->components)) {
54 v = w->components.next;
55 delFromList(v);
56 freeMailInfo(v);
57 }
58 nzFree(w->tolist);
59 nzFree(w->cclist);
60 nzFree(w);
61 } /* freeMailInfo */
62
63 static bool ignoreImages;
64
writeAttachment(struct MHINFO * w)65 static void writeAttachment(struct MHINFO *w)
66 {
67 const char *atname;
68 if ((ismc | ignoreImages) && w->atimage)
69 return; /* image ignored */
70 if (w->pgp)
71 return; /* Ignore PGP signatures. */
72 if (w->error64 == BAD_BASE64_DECODE)
73 i_printf(MSG_Abbreviated);
74 if (w->start == w->end) {
75 i_printf(MSG_AttEmpty);
76 if (w->cfn[0])
77 printf(" %s", w->cfn);
78 nl();
79 atname = "x";
80 } else {
81 i_printf(MSG_Att);
82 atname = getFileName(MSG_FileName, (w->cfn[0] ? w->cfn : 0),
83 true, false);
84 /* X is like x, but deletes all future images */
85 if (stringEqual(atname, "X")) {
86 atname = "x";
87 ignoreImages = true;
88 }
89 }
90 if (!ismc && stringEqual(atname, "e")) {
91 int cx, svcx = context;
92 for (cx = 1; cx < MAXSESSION; ++cx)
93 if (!sessionList[cx].lw)
94 break;
95 if (cx == MAXSESSION) {
96 i_printf(MSG_AttNoBuffer);
97 } else {
98 cxSwitch(cx, false);
99 i_printf(MSG_SessionX, cx);
100 if (!addTextToBuffer
101 ((pst) w->start, w->end - w->start, 0, false))
102 i_printf(MSG_AttNoCopy, cx);
103 else if (w->cfn[0])
104 cf->fileName = cloneString(w->cfn);
105 cxSwitch(svcx, false); /* back to where we were */
106 }
107 } else if (!stringEqual(atname, "x")) {
108 int fh =
109 open(atname, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC,
110 MODE_rw);
111 if (fh < 0) {
112 i_printf(MSG_AttNoSave, atname);
113 if (ismc)
114 exit(1);
115 } else {
116 int nb = w->end - w->start;
117 if (write(fh, w->start, nb) < nb) {
118 i_printf(MSG_AttNoWrite, atname);
119 if (ismc)
120 exit(1);
121 }
122 close(fh);
123 }
124 }
125 } /* writeAttachment */
126
writeAttachments(struct MHINFO * w)127 static void writeAttachments(struct MHINFO *w)
128 {
129 struct MHINFO *v;
130 if (w->doAttach) {
131 writeAttachment(w);
132 } else {
133 foreach(v, w->components)
134 writeAttachments(v);
135 }
136 } /* writeAttachments */
137
138 /* string to hold the returned data from the mail server */
139 static char *mailstring;
140 static int mailstring_l;
141 static char *mailbox_url, *message_url;
142
143 static int imapfetch = 100;
144 static bool earliest;
145
setLimit(const char * t)146 static void setLimit(const char *t)
147 {
148 char early = false;
149 skipWhite(&t);
150 if(*t == '-')
151 early = true, ++t;
152 if (!isdigit(*t)) {
153 i_puts(MSG_NumberExpected);
154 return;
155 }
156 imapfetch = atoi(t);
157 earliest = early;
158 if (imapfetch < 10)
159 imapfetch = 10;
160 i_printf(MSG_FetchN, imapfetch);
161 } /* setLimit */
162
163 /* mail message in a folder */
164 struct MIF {
165 int uid;
166 int seqno;
167 int size;
168 char *cbase; /* allocated string containing the following */
169 char *subject, *from, *reply;
170 char *refer;
171 time_t sent;
172 bool seen, gone;
173 };
174
175 /* folders at the top of an imap system */
176 static struct FOLDER {
177 const char *name;
178 const char *path;
179 bool children;
180 int nmsgs; /* number of messages in this folder */
181 int nfetch; /* how many to fetch */
182 int unread; /* how many not yet seen */
183 int start;
184 int uidnext; /* uid of next message */
185 struct MIF *mlist; /* allocated */
186 } *topfolders;
187
188 static int n_folders;
189 static char *tf_cbase; /* base of strings for folder names and paths */
190 static bool move_capable = false;
191
192 static void examineFolder(CURL * handle, struct FOLDER *f, bool dostats);
193
194 /* This routine mucks with the passed in string, which was allocated
195 * to receive data from the imap server. So leave it allocated. */
setFolders(CURL * handle)196 static void setFolders(CURL * handle)
197 {
198 struct FOLDER *f;
199 char *s, *t;
200 char *child, *lbrk;
201 char qc; /* quote character */
202 int i;
203
204 // Reset things, in case called on refresh
205 nzFree(topfolders);
206 topfolders = 0;
207 nzFree(tf_cbase);
208 tf_cbase = 0;
209 n_folders = 0;
210
211 s = mailstring;
212 while ((t = strstr(s, "LIST (\\"))) {
213 s = t + 7;
214 ++n_folders;
215 }
216
217 topfolders = allocZeroMem(n_folders * sizeof(struct FOLDER));
218
219 f = topfolders;
220 s = mailstring;
221 while ((t = strstr(s, "LIST (\\"))) {
222 s = t + 6;
223 lbrk = strpbrk(s, "\r\n"); // line break
224 if (!lbrk)
225 continue;
226 child = strstr(s, "Children");
227 if (child && child < lbrk) {
228 /* HasChildren or HasNoChildren */
229 f->children = (child[-1] == 's');
230 t = child + 8;
231 } else {
232 // Try another one.
233 child = strstr(s, "Inferiors");
234 if (!child || child > lbrk)
235 continue;
236 t = child + 10;
237 }
238 while (*t == ' ')
239 ++t;
240 while (*child != '\\')
241 --child;
242 if (child < s) /* should never happen */
243 child = s;
244 strmove(child, t);
245 lbrk = strpbrk(s, "\r\n"); // recalculate
246 if (*s == '\\') { /* there's a name */
247 ++s;
248 t = strchr(s, ')');
249 if (!t || t > lbrk)
250 continue;
251 while (t > s && t[-1] == ' ')
252 --t;
253 *t = 0;
254 f->name = s;
255 s = t + 1;
256 /* the null folder at the top, like /, isn't really a folder. */
257 if (stringEqual(f->name, "Noselect"))
258 continue;
259 } else
260 f->name = emptyString;
261 /* now get the path */
262 t = lbrk;
263 qc = ' ';
264 s = t - 1;
265 if (*s == '"')
266 qc = '"', --s, --t;
267 for (; s > child && *s != qc; --s) ;
268 if (s == child)
269 continue;
270 f->path = ++s;
271 *t = 0;
272 if (s == t)
273 continue;
274 s = t + 1;
275 /* successfully built this folder, move on to the next one */
276 ++f;
277 }
278
279 n_folders = f - topfolders;
280
281 /* You don't dare free mailstring, because it's pieces
282 * are now part of the folders, name and path etc. */
283 tf_cbase = mailstring;
284 mailstring = 0;
285
286 f = topfolders;
287 for (i = 0; i < n_folders; ++i, ++f)
288 examineFolder(handle, f, true);
289 } /* setFolders */
290
folderByName(char * line)291 static struct FOLDER *folderByName(char *line)
292 {
293 int i, j;
294 struct FOLDER *f;
295 int cnt = 0;
296
297 stripWhite(line);
298 if (!line[0])
299 return 0;
300
301 i = stringIsNum(line);
302 if (i > 0 && i <= n_folders)
303 return topfolders + i - 1;
304
305 f = topfolders;
306 for (i = 0; i < n_folders; ++i, ++f)
307 if (strstrCI(f->path, line))
308 ++cnt, j = i;
309 if (cnt == 1)
310 return topfolders + j;
311 if (cnt)
312 i_printf(MSG_ManyFolderMatch, line);
313 else
314 i_printf(MSG_NoFolderMatch, line);
315 return 0;
316 } /* folderByName */
317
318 /* data block for the curl ccallback write function in http.c */
319 static struct i_get callback_data;
320
getMailData(CURL * handle)321 static CURLcode getMailData(CURL * handle)
322 {
323 static bool first_call = true;
324 CURLcode res;
325 callback_data.buffer = initString(&callback_data.length);
326 callback_data.move_capable = false;
327 res = curl_easy_perform(handle);
328 mailstring = callback_data.buffer;
329 mailstring_l = callback_data.length;
330 callback_data.buffer = 0;
331 if (first_call) {
332 move_capable = callback_data.move_capable;
333 if (debugLevel < 4)
334 curl_easy_setopt(handle, CURLOPT_VERBOSE, 0);
335 debugPrint(3, "imap is %smove capable",
336 (move_capable ? "" : "not "));
337 first_call = false;
338 }
339 return res;
340 }
341
342 /* imap emails come in through the headers, not the data.
343 * No kidding! I don't understand it either.
344 * This callback function doesn't use a data block, mailstring is assumed. */
imap_header_callback(char * i,size_t size,size_t nitems,void * data)345 static size_t imap_header_callback(char *i, size_t size,
346 size_t nitems, void *data)
347 {
348 size_t b = nitems * size;
349 int dots1, dots2;
350 dots1 = mailstring_l / CHUNKSIZE;
351 stringAndBytes(&mailstring, &mailstring_l, i, b);
352 dots2 = mailstring_l / CHUNKSIZE;
353 if (dots1 < dots2) {
354 for (; dots1 < dots2; ++dots1)
355 putchar('.');
356 fflush(stdout);
357 }
358 return b;
359 } /* imap_header_callback */
360
361 /* after the email has been fetched via pop3 or imap */
undosOneMessage(void)362 static void undosOneMessage(void)
363 {
364 int j, k;
365
366 if (mailstring_l >= CHUNKSIZE)
367 nl(); /* We printed dots, so we terminate them with newline */
368
369 /* Remove DOS newlines. */
370 for (j = k = 0; j < mailstring_l; j++) {
371 if (mailstring[j] == '\r' && j < mailstring_l - 1
372 && mailstring[j + 1] == '\n')
373 continue;
374 mailstring[k++] = mailstring[j];
375 }
376 mailstring_l = k;
377 mailstring[k] = 0;
378 } /* undosOneMessage */
379
380 static char presentMail(void);
381 static void envelopes(CURL * handle, struct FOLDER *f);
382 static void isoDecode(char *vl, char **vrp);
383
cleanFolder(struct FOLDER * f)384 static void cleanFolder(struct FOLDER *f)
385 {
386 int j;
387 struct MIF *mif;
388 if (!f->mlist)
389 return;
390 mif = f->mlist;
391 for (j = 0; j < f->nfetch; ++j, ++mif)
392 nzFree(mif->cbase);
393 nzFree(f->mlist);
394 f->mlist = NULL;
395 f->nmsgs = f->nfetch = f->unread = 0;
396 } /* cleanFolder */
397
398 /* search through imap server for a particular string */
399 /* Return true if the search ran successfully and found some messages. */
imapSearch(CURL * handle,struct FOLDER * f,char * line)400 static bool imapSearch(CURL * handle, struct FOLDER *f, char *line)
401 {
402 char searchtype = 's';
403 char *t, *u;
404 CURLcode res;
405 int cnt;
406 struct MIF *mif;
407 char cust_cmd[200];
408
409 if (*line && line[1] == ' ' && strchr("sfb", *line)) {
410 searchtype = *line;
411 line += 2;
412 } else if (line[0] && !isspace(line[0]) &&
413 (isspace(line[1]) || !line[1])) {
414 i_puts(MSG_ImapSearchHelp);
415 return false;
416 }
417
418 stripWhite(line);
419 if (!line[0]) {
420 i_puts(MSG_Empty);
421 return false;
422 }
423
424 if (strchr(line, '"')) {
425 i_puts(MSG_SearchQuote);
426 return false;
427 }
428
429 strcpy(cust_cmd, "SEARCH ");
430 if (cons_utf8)
431 strcat(cust_cmd, "CHARSET UTF-8 ");
432 if (searchtype == 's')
433 strcat(cust_cmd, "SUBJECT");
434 if (searchtype == 'f')
435 strcat(cust_cmd, "FROM");
436 if (searchtype == 'b')
437 strcat(cust_cmd, "BODY");
438 strcat(cust_cmd, " \"");
439 strcat(cust_cmd, line);
440 strcat(cust_cmd, "\"");
441 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cust_cmd);
442 res = getMailData(handle);
443 if (res != CURLE_OK) {
444 nzFree(mailstring);
445 ebcurl_setError(res, mailbox_url, 1, emptyString);
446 return false;
447 }
448
449 t = strstr(mailstring, "SEARCH ");
450 if (!t) {
451 none:
452 nzFree(mailstring);
453 i_puts(MSG_NoMatch);
454 return false;
455 }
456 t += 6;
457 cnt = 0;
458 while (*t == ' ')
459 ++t;
460 u = t;
461 while (*u) {
462 if (!isdigit(*u))
463 break;
464 ++cnt;
465 while (isdigit(*u))
466 ++u;
467 skipWhite2(&u);
468 }
469 if (!cnt)
470 goto none;
471
472 cleanFolder(f);
473
474 f->nmsgs = f->nfetch = cnt;
475 if (cnt > imapfetch)
476 f->nfetch = imapfetch;
477 f->mlist = allocZeroMem(sizeof(struct MIF) * f->nfetch);
478 mif = f->mlist;
479 u = t;
480 while (*u) {
481 int seqno = strtol(u, &u, 10);
482 if (cnt <= f->nfetch) {
483 mif->seqno = seqno;
484 ++mif;
485 }
486 --cnt;
487 skipWhite2(&u);
488 }
489
490 envelopes(handle, f);
491
492 return true;
493 } /* imapSearch */
494
printEnvelope(const struct MIF * mif)495 static void printEnvelope(const struct MIF *mif)
496 {
497 char *envp; // print the envelope concisely
498 char *envp_end;
499 int envp_l;
500 envp = initString(&envp_l);
501 #if 0
502 if (!mif->seen)
503 stringAndChar(&envp, &envp_l, '*');
504 #endif
505 stringAndString(&envp, &envp_l, mif->from);
506 stringAndString(&envp, &envp_l, ": ");
507 stringAndString(&envp, &envp_l, mif->subject);
508 if (mif->sent) {
509 stringAndChar(&envp, &envp_l, ' ');
510 stringAndString(&envp, &envp_l, conciseTime(mif->sent));
511 }
512 stringAndChar(&envp, &envp_l, ' ');
513 stringAndString(&envp, &envp_l, conciseSize(mif->size));
514 envp_end = envp + envp_l;
515 isoDecode(envp, &envp_end);
516 *envp_end = 0;
517 // Resulting line could contain utf8, use the portable puts routine.
518 eb_puts(envp);
519 nzFree(envp);
520 }
521
viewAll(struct FOLDER * f)522 static void viewAll(struct FOLDER *f)
523 {
524 int j;
525 struct MIF *mif = f->mlist;
526 for (j = 0; j < f->nfetch; ++j, ++mif) {
527 if (!mif->gone)
528 printEnvelope(mif);
529 }
530 }
531
532 // 0 ok, -1 curl error, 1 something was deleted
bulkMoveDelete(CURL * handle,struct FOLDER * f,struct MIF * this_mif,char key,char subkey,struct FOLDER * destination)533 static int bulkMoveDelete(CURL * handle, struct FOLDER *f,
534 struct MIF *this_mif, char key, char subkey,
535 struct FOLDER *destination)
536 {
537 CURLcode res = CURLE_OK;
538 char cust_cmd[80];
539 int j;
540 struct MIF *mif = f->mlist;
541 int mcnt = 0; // move count
542 char *t, *fromline = 0;
543 bool yesdel = false;
544
545 if (key == 'f') {
546 fromline = this_mif->from;
547 if (!fromline[0]) {
548 i_puts(MSG_NoFromLine);
549 return 0;
550 }
551 }
552
553 for (j = 0; j < f->nfetch; ++j, ++mif) {
554 bool delflag = false;
555 if (mif->gone)
556 continue;
557 mif->seqno -= mcnt;
558 if (fromline && !stringEqual(fromline, mif->from))
559 continue;
560 if (subkey == 'm') {
561 if (asprintf(&t, "%s %d \"%s\"",
562 (move_capable ? "MOVE" : "COPY"),
563 mif->seqno, destination->path) == -1)
564 i_printfExit(MSG_MemAllocError, 24);
565 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, t);
566 free(t);
567 res = getMailData(handle);
568 nzFree(mailstring);
569 if (res != CURLE_OK)
570 goto abort;
571 // The move command shifts all the sequence numbers down.
572 if (move_capable) {
573 mif->gone = true;
574 ++mcnt;
575 } else {
576 delflag = true;
577 }
578 }
579 if (subkey == 'd' || delflag) {
580 sprintf(cust_cmd, "STORE %d +Flags \\Deleted",
581 mif->seqno);
582 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST,
583 cust_cmd);
584 res = getMailData(handle);
585 nzFree(mailstring);
586 if (res != CURLE_OK)
587 goto abort;
588 mif->gone = true;
589 yesdel = true;
590 }
591 }
592
593 return yesdel;
594
595 abort:
596 return -1;
597 }
598
599 /* scan through the messages in a folder */
scanFolder(CURL * handle,struct FOLDER * f)600 static void scanFolder(CURL * handle, struct FOLDER *f)
601 {
602 struct MIF *mif;
603 int j, rc;
604 CURLcode res = CURLE_OK;
605 char *t;
606 char key;
607 char cust_cmd[80];
608 char inputline[80];
609 bool yesdel = false, delflag;
610
611 if (!f->nmsgs) {
612 i_puts(MSG_NoMessages);
613 return;
614 }
615
616 /* tell the server to descend into this folder */
617 if (asprintf(&t, "SELECT \"%s\"", f->path) == -1)
618 i_printfExit(MSG_MemAllocError, strlen(f->path) + 12);
619 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, t);
620 free(t);
621 res = getMailData(handle);
622 nzFree(mailstring);
623 if (res != CURLE_OK) {
624 abort:
625 ebcurl_setError(res, mailbox_url, 1, emptyString);
626 i_puts(MSG_EndFolder);
627 return;
628 }
629
630 showmessages:
631
632 mif = f->mlist;
633 for (j = 0; j < f->nfetch; ++j, ++mif) {
634 if (mif->gone)
635 continue;
636 reaction:
637 printEnvelope(mif);
638 action:
639 delflag = false;
640 printf("? ");
641 fflush(stdout);
642 key = getLetter("h?qvbfdslmn /");
643 printf("\b\b\b");
644 fflush(stdout);
645 if (key == '?' || key == 'h') {
646 i_puts(MSG_ImapMessageHelp);
647 goto action;
648 }
649
650 if (key == 'q') {
651 i_puts(MSG_Quit);
652 imap_done:
653 curl_easy_cleanup(handle);
654 exit(0);
655 }
656
657 if (key == '/') {
658 i_printf(MSG_Search);
659 fflush(stdout);
660 if (!fgets(inputline, sizeof(inputline), stdin))
661 goto imap_done;
662 if (!imapSearch(handle, f, inputline))
663 goto action;
664 if (f->nmsgs > f->nfetch)
665 i_printf(MSG_ShowLast + earliest, f->nfetch, f->nmsgs);
666 else
667 i_printf(MSG_MessagesX, f->nmsgs);
668 goto showmessages;
669 }
670
671 if (key == ' ') {
672 /* download the email from the imap server */
673 sprintf(cust_cmd, "FETCH %d BODY[]", mif->seqno);
674 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST,
675 cust_cmd);
676 mailstring = initString(&mailstring_l);
677 /* I wanted to turn the write function off here, because we don't need it,
678 * but turning it off and leaving HEADERFUNCTION on causes a seg fault,
679 * I have no idea why.
680 * curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, NULL);
681 * curl_easy_setopt(handle, CURLOPT_WRITEDATA, NULL);
682 */
683 curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION,
684 imap_header_callback);
685 callback_data.buffer =
686 initString(&callback_data.length);
687 res = curl_easy_perform(handle);
688 /* and put things back */
689 nzFree(callback_data.buffer);
690 curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL);
691 undosOneMessage();
692 if (res != CURLE_OK) {
693 ebcurl_setError(res, mailbox_url, 1,
694 emptyString);
695 nzFree(mailstring);
696 goto action;
697 }
698 /* have to strip 2 fetch BODY lines off the front,
699 * and ) A018 OK off the end. */
700 t = strchr(mailstring, '\n');
701 if (t)
702 t = strchr(t + 1, '\n');
703 if (t) {
704 ++t;
705 mailstring_l -= (t - mailstring);
706 strmove(mailstring, t);
707 }
708 t = mailstring + mailstring_l;
709 if (t > mailstring && t[-1] == '\n')
710 t[-1] = 0, --mailstring_l;
711 t = strrchr(mailstring, '\n');
712 if (t && t - 2 >= mailstring &&
713 !strncmp(t - 2, "\n)\nA", 4)) {
714 --t;
715 *t = 0;
716 mailstring_l = t - mailstring;
717 }
718 key = presentMail();
719 /* presentMail has already freed mailstring */
720 }
721
722 if (key == 'v') {
723 static const char delim[] = "----------";
724 puts(delim);
725 viewAll(f);
726 puts(delim);
727 goto reaction;
728 }
729
730 if (key == 'b' || key == 'f') {
731 char subkey;
732 struct FOLDER *g;
733 if (key == 'b')
734 i_printf(MSG_Batch);
735 else
736 i_printf(MSG_From, mif->from);
737 fflush(stdout);
738 subkey = getLetter("mdx");
739 printf("\b");
740 if (subkey == 'x') {
741 i_puts(MSG_Abort);
742 goto reaction;
743 }
744 if (subkey == 'd')
745 i_puts(MSG_Delete);
746 if (subkey == 'm') {
747 i_printf(MSG_MoveTo);
748 fflush(stdout);
749 if (!fgets(inputline, sizeof(inputline), stdin))
750 goto imap_done;
751 g = folderByName(inputline);
752 if (!g || g == f) {
753 // should have a better error message here.
754 i_puts(MSG_Abort);
755 goto reaction;
756 }
757 }
758 rc = bulkMoveDelete(handle, f, mif, key, subkey, g);
759 if (rc < 0)
760 goto abort;
761 if (rc)
762 yesdel = true;
763 // If current mail is gone, step ahead.
764 if (mif->gone)
765 continue;
766 goto reaction;
767 }
768
769 if (key == 's') {
770 i_puts(MSG_Stop);
771 break;
772 }
773
774 if (key == 'l') {
775 i_printf(MSG_Limit);
776 fflush(stdout);
777 if (!fgets(inputline, sizeof(inputline), stdin))
778 goto imap_done;
779 setLimit(inputline);
780 goto action;
781 }
782
783 if (key == 'm') {
784 struct FOLDER *g;
785 i_printf(MSG_MoveTo);
786 fflush(stdout);
787 if (!fgets(inputline, sizeof(inputline), stdin))
788 goto imap_done;
789 g = folderByName(inputline);
790 if (g && g != f) {
791 if (asprintf(&t, "%s %d \"%s\"",
792 (move_capable ? "MOVE" : "COPY"),
793 mif->seqno, g->path) == -1)
794 i_printfExit(MSG_MemAllocError, 24);
795 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST,
796 t);
797 free(t);
798 res = getMailData(handle);
799 nzFree(mailstring);
800 if (res != CURLE_OK)
801 goto abort;
802 // The move command shifts all the sequence numbers down.
803 if (move_capable) {
804 mif->gone = true;
805 int j2 = j + 1;
806 struct MIF *mif2 = mif + 1;
807 while (j2 < f->nfetch) {
808 --mif2->seqno;
809 ++j2, ++mif2;
810 }
811 } else {
812 delflag = true;
813 }
814 }
815 }
816
817 if (key == 'd') {
818 delflag = true;
819 i_puts(MSG_Delete);
820 }
821
822 if (!delflag)
823 continue;
824 sprintf(cust_cmd, "STORE %d +Flags \\Deleted", mif->seqno);
825 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cust_cmd);
826 res = getMailData(handle);
827 nzFree(mailstring);
828 if (res != CURLE_OK)
829 goto abort;
830 mif->gone = true;
831 yesdel = true;
832 }
833
834 if (yesdel) {
835 /* things deleted, time to expunge */
836 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "EXPUNGE");
837 mailstring = initString(&mailstring_l);
838 res = getMailData(handle);
839 nzFree(mailstring);
840 if (res != CURLE_OK)
841 goto abort;
842 }
843
844 puts("end of folder");
845 } /* scanFolder */
846
847 // \" is an escaped quote inside the string.
nextRealQuote(char * p)848 static char *nextRealQuote(char *p)
849 {
850 char *q = p, *r;
851 char c;
852 for (; (c = *p); ++p) {
853 if (c == '\\' && p[1] == '"') {
854 *q++ = '"';
855 ++p;
856 continue;
857 }
858 if (c == '"')
859 break;
860 *q++ = c;
861 }
862 if (!c)
863 return 0;
864 for (r = q; q <= p; ++q)
865 *q = ' ';
866 return r;
867 }
868
envelopes(CURL * handle,struct FOLDER * f)869 static void envelopes(CURL * handle, struct FOLDER *f)
870 {
871 int j;
872 char *t, *u;
873 CURLcode res;
874 char cust_cmd[80];
875
876 for (j = 0; j < f->nfetch; ++j) {
877 struct MIF *mif = f->mlist + j;
878
879 #if 0
880 // nobody is using the uid, don't fetch it, save time.
881 sprintf(cust_cmd, "FETCH %d UID", mif->seqno);
882 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cust_cmd);
883 res = getMailData(handle);
884 if (res != CURLE_OK)
885 goto abort;
886 t = strstr(mailstring, "FETCH (UID ");
887 if (t) {
888 t += 11;
889 while (*t == ' ')
890 ++t;
891 if (isdigit(*t))
892 mif->uid = atoi(t);
893 }
894 nzFree(mailstring);
895 #endif
896
897 sprintf(cust_cmd, "FETCH %d ALL", mif->seqno);
898 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, cust_cmd);
899 res = getMailData(handle);
900 if (res != CURLE_OK) {
901 //abort:
902 ebcurl_setError(res, mailbox_url, 2, emptyString);
903 }
904
905 mif->cbase = mailstring;
906 mif->subject = emptyString;
907 mif->from = emptyString;
908 mif->reply = emptyString;
909
910 t = strstr(mailstring, "ENVELOPE (");
911 if (!t) {
912 nzFree(mailstring);
913 continue;
914 }
915
916 /* pull out subject, reply, etc */
917 /* Don't free mailstring, we're using pieces of it */
918 t += 10;
919 while (*t == ' ')
920 ++t;
921 // date first, and it must be quoted.
922 // We don't use this date because it isn't standardized,
923 // it's whatever the sender's email client puts on the Date field.
924 // We use INTERNALDATE later.
925 if (*t != '"')
926 continue;
927 t = strchr(++t, '"');
928 if (!t)
929 continue;
930 ++t;
931
932 /* subject next, I'll assume it is always quoted */
933 while (*t == ' ')
934 ++t;
935 if (*t != '"')
936 continue;
937 ++t;
938 u = nextRealQuote(t);
939 if (!u)
940 continue;
941 *u = 0;
942 if (*t == '[' && u[-1] == ']')
943 ++t, u[-1] = 0;
944 mif->subject = t;
945 t = u + 1;
946
947 while (*t == ' ')
948 ++t;
949 if (strncmp(t, "((\"", 3))
950 goto doref;
951 t += 3;
952 u = nextRealQuote(t);
953 if (!u)
954 goto doref;
955 *u = 0;
956 mif->from = t;
957 t = u + 1;
958
959 while (*t == ' ')
960 ++t;
961 if (strncmp(t, "NIL", 3))
962 goto doref;
963 t += 3;
964 while (*t == ' ')
965 ++t;
966 /* again assuming each field is quoted */
967 if (*t != '"')
968 goto doref;
969 ++t;
970 u = strchr(t, '"');
971 if (!u)
972 goto doref;
973 *u = '@';
974 ++u;
975 while (*u == ' ')
976 ++u;
977 if (*u != '"')
978 goto doref;
979 ++u;
980 strmove(strchr(t, '@') + 1, u);
981 u = strchr(t, '"');
982 if (!u)
983 goto doref;
984 *u = 0;
985 mif->reply = t;
986 t = u + 1;
987
988 doref:
989 /* find the reference string, for replies */
990 u = strstr(t, " \"<");
991 if (!u)
992 goto doflags;
993 t = u + 2;
994 u = strchr(t, '"');
995 if (!u)
996 goto doflags;
997 *u = 0;
998 mif->refer = t; // not used
999 t = u + 1;
1000
1001 doflags:
1002 /* flags, mostly looking for has this been read */
1003 u = strstr(t, "FLAGS (");
1004 if(!u) {
1005 // sometimes flags and stuff comes at the beginning.
1006 // It should be somewhere!
1007 t = mailstring;
1008 u = strstr(t, "FLAGS (");
1009 }
1010 if (!u)
1011 goto dodate;
1012 t = u + 7;
1013 if (strstr(t, "\\Seen"))
1014 mif->seen = true;
1015 else
1016 ++f->unread;
1017
1018 dodate:
1019 u = strstr(t, "INTERNALDATE ");
1020 if (!u)
1021 goto dosize;
1022 t = u + 13;
1023 while (*t == ' ')
1024 ++t;
1025 if (*t != '"')
1026 goto dosize;
1027 ++t;
1028 u = strchr(t, '"');
1029 if (!u)
1030 goto dosize;
1031 *u = 0;
1032 mif->sent = parseHeaderDate(t);
1033 t = u + 1;
1034
1035 dosize:
1036 u = strstr(t, "SIZE ");
1037 if (!u)
1038 continue;
1039 t = u + 5;
1040 if (!isdigit(*t))
1041 continue;
1042 mif->size = atoi(t);
1043
1044 }
1045 } /* envelopes */
1046
1047 /* examine the specified folder, gather message envelopes */
examineFolder(CURL * handle,struct FOLDER * f,bool dostats)1048 static void examineFolder(CURL * handle, struct FOLDER *f, bool dostats)
1049 {
1050 int j;
1051 char *t;
1052 CURLcode res;
1053
1054 cleanFolder(f);
1055
1056 /* interrogate folder */
1057 if (asprintf(&t, "EXAMINE \"%s\"", f->path) == -1)
1058 i_printfExit(MSG_MemAllocError, strlen(f->path) + 12);
1059 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, t);
1060 free(t);
1061 res = getMailData(handle);
1062 if (res != CURLE_OK)
1063 ebcurl_setError(res, mailbox_url, 2, emptyString);
1064
1065 /* look for message count */
1066 t = strstr(mailstring, " EXISTS");
1067 if (t) {
1068 while (*t == ' ')
1069 --t;
1070 if (isdigit(*t)) {
1071 while (isdigit(*t))
1072 --t;
1073 ++t;
1074 f->nmsgs = atoi(t);
1075 }
1076 }
1077 f->nfetch = f->nmsgs;
1078 if (f->nfetch > imapfetch)
1079 f->nfetch = imapfetch;
1080 f->start = 1;
1081 if (f->nmsgs > f->nfetch && !earliest)
1082 f->start += (f->nmsgs - f->nfetch);
1083
1084 t = strstr(mailstring, "UIDNEXT ");
1085 if (t) {
1086 t += 8;
1087 while (*t == ' ')
1088 ++t;
1089 if (isdigit(*t))
1090 f->uidnext = atoi(t);
1091 }
1092
1093 nzFree(mailstring);
1094 if (dostats) {
1095 printf("%2lld %s", (unsigned long long)(f - topfolders + 1),
1096 f->path);
1097 /*
1098 if (f->children)
1099 printf(" with children");
1100 */
1101 printf(", ");
1102 i_printf(MSG_MessagesX, f->nmsgs);
1103 return;
1104 }
1105
1106 if (!f->nmsgs)
1107 return;
1108
1109 /* get some information on each message */
1110 f->mlist = allocZeroMem(sizeof(struct MIF) * f->nfetch);
1111 for (j = 0; j < f->nfetch; ++j) {
1112 struct MIF *mif = f->mlist + j;
1113 mif->seqno = f->start + j;
1114 }
1115
1116 envelopes(handle, f);
1117
1118 if (f->nmsgs > f->nfetch)
1119 i_printf(MSG_ShowLast + earliest, f->nfetch, f->nmsgs);
1120 else
1121 i_printf(MSG_MessagesX, f->nmsgs);
1122 } /* examineFolder */
1123
1124 /* find the last mail in the local unread directory */
1125 static int unreadMax, unreadMin, unreadCount;
1126 static int unreadBase; /* find min larger than base */
1127
unreadStats(void)1128 static void unreadStats(void)
1129 {
1130 const char *f;
1131 int n;
1132
1133 unreadMax = 0;
1134 unreadMin = 0;
1135 unreadCount = 0;
1136
1137 while ((f = nextScanFile(mailUnread))) {
1138 if (!stringIsNum(f))
1139 continue;
1140 n = atoi(f);
1141 if (n > unreadMax)
1142 unreadMax = n;
1143 if (n > unreadBase) {
1144 if (!unreadMin || n < unreadMin)
1145 unreadMin = n;
1146 ++unreadCount;
1147 }
1148 }
1149 } /* unreadStats */
1150
1151 static char *umf; /* unread mail file */
1152 static char *umf_end;
1153 static int umfd; /* file descriptor for the above */
1154 /* convert mail message to/from utf8 if need be. */
1155 /* This isn't really right, cause it should be done per mime component. */
1156 static char *mailu8;
1157 static int mailu8_l;
1158
newFetchmailHandle(const char * username,const char * password)1159 static CURL *newFetchmailHandle(const char *username, const char *password)
1160 {
1161 CURLcode res;
1162 CURL *handle = curl_easy_init();
1163 if (!handle)
1164 i_printfExit(MSG_LibcurlNoInit);
1165
1166 curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, mailTimeout);
1167 curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, eb_curl_callback);
1168 curl_easy_setopt(handle, CURLOPT_WRITEDATA, &callback_data);
1169 curl_easy_setopt(handle, CURLOPT_VERBOSE, 1);
1170 curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, ebcurl_debug_handler);
1171 curl_easy_setopt(handle, CURLOPT_DEBUGDATA, &callback_data);
1172
1173 res = curl_easy_setopt(handle, CURLOPT_USERNAME, username);
1174 if (res != CURLE_OK)
1175 ebcurl_setError(res, mailbox_url, 1, emptyString);
1176
1177 res = curl_easy_setopt(handle, CURLOPT_PASSWORD, password);
1178 if (res != CURLE_OK)
1179 ebcurl_setError(res, mailbox_url, 2, emptyString);
1180
1181 return handle;
1182 } /* newFetchmailHandle */
1183
get_mailbox_url(const struct MACCOUNT * account)1184 static void get_mailbox_url(const struct MACCOUNT *account)
1185 {
1186 const char *scheme = "pop3";
1187 char *url = NULL;
1188
1189 if (account->inssl)
1190 scheme = "pop3s";
1191
1192 if (isimap)
1193 scheme = (account->inssl ? "imaps" : "imap");
1194
1195 if (asprintf(&url,
1196 "%s://%s:%d/", scheme, account->inurl,
1197 account->inport) == -1) {
1198 /* The byte count is a little white lie / guess, we don't know
1199 * how much asprintf *really* requested. */
1200 i_printfExit(MSG_MemAllocError,
1201 strlen(scheme) + strlen(account->inurl) + 8);
1202 }
1203 mailbox_url = url;
1204 } /* get_mailbox_url */
1205
1206 /* reads message into mailstring, it's up to you to free it */
fetchOneMessage(CURL * handle,int message_number)1207 static CURLcode fetchOneMessage(CURL * handle, int message_number)
1208 {
1209 CURLcode res = CURLE_OK;
1210
1211 res = setCurlURL(handle, message_url);
1212 if (res != CURLE_OK)
1213 return res;
1214 res = curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, NULL);
1215 if (res != CURLE_OK)
1216 return res;
1217 res = curl_easy_setopt(handle, CURLOPT_NOBODY, 0L);
1218 if (res != CURLE_OK)
1219 return res;
1220
1221 res = getMailData(handle);
1222 undosOneMessage();
1223 if (res != CURLE_OK)
1224 return res;
1225
1226 /* got the file, save it in unread */
1227 sprintf(umf_end, "%d", unreadMax + message_number);
1228 umfd = open(umf, O_WRONLY | O_TEXT | O_CREAT, MODE_rw);
1229 if (umfd < 0)
1230 i_printfExit(MSG_NoCreate, umf);
1231 if (write(umfd, mailstring, mailstring_l) < mailstring_l)
1232 i_printfExit(MSG_NoWrite, umf);
1233 close(umfd);
1234
1235 return res;
1236 } /* fetchOneMessage */
1237
deleteOneMessage(CURL * handle)1238 static CURLcode deleteOneMessage(CURL * handle)
1239 {
1240 CURLcode res = curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "DELE");
1241 if (res != CURLE_OK)
1242 return res;
1243 res = curl_easy_setopt(handle, CURLOPT_NOBODY, 1L);
1244 if (res != CURLE_OK)
1245 return res;
1246 res = getMailData(handle);
1247 return res;
1248 } /* deleteOneMessage */
1249
count_messages(CURL * handle,int * message_count)1250 static CURLcode count_messages(CURL * handle, int *message_count)
1251 {
1252 CURLcode res = setCurlURL(handle, mailbox_url);
1253 int i, num_messages = 0;
1254 bool last_nl = true;
1255
1256 if (res != CURLE_OK)
1257 return res;
1258
1259 res = getMailData(handle);
1260 if (res != CURLE_OK)
1261 return res;
1262
1263 if (isimap) {
1264 struct FOLDER *f;
1265 char inputline[80];
1266 char *t;
1267
1268 setFolders(handle);
1269 if (!n_folders) {
1270 i_puts(MSG_NoFolders);
1271 imap_done:
1272 curl_easy_cleanup(handle);
1273 exit(0);
1274 }
1275
1276 i_puts(MSG_SelectFolder);
1277 input:
1278 if (!fgets(inputline, sizeof(inputline), stdin))
1279 goto imap_done;
1280 stripWhite(inputline);
1281
1282 if (stringEqual(inputline, "rf")) {
1283 refresh:
1284 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, 0);
1285 res = getMailData(handle);
1286 if (res != CURLE_OK)
1287 goto imap_done;
1288 setFolders(handle);
1289 goto input;
1290 }
1291
1292 if (stringEqual(inputline, "q")) {
1293 i_puts(MSG_Quit);
1294 goto imap_done;
1295 }
1296
1297 t = inputline;
1298 if (t[0] == 'd' && t[1] == 'b' && isdigit(t[2]) && !t[3]) {
1299 debugLevel = t[2] - '0';
1300 curl_easy_setopt(handle, CURLOPT_VERBOSE,
1301 (debugLevel >= 4));
1302 goto input;
1303 }
1304
1305 if (*t == 'l' && isspace(t[1])) {
1306 setLimit(t + 1);
1307 goto input;
1308 }
1309
1310 if (!strncmp(t, "create ", 7)) {
1311 char *w;
1312 t += 7;
1313 stripWhite(t);
1314 if (!*t)
1315 goto input;
1316 if (asprintf(&w, "CREATE \"%s\"", t) < 0)
1317 goto imap_done;
1318 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, w);
1319 res = getMailData(handle);
1320 free(w);
1321 nzFree(mailstring);
1322 if (res != CURLE_OK) {
1323 i_printf(MSG_NoCreate2, t);
1324 nl();
1325 goto input;
1326 }
1327 goto refresh;
1328 }
1329
1330 if (!strncmp(t, "delete ", 7)) {
1331 char *w;
1332 t += 7;
1333 stripWhite(t);
1334 if (!*t)
1335 goto input;
1336 if (asprintf(&w, "DELETE \"%s\"", t) < 0)
1337 goto imap_done;
1338 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, w);
1339 res = getMailData(handle);
1340 free(w);
1341 nzFree(mailstring);
1342 if (res != CURLE_OK) {
1343 i_printf(MSG_NoAccess, t);
1344 nl();
1345 goto input;
1346 }
1347 goto refresh;
1348 }
1349
1350 if (!strncmp(t, "rename ", 7)) {
1351 char *u, *w;
1352 t += 7;
1353 stripWhite(t);
1354 if (!*t)
1355 goto input;
1356 u = strchr(t, ' ');
1357 if (!u)
1358 goto input;
1359 *u++ = 0;
1360 stripWhite(u);
1361 if (!*u)
1362 goto input;
1363 if (asprintf(&w, "RENAME \"%s\" \"%s\"", t, u) < 0)
1364 goto imap_done;
1365 curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, w);
1366 res = getMailData(handle);
1367 free(w);
1368 nzFree(mailstring);
1369 if (res != CURLE_OK) {
1370 i_printf(MSG_NoAccess, t);
1371 nl();
1372 goto input;
1373 }
1374 goto refresh;
1375 }
1376
1377 f = folderByName(t);
1378 if (!f)
1379 goto input;
1380 examineFolder(handle, f, false);
1381 scanFolder(handle, f);
1382 goto input;
1383 }
1384
1385 for (i = 0; i < mailstring_l; i++) {
1386 if (mailstring[i] == '\n' || mailstring[i] == '\r') {
1387 last_nl = true;
1388 continue;
1389 }
1390 if (last_nl && isdigit(mailstring[i]))
1391 num_messages++;
1392 last_nl = false;
1393 }
1394
1395 *message_count = num_messages;
1396 return CURLE_OK;
1397 } /* count_messages */
1398
1399 /* Returns number of messages fetched */
fetchMail(int account)1400 int fetchMail(int account)
1401 {
1402 CURL *mail_handle;
1403 const struct MACCOUNT *a = accounts + account - 1;
1404 const char *login = a->login;
1405 const char *pass = a->password;
1406 int nfetch = 0; /* number of messages actually fetched */
1407 CURLcode res = CURLE_OK;
1408 const char *url_for_error;
1409 int message_count = 0, message_number;
1410
1411 get_mailbox_url(a);
1412 url_for_error = mailbox_url;
1413
1414 if (!mailDir)
1415 i_printfExit(MSG_NoMailDir);
1416 if (chdir(mailDir))
1417 i_printfExit(MSG_NoDirChange, mailDir);
1418
1419 if (!umf) {
1420 umf = allocMem(strlen(mailUnread) + 12);
1421 sprintf(umf, "%s/", mailUnread);
1422 umf_end = umf + strlen(umf);
1423 }
1424 unreadBase = 0;
1425 unreadStats();
1426
1427 mail_handle = newFetchmailHandle(login, pass);
1428 res = count_messages(mail_handle, &message_count);
1429 if (res != CURLE_OK)
1430 goto fetchmail_cleanup;
1431
1432 for (message_number = 1; message_number <= message_count;
1433 message_number++) {
1434 if (asprintf(&message_url, "%s%u", mailbox_url, message_number)
1435 == -1) {
1436 /* Again, the byte count in the error message is a bit of a fib. */
1437 i_printfExit(MSG_MemAllocError,
1438 strlen(mailbox_url) + 11);
1439 }
1440 res = fetchOneMessage(mail_handle, message_number);
1441 if (res != CURLE_OK)
1442 goto fetchmail_cleanup;
1443 nfetch++;
1444 res = deleteOneMessage(mail_handle);
1445 if (res != CURLE_OK)
1446 goto fetchmail_cleanup;
1447 nzFree(message_url);
1448 message_url = NULL;
1449 }
1450
1451 fetchmail_cleanup:
1452 if (message_url)
1453 url_for_error = message_url;
1454 if (res != CURLE_OK)
1455 ebcurl_setError(res, url_for_error, 1, emptyString);
1456 curl_easy_cleanup(mail_handle);
1457 nzFree(message_url);
1458 nzFree(mailbox_url);
1459 nzFree(mailstring);
1460 mailstring = initString(&mailstring_l);
1461 return nfetch;
1462 } /* fetchMail */
1463
1464 /* fetch from all accounts except those with nofetch or imap set */
fetchAllMail(void)1465 int fetchAllMail(void)
1466 {
1467 int i, j;
1468 const struct MACCOUNT *a, *b;
1469 int nfetch = 0;
1470
1471 for (i = 1; i <= maxAccount; ++i) {
1472 a = accounts + i - 1;
1473 if (a->nofetch | a->imap)
1474 continue;
1475
1476 /* don't fetch from an earlier account that has the same host an dlogin */
1477 for (j = 1; j < i; ++j) {
1478 b = accounts + j - 1;
1479 if (!b->nofetch &&
1480 stringEqual(a->inurl, b->inurl) &&
1481 stringEqual(a->login, b->login))
1482 break;
1483 }
1484 if (j < i)
1485 continue;
1486
1487 debugPrint(3, "fetch from %d %s", i, a->inurl);
1488 nfetch += fetchMail(i);
1489 }
1490
1491 return nfetch;
1492 } /* fetchAllMail */
1493
1494 static void readReplyInfo(void);
1495 static void writeReplyInfo(const char *addstring);
1496
scanMail(void)1497 void scanMail(void)
1498 {
1499 int nmsgs, m;
1500
1501 if (!isInteractive)
1502 i_printfExit(MSG_FetchNotBackgnd);
1503 if (!mailDir)
1504 i_printfExit(MSG_NoMailDir);
1505 if (chdir(mailDir))
1506 i_printfExit(MSG_NoDirChange, mailDir);
1507
1508 if (!umf) {
1509 umf = allocMem(strlen(mailUnread) + 12);
1510 sprintf(umf, "%s/", mailUnread);
1511 umf_end = umf + strlen(umf);
1512 }
1513
1514 /* How many mail messages? */
1515 unreadBase = 0;
1516 unreadStats();
1517 nmsgs = unreadCount;
1518 if (!nmsgs) {
1519 i_puts(MSG_NoMail);
1520 exit(0);
1521 }
1522 i_printf(MSG_MessagesX, nmsgs);
1523
1524 loadAddressBook();
1525
1526 for (m = 1; m <= nmsgs; ++m) {
1527 nzFree(lastMailText);
1528 lastMailText = 0;
1529 /* Now grab the entire message */
1530 unreadStats();
1531 sprintf(umf_end, "%d", unreadMin);
1532 if (!fileIntoMemory(umf, &mailstring, &mailstring_l))
1533 showErrorAbort();
1534 unreadBase = unreadMin;
1535
1536 if (presentMail() == 'd')
1537 unlink(umf);
1538 } /* loop over mail messages */
1539
1540 exit(0);
1541 } /* scanMail */
1542
1543 /* a mail message is in mailstring, present it to the user */
1544 /* Return the key that was pressed.
1545 * stop is only meaningful for imap. */
presentMail(void)1546 static char presentMail(void)
1547 {
1548 int j, k;
1549 const char *redirect = NULL; /* send mail elsewhere */
1550 char key = 0;
1551 const char *atname = NULL; /* name of file or attachment */
1552 bool delflag = false; /* delete this mail */
1553 bool scanat = false; /* scan for attachments */
1554 int displine;
1555 int stashNumber = -1;
1556 char exists;
1557 int fsize; /* file size */
1558 int fh;
1559
1560 /* clear things out from the last message */
1561 if (lastMailInfo)
1562 freeMailInfo(lastMailInfo);
1563 lastMailInfo = 0;
1564
1565 if (sessionList[1].lw)
1566 cxQuit(1, 2);
1567 cs = 0;
1568 cxSwitch(1, false);
1569
1570 iuReformat(mailstring, mailstring_l, &mailu8, &mailu8_l);
1571 if (mailu8) {
1572 if (!addTextToBuffer((pst) mailu8, mailu8_l, 0, false))
1573 showErrorAbort();
1574 } else {
1575 if (!addTextToBuffer((pst) mailstring, mailstring_l, 0, false))
1576 showErrorAbort();
1577 }
1578
1579 browseCurrentBuffer();
1580
1581 if (!passMail) {
1582 redirect = mailRedirect(lastMailInfo->to,
1583 lastMailInfo->from,
1584 lastMailInfo->reply,
1585 lastMailInfo->subject);
1586 }
1587
1588 if (redirect) {
1589 if (!isimap) {
1590 delflag = true;
1591 key = 'w';
1592 if (*redirect == '-')
1593 ++redirect, key = 'u';
1594 if (stringEqual(redirect, "x")) {
1595 i_printf(MSG_Junk);
1596 printf("[%s]\n", lastMailInfo->subject);
1597 } else
1598 printf("> %s\n", redirect);
1599 } else {
1600 if (*redirect == '-')
1601 ++redirect;
1602 if (stringEqual(redirect, "x"))
1603 redirect = NULL;
1604 }
1605 }
1606
1607 /* display the next page of mail and get a command from the keyboard */
1608 displine = 1;
1609 paging:
1610 if (!delflag) { /* show next page */
1611 if (displine <= cw->dol) {
1612 for (j = 0; j < 20 && displine <= cw->dol;
1613 ++j, ++displine) {
1614 char *showline = (char *)fetchLine(displine, 1);
1615 k = pstLength((pst) showline);
1616 showline[--k] = 0;
1617 printf("%s\n", showline);
1618 nzFree(showline);
1619 }
1620 }
1621 }
1622
1623 /* get key command from user */
1624 key_command:
1625 if (delflag)
1626 goto writeMail;
1627
1628 /* interactive prompt depends on whether there is more text or not */
1629 printf("%c ", displine > cw->dol ? '?' : '*');
1630 fflush(stdout);
1631 key = getLetter((isimap ? "qvbfh? nwWuUasdm" : "qh? nwud"));
1632 printf("\b\b\b");
1633 fflush(stdout);
1634
1635 switch (key) {
1636 case 'q':
1637 i_puts(MSG_Quit);
1638 exit(0);
1639
1640 case 'n':
1641 i_puts(MSG_Next);
1642 goto afterinput;
1643
1644 case 's':
1645 case 'm':
1646 case 'v':
1647 case 'f':
1648 case 'b':
1649 goto afterinput;
1650
1651 case 'd':
1652 if (!isimap)
1653 i_puts(MSG_Delete);
1654 delflag = true;
1655 goto afterinput;
1656
1657 case ' ':
1658 if (displine > cw->dol)
1659 i_puts(MSG_EndMessage);
1660 goto paging;
1661
1662 case '?':
1663 case 'h':
1664 i_puts(isimap ? MSG_ImapReadHelp : MSG_MailHelp);
1665 goto key_command;
1666
1667 case 'a':
1668 key = 'w'; /* this will scan attachments */
1669 scanat = true;
1670
1671 case 'w':
1672 case 'W':
1673 case 'u':
1674 case 'U':
1675 break;
1676
1677 default:
1678 i_puts(MSG_NYI);
1679 goto key_command;
1680 } /* switch */
1681
1682 /* At this point we're saving the mail somewhere. */
1683 writeMail:
1684 if (!isimap || isupper(key))
1685 delflag = true;
1686 atname = 0;
1687 if (!isimap)
1688 atname = redirect;
1689
1690 if (scanat)
1691 goto attachOnly;
1692
1693 saveMail:
1694 if (!atname)
1695 atname = getFileName(MSG_FileName, redirect, false, false);
1696 if (stringEqual(atname, "x"))
1697 goto afterinput;
1698
1699 exists = fileTypeByName(atname, false);
1700 fh = open(atname, O_WRONLY | O_TEXT | O_CREAT | O_APPEND, MODE_rw);
1701 if (fh < 0) {
1702 i_printf(MSG_NoCreate, atname);
1703 goto saveMail;
1704 }
1705 if (exists)
1706 write(fh,
1707 "======================================================================\n",
1708 71);
1709 if (key == 'u' || key == 'U') {
1710 if (write(fh, mailstring, mailstring_l) < mailstring_l) {
1711 badsave:
1712 i_printf(MSG_NoWrite, atname);
1713 close(fh);
1714 goto saveMail;
1715 }
1716 close(fh);
1717 fsize = mailstring_l;
1718 } else {
1719
1720 /* key = w, write the file and save the original unformatted */
1721 if (mailStash) {
1722 char *rmf; /* raw mail file */
1723 int rmfh; /* file handle to same */
1724 /* I want a fairly easy filename, in case I want to go look at the original.
1725 * Not a 30 character message ID that I am forced to cut&paste.
1726 * 4 or 5 digits would be nice.
1727 * So the filename looks like /home/foo/.Trash/rawmail/36921
1728 * I pick the digits randomly.
1729 * Please don't accumulate 100,000 emails before you empty your trash.
1730 * It's good to have a cron job empty the trash early Sunday morning.
1731 */
1732
1733 k = strlen(mailStash);
1734 rmf = allocMem(k + 12);
1735 /* Try 20 times, then give up. */
1736 for (j = 0; j < 20; ++j) {
1737 int rn = rand() % 100000; /* random number */
1738 sprintf(rmf, "%s/%05d", mailStash, rn);
1739 if (fileTypeByName(rmf, false))
1740 continue;
1741 /* dump the original mail into the file */
1742 rmfh =
1743 open(rmf,
1744 O_WRONLY | O_TEXT | O_CREAT | O_APPEND,
1745 MODE_rw);
1746 if (rmfh < 0)
1747 break;
1748 if (write(rmfh, mailstring, mailstring_l) <
1749 mailstring_l) {
1750 close(rmfh);
1751 unlink(rmf);
1752 break;
1753 }
1754 close(rmfh);
1755 /* written successfully, remember the stash number */
1756 stashNumber = rn;
1757 break;
1758 }
1759 }
1760
1761 fsize = 0;
1762 for (j = 1; j <= cw->dol; ++j) {
1763 char *showline = (char *)fetchLine(j,
1764 1);
1765 int len = pstLength((pst)
1766 showline);
1767 if (write(fh, showline, len) < len)
1768 goto badsave;
1769 nzFree(showline);
1770 fsize += len;
1771 } /* loop over lines */
1772
1773 if (stashNumber >= 0) {
1774 char addstash[60];
1775 int minor = rand() % 100000;
1776 sprintf(addstash, "\nUnformatted %05d.%05d\n",
1777 stashNumber, minor);
1778 k = strlen(addstash);
1779 if (write(fh, addstash, k) < k)
1780 goto badsave;
1781 fsize += k;
1782 /* write the mailInfo data to the mail reply file */
1783 addstash[k - 1] = ':';
1784 writeReplyInfo(addstash + k - 12);
1785 }
1786
1787 close(fh);
1788
1789 attachOnly:
1790
1791 if (nattach)
1792 writeAttachments(lastMailInfo);
1793 else if (scanat)
1794 i_puts(MSG_NoAttachments);
1795 } /* unformat or format */
1796
1797 if (scanat)
1798 goto afterinput;
1799 /* print "mail saved" message */
1800 i_printf(MSG_MailSaved, fsize);
1801 if (exists)
1802 i_printf(MSG_Appended);
1803 nl();
1804
1805 afterinput:
1806 nzFree(mailstring);
1807 mailstring = 0;
1808 nzFree(mailu8);
1809 mailu8 = 0;
1810
1811 if (delflag)
1812 return 'd';
1813 return strchr("smvbf", key) ? key : 'n';
1814 } /* presentMail */
1815
1816 /* Here are the common keywords for mail header lines.
1817 * These are in alphabetical order, so you can stick more in as you find them.
1818 * The more words we have, the more accurate the test. */
1819 static const char *const mhwords[] = {
1820 "action:",
1821 "arrival-date:",
1822 "bcc:",
1823 "cc:",
1824 "content-transfer-encoding:",
1825 "content-type:",
1826 "date:",
1827 "delivered-to:",
1828 "errors-to:",
1829 "final-recipient:",
1830 "from:",
1831 "importance:",
1832 "last-attempt-date:",
1833 "list-id:",
1834 "mailing-list:",
1835 "message-id:",
1836 "mime-version:",
1837 "precedence:",
1838 "received:",
1839 "remote-mta:",
1840 "reply-to:",
1841 "reporting-mta:",
1842 "return-path:",
1843 "sender:",
1844 "sent:",
1845 "status:",
1846 "subject:",
1847 "to:",
1848 "user-agent:",
1849 "x-beenthere:",
1850 "x-comment:",
1851 "x-loop:",
1852 "x-mailer:",
1853 "x-mailman-version:",
1854 "x-mdaemon-deliver-to:",
1855 "x-mdremoteip:",
1856 "x-mimeole:",
1857 "x-ms-tnef-correlator:",
1858 "x-msmail-priority:",
1859 "x-originating-ip:",
1860 "x-priority:",
1861 "x-return-path:",
1862 "X-Spam-Checker-Version:",
1863 "x-spam-level:",
1864 "x-spam-msg-id:",
1865 "X-SPAM-Msg-Sniffer-Result:",
1866 "x-spam-processed:",
1867 "x-spam-status:",
1868 "x-uidl:",
1869 0
1870 };
1871
1872 /* Before we render a mail message, let's make sure it looks like email.
1873 * This is similar to htmlTest() in html.c. */
emailTest(void)1874 bool emailTest(void)
1875 {
1876 int i, j, k, n;
1877
1878 /* This is a very simple test - hopefully not too simple.
1879 * The first 20 non-indented lines have to look like mail header lines,
1880 * with at least half the keywords recognized. */
1881 for (i = 1, j = k = 0; i <= cw->dol && j < 20; ++i) {
1882 char *q;
1883 char *p = (char *)fetchLine(i, -1);
1884 char first = *p;
1885 if (first == '\n' || (first == '\r' && p[1] == '\n'))
1886 break;
1887 if (first == ' ' || first == '\t')
1888 continue;
1889 ++j; /* nonindented line */
1890 for (q = p; isalnumByte(*q) || *q == '_' || *q == '-'; ++q) ;
1891 if (q == p)
1892 continue;
1893 if (*q++ != ':')
1894 continue;
1895 /* X-Whatever is a mail header word */
1896 if (q - p >= 8 && p[1] == '-' && toupper(p[0]) == 'X') {
1897 ++k;
1898 } else {
1899 for (n = 0; mhwords[n]; ++n)
1900 if (memEqualCI(mhwords[n], p, q - p))
1901 break;
1902 if (mhwords[n])
1903 ++k;
1904 }
1905 if (k >= 4 && k * 2 >= j)
1906 return true;
1907 } /* loop over lines */
1908
1909 return false;
1910 } /* emailTest */
1911
mail64Error(int err)1912 void mail64Error(int err)
1913 {
1914 switch (err) {
1915 case BAD_BASE64_DECODE:
1916 runningError(MSG_AttBad64);
1917 break;
1918 case EXTRA_CHARS_BASE64_DECODE:
1919 runningError(MSG_AttAfterChars);
1920 break;
1921 } /* switch on error code */
1922 } /* mail64Error */
1923
unpackQP(struct MHINFO * w)1924 static void unpackQP(struct MHINFO *w)
1925 {
1926 char c, d, *q, *r;
1927 for (q = r = w->start; q < w->end; ++q) {
1928 c = *q;
1929 if (c != '=') {
1930 *r++ = c;
1931 continue;
1932 }
1933 c = *++q;
1934 if (c == '\n')
1935 continue;
1936 d = q[1];
1937 if (isxdigit(c) && isxdigit(d)) {
1938 d = fromHex(c, d);
1939 if (d == 0)
1940 d = ' ';
1941 *r++ = d;
1942 ++q;
1943 continue;
1944 }
1945 --q;
1946 *r++ = '=';
1947 }
1948 w->end = r;
1949 *r = 0;
1950 } /* unpackQP */
1951
1952 /* Look for the name of the attachment and boundary */
ctExtras(struct MHINFO * w,const char * s,const char * t)1953 static void ctExtras(struct MHINFO *w, const char *s, const char *t)
1954 {
1955 char quote;
1956 const char *q, *al, *ar;
1957
1958 if (w->ct < CT_MULTI) {
1959 quote = 0;
1960 for (q = s + 1; q < t; ++q) {
1961 if (isalnumByte(q[-1]))
1962 continue;
1963 /* could be name= or filename= */
1964 if (memEqualCI(q, "file", 4))
1965 q += 4;
1966 if (!memEqualCI(q, "name=", 5))
1967 continue;
1968 q += 5;
1969 if (*q == '"') {
1970 quote = *q;
1971 ++q;
1972 }
1973 for (al = q; q < t; ++q) {
1974 if (*q == '"')
1975 break;
1976 if (quote)
1977 continue;
1978 if (strchr(",; \t", *q))
1979 break;
1980 }
1981 ar = q;
1982 if (ar - al >= MHLINE)
1983 ar = al + MHLINE - 1;
1984 strncpy(w->cfn, al, ar - al);
1985 break;
1986 }
1987 }
1988 /* regular file */
1989 if (w->ct >= CT_MULTI) {
1990 quote = 0;
1991 for (q = s + 1; q < t; ++q) {
1992 if (isalnumByte(q[-1]))
1993 continue;
1994 if (!memEqualCI(q, "boundary=", 9))
1995 continue;
1996 q += 9;
1997 if (*q == '"') {
1998 quote = *q;
1999 ++q;
2000 }
2001 for (al = q; q < t; ++q) {
2002 if (*q == '"')
2003 break;
2004 if (quote)
2005 continue;
2006 if (strchr(",; \t", *q))
2007 break;
2008 }
2009 ar = q;
2010 w->boundlen = ar - al;
2011 strncpy(w->boundary, al, ar - al);
2012 break;
2013 }
2014 } /* multi or alt */
2015 } /* ctExtras */
2016
isoDecode(char * vl,char ** vrp)2017 static void isoDecode(char *vl, char **vrp)
2018 {
2019 char *vr = *vrp;
2020 char *start, *end; /* section being decoded */
2021 char *s, *t, c, d, code;
2022 int len;
2023 uchar val, leftover, mod;
2024
2025 start = vl;
2026 restart:
2027 start = strstr(start, "=?");
2028 if (!start || start >= vr)
2029 goto finish;
2030 start += 2;
2031 if (!memEqualCI(start, "iso-", 4) &&
2032 !memEqualCI(start, "us-ascii", 8) &&
2033 !memEqualCI(start, "utf-", 4) &&
2034 !memEqualCI(start, "cp1252", 6) &&
2035 !memEqualCI(start, "gb", 2) && !memEqualCI(start, "windows-", 8))
2036 goto restart;
2037 s = strchr(start, '?');
2038 if (!s || s > vr - 5 || s[2] != '?')
2039 goto restart;
2040 code = s[1];
2041 code = toupper(code);
2042 if (code != 'Q' && code != 'B')
2043 goto restart;
2044 s += 3;
2045 end = strstr(s, "?=");
2046 if (!end || end > vr - 2)
2047 goto restart;
2048
2049 t = start - 2;
2050
2051 if (code == 'Q') {
2052 while (s < end) {
2053 c = *s++;
2054 if (c == '=') {
2055 c = *s;
2056 d = s[1];
2057 if (isxdigit(c) && isxdigit(d)) {
2058 d = fromHex(c, d);
2059 *t++ = d;
2060 s += 2;
2061 continue;
2062 }
2063 c = '=';
2064 }
2065 *t++ = c;
2066 }
2067 goto copy;
2068 }
2069
2070 /* base64 */
2071 mod = 0;
2072 for (; s < end; ++s) {
2073 c = *s;
2074 if (isspaceByte(c))
2075 continue;
2076 if (c == '=')
2077 continue;
2078 val = base64Bits(c);
2079 if (val & 64)
2080 val = 0; /* ignore errors here */
2081 if (mod == 0) {
2082 leftover = val << 2;
2083 } else if (mod == 1) {
2084 *t++ = (leftover | (val >> 4));
2085 leftover = val << 4;
2086 } else if (mod == 2) {
2087 *t++ = (leftover | (val >> 2));
2088 leftover = val << 6;
2089 } else {
2090 *t++ = (leftover | val);
2091 }
2092 ++mod;
2093 mod &= 3;
2094 }
2095
2096 copy:
2097 s += 2;
2098 start = t;
2099 len = vr - s;
2100 if (len)
2101 memmove(t, s, len);
2102 vr = t + len;
2103 goto restart;
2104
2105 finish:
2106 for (s = vl; s < vr; ++s) {
2107 c = *s;
2108 if (c == 0 || c == '\t')
2109 *s = ' ';
2110 }
2111
2112 *vrp = vr;
2113 } /* isoDecode */
2114
2115 /* mail header reformat, to/from utf8 */
mhReformat(char * line)2116 static void mhReformat(char *line)
2117 {
2118 char *tbuf;
2119 int tlen = strlen(line);
2120 iuReformat(line, tlen, &tbuf, &tlen);
2121 if (!tbuf)
2122 return;
2123 if (tlen >= MHLINE)
2124 tbuf[MHLINE - 1] = 0;
2125 strcpy(line, tbuf);
2126 nzFree(tbuf);
2127 } /* mhReformat */
2128
extractLessGreater(char * s)2129 static void extractLessGreater(char *s)
2130 {
2131 char *vl, *vr;
2132 vl = strchr(s, '<');
2133 vr = strchr(s, '>');
2134 if (vl && vr && vl < vr) {
2135 *vr = 0;
2136 strmove(s, vl + 1);
2137 }
2138 } /* extractLessGreater */
2139
2140 /* Now that we know it's mail, see what information we can
2141 * glean from the headers.
2142 * Returns a pointer to an allocated MHINFO structure.
2143 * This routine is recursive. */
headerGlean(char * start,char * end)2144 static struct MHINFO *headerGlean(char *start, char *end)
2145 {
2146 char *s, *t, *q;
2147 char *vl, *vr; /* value left and value right */
2148 struct MHINFO *w;
2149 int j, k, n;
2150 char linetype = 0;
2151
2152 /* defaults */
2153 w = allocZeroMem(sizeof(struct MHINFO));
2154 initList(&w->components);
2155 w->ct = CT_OTHER;
2156 w->ce = CE_8BIT;
2157 w->andOthers = false;
2158 w->tolist = initString(&w->tolen);
2159 w->cclist = initString(&w->cclen);
2160 w->start = start, w->end = end;
2161
2162 for (s = start; s < end; s = t + 1) {
2163 char quote;
2164 char first = *s;
2165 t = strchr(s, '\n');
2166 if (!t)
2167 t = end - 1; /* should never happen */
2168 if (t == s)
2169 break; /* empty line */
2170
2171 if (first == ' ' || first == '\t') {
2172 if (linetype == 'c')
2173 ctExtras(w, s, t);
2174 if (linetype == 't')
2175 stringAndBytes(&w->tolist, &w->tolen, s, t - s);
2176 if (linetype == 'y')
2177 stringAndBytes(&w->cclist, &w->cclen, s, t - s);
2178 if (linetype == 's') {
2179 int l1, l2;
2180 ++s;
2181 l1 = strlen(w->subject);
2182 l2 = t - s;
2183 if(l1 + l2 > MHLINE)
2184 l2 = MHLINE - l1;
2185 strncpy(w->subject + l1, s, l2);
2186 if (t == end - 1 || (t[1] != ' ' && t[1] != '\t')) {
2187 vl = w->subject;
2188 mhReformat(vl);
2189 vr = vl + strlen(vl);
2190 isoDecode(vl, &vr);
2191 *vr = 0;
2192 }
2193 }
2194 continue;
2195 }
2196
2197 /* find the lead word */
2198 for (q = s; isalnumByte(*q) || *q == '_' || *q == '-'; ++q) ;
2199 if (q == s)
2200 continue; /* should never happen */
2201 if (*q++ != ':')
2202 continue; /* should never happen */
2203 for (vl = q; *vl == ' ' || *vl == '\t'; ++vl) ;
2204 if (vl == t && t < end - 1 && (t[1] == ' ' || t[1] == '\t')) {
2205 // foobar: and that's all, maybe on the next line?
2206 t = strchr(t + 1, '\n');
2207 if (!t)
2208 t = end - 1; /* should never happen */
2209 for (++vl; *vl == ' ' || *vl == '\t'; ++vl) ;
2210 }
2211 for (vr = t; vr > vl && (vr[-1] == ' ' || vr[-1] == '\t');
2212 --vr) ;
2213 if (vr == vl)
2214 continue; /* empty */
2215
2216 /* too long? */
2217 if (vr - vl > MHLINE - 1)
2218 vr = vl + MHLINE - 1;
2219
2220 /* This is sort of a switch statement on the word */
2221 if (memEqualCI(s, "subject:", q - s)) {
2222 linetype = 's';
2223 if (w->subject[0]) {
2224 linetype = 0;
2225 continue;
2226 }
2227 /* get rid of forward/reply prefixes */
2228 for (q = vl; q < vr; ++q) {
2229 static const char *const prefix[] = {
2230 "re", "sv", "fwd", 0
2231 };
2232 if (!isalphaByte(*q))
2233 continue;
2234 if (q > vl && isalnumByte(q[-1]))
2235 continue;
2236 for (j = 0; prefix[j]; ++j)
2237 if (memEqualCI
2238 (q, prefix[j], strlen(prefix[j])))
2239 break;
2240 if (!prefix[j])
2241 continue;
2242 j = strlen(prefix[j]);
2243 if (!strchr(":-,;", q[j]))
2244 continue;
2245 ++j;
2246 while (q + j < vr && q[j] == ' ')
2247 ++j;
2248 memmove(q, q + j, vr - q - j);
2249 vr -= j;
2250 --q; /* try again */
2251 }
2252 strncpy(w->subject, vl, vr - vl);
2253 if (t == end - 1 || (t[1] != ' ' && t[1] != '\t')) {
2254 vl = w->subject;
2255 mhReformat(vl);
2256 vr = vl + strlen(vl);
2257 isoDecode(vl, &vr);
2258 *vr = 0;
2259 }
2260 continue;
2261 }
2262
2263 if (memEqualCI(s, "reply-to:", q - s)) {
2264 linetype = 'r';
2265 if (!w->reply[0])
2266 strncpy(w->reply, vl, vr - vl);
2267 continue;
2268 }
2269
2270 if (memEqualCI(s, "message-id:", q - s)) {
2271 linetype = 'm';
2272 if (!w->mid[0])
2273 strncpy(w->mid, vl, vr - vl);
2274 continue;
2275 }
2276
2277 if (memEqualCI(s, "references:", q - s)) {
2278 linetype = 'e';
2279 if (!w->ref[0])
2280 strncpy(w->ref, vl, vr - vl);
2281 continue;
2282 }
2283
2284 if (memEqualCI(s, "from:", q - s)) {
2285 linetype = 'f';
2286 if (w->from[0])
2287 continue;
2288 isoDecode(vl, &vr);
2289 strncpy(w->from, vl, vr - vl);
2290 mhReformat(w->from);
2291 continue;
2292 }
2293
2294 if (memEqualCI(s, "date:", q - s)
2295 || memEqualCI(s, "sent:", q - s)) {
2296 linetype = 'd';
2297 if (w->date[0])
2298 continue;
2299 /* don't need the weekday, seconds, or timezone */
2300 if (vr - vl > 5 &&
2301 isalphaByte(vl[0]) && isalphaByte(vl[1])
2302 && isalphaByte(vl[2]) && vl[3] == ','
2303 && vl[4] == ' ')
2304 vl += 5;
2305 strncpy(w->date, vl, vr - vl);
2306 q = strrchr(w->date, ':');
2307 if (q)
2308 *q = 0;
2309 continue;
2310 }
2311
2312 if (memEqualCI(s, "to:", q - s)) {
2313 linetype = 't';
2314 if (w->tolen)
2315 stringAndChar(&w->tolist, &w->tolen, ',');
2316 stringAndBytes(&w->tolist, &w->tolen, q, vr - q);
2317 if (w->to[0])
2318 continue;
2319 strncpy(w->to, vl, vr - vl);
2320 /* Only retain the first recipient */
2321 quote = 0;
2322 for (q = w->to; *q; ++q) {
2323 if (*q == ',' && !quote) {
2324 w->andOthers = true;
2325 break;
2326 }
2327 if (*q == '"') {
2328 if (!quote)
2329 quote = *q;
2330 else if (quote == *q)
2331 quote = 0;
2332 continue;
2333 }
2334 if (*q == '<') {
2335 if (!quote)
2336 quote = *q;
2337 continue;
2338 }
2339 if (*q == '>') {
2340 if (quote == '<')
2341 quote = 0;
2342 continue;
2343 }
2344 }
2345 *q = 0; /* cut it off at the comma */
2346 continue;
2347 }
2348
2349 if (memEqualCI(s, "cc:", q - s)) {
2350 linetype = 'y';
2351 if (w->cclen)
2352 stringAndChar(&w->cclist, &w->cclen, ',');
2353 stringAndBytes(&w->cclist, &w->cclen, q, vr - q);
2354 w->andOthers = true;
2355 continue;
2356 }
2357
2358 if (memEqualCI(s, "content-type:", q - s)) {
2359 linetype = 'c';
2360 if (memEqualCI(vl, "application/pgp-signature", 25))
2361 w->pgp = true;
2362 if (memEqualCI(vl, "text", 4))
2363 w->ct = CT_RICH;
2364 if (memEqualCI(vl, "text/html", 9))
2365 w->ct = CT_HTML;
2366 if (memEqualCI(vl, "text/plain", 10))
2367 w->ct = CT_TEXT;
2368 if (memEqualCI(vl, "application", 11))
2369 w->ct = CT_APPLIC;
2370 if (memEqualCI(vl, "multipart", 9))
2371 w->ct = CT_MULTI;
2372 if (memEqualCI(vl, "multipart/alternative", 21))
2373 w->ct = CT_ALT;
2374
2375 ctExtras(w, s, t);
2376 continue;
2377 }
2378
2379 if (memEqualCI(s, "content-transfer-encoding:", q - s)) {
2380 linetype = 'e';
2381 if (memEqualCI(vl, "quoted-printable", 16))
2382 w->ce = CE_QP;
2383 if (memEqualCI(vl, "7bit", 4))
2384 w->ce = CE_7BIT;
2385 if (memEqualCI(vl, "8bit", 4))
2386 w->ce = CE_8BIT;
2387 if (memEqualCI(vl, "base64", 6))
2388 w->ce = CE_64;
2389 continue;
2390 }
2391
2392 linetype = 0;
2393 } /* loop over lines */
2394
2395 /* make sure there's room for a final nl */
2396 stringAndChar(&w->tolist, &w->tolen, ' ');
2397 stringAndChar(&w->cclist, &w->cclen, ' ');
2398 extractEmailAddresses(w->tolist);
2399 extractEmailAddresses(w->cclist);
2400
2401 w->start = start = s + 1;
2402
2403 /* Fix up reply and from lines.
2404 * From should be the name, reply the address. */
2405 if (!w->from[0])
2406 strcpy(w->from, w->reply);
2407 if (!w->reply[0])
2408 strcpy(w->reply, w->from);
2409 if (w->from[0] == '"') {
2410 strmove(w->from, w->from + 1);
2411 q = strchr(w->from, '"');
2412 if (q)
2413 *q = 0;
2414 }
2415 vl = strchr(w->from, '<');
2416 vr = strchr(w->from, '>');
2417 if (vl && vr && vl < vr) {
2418 while (vl > w->from && vl[-1] == ' ')
2419 --vl;
2420 *vl = 0;
2421 }
2422 extractLessGreater(w->reply);
2423 /* get rid of (name) comment */
2424 vl = strchr(w->reply, '(');
2425 vr = strchr(w->reply, ')');
2426 if (vl && vr && vl < vr) {
2427 while (vl > w->reply && vl[-1] == ' ')
2428 --vl;
2429 *vl = 0;
2430 }
2431 /* no @ means it's not an email address */
2432 if (!strchr(w->reply, '@'))
2433 w->reply[0] = 0;
2434 if (stringEqual(w->reply, w->from))
2435 w->from[0] = 0;
2436 extractLessGreater(w->to);
2437 extractLessGreater(w->mid);
2438 extractLessGreater(w->ref);
2439
2440 cutDuplicateEmails(w->tolist, w->cclist, w->reply);
2441 if (debugLevel >= 5) {
2442 puts("mail header analyzed");
2443 printf("subject: %s\n", w->subject);
2444 printf("from: %s\n", w->from);
2445 printf("date: %s\n", w->date);
2446 printf("reply: %s\n", w->reply);
2447 printf("tolist: %s\n", w->tolist);
2448 printf("cclist: %s\n", w->cclist);
2449 printf("reference: %s\n", w->ref);
2450 printf("message: %s\n", w->mid);
2451 printf("boundary: %d|%s\n", w->boundlen, w->boundary);
2452 printf("filename: %s\n", w->cfn);
2453 printf("content %d/%d\n", w->ct, w->ce);
2454 }
2455
2456 if (w->ce == CE_QP)
2457 unpackQP(w);
2458 if (w->ce == CE_64) {
2459 w->error64 = base64Decode(w->start, &w->end);
2460 if (w->error64 != GOOD_BASE64_DECODE)
2461 mail64Error(w->error64);
2462 }
2463 if ((w->ce == CE_64 && w->ct == CT_OTHER) ||
2464 w->ct == CT_APPLIC || w->cfn[0]) {
2465 w->doAttach = true;
2466 ++nattach;
2467 q = w->cfn;
2468 if (*q) { /* name present */
2469 if (stringEqual(q, "winmail.dat")) {
2470 w->atimage = true;
2471 ++nimages;
2472 } else if ((q = strrchr(q, '.'))) {
2473 static const char *const imagelist[] = {
2474 "gif", "jpg", "tif", "bmp", "asc",
2475 "png", 0
2476 };
2477 /* the asc isn't an image, it's a signature card. */
2478 /* Similarly for the winmail.dat */
2479 if (stringInListCI(imagelist, q + 1) >= 0) {
2480 w->atimage = true;
2481 ++nimages;
2482 }
2483 }
2484 if (!w->atimage && nattach == nimages + 1)
2485 firstAttach = w->cfn;
2486 }
2487 return w;
2488 }
2489
2490 /* loop over the mime components */
2491 if (w->ct == CT_MULTI || w->ct == CT_ALT) {
2492 char *lastbound = 0;
2493 bool endmode = false;
2494 struct MHINFO *child;
2495 /* We really need the -1 here, because sometimes the boundary will
2496 * be the very first thing in the message body. */
2497 s = w->start - 1;
2498 while (!endmode && (t = strstr(s, "\n--")) && t < end) {
2499 if (memcmp(t + 3, w->boundary, w->boundlen)) {
2500 s = t + 3;
2501 continue;
2502 }
2503 q = t + 3 + w->boundlen;
2504 while (*q == '-')
2505 endmode = true, ++q;
2506 if (*q == '\n')
2507 ++q;
2508 debugPrint(5, "boundary found at offset %d",
2509 t - w->start);
2510 if (lastbound) {
2511 child = headerGlean(lastbound, t);
2512 addToListBack(&w->components, child);
2513 }
2514 s = lastbound = q;
2515 }
2516 w->start = w->end = 0;
2517 return w;
2518 }
2519
2520 /* mime or alt */
2521 /* Scan through, we might have a mail message included inline */
2522 vl = 0; /* first mail header keyword line */
2523 for (s = start; s < end; s = t + 1) {
2524 char first = *s;
2525 t = strchr(s, '\n');
2526 if (!t)
2527 t = end - 1; /* should never happen */
2528 if (t == s) { /* empty line */
2529 if (!vl)
2530 continue;
2531 /* Do we have enough for a mail header? */
2532 if (k >= 4 && k * 2 >= j) {
2533 struct MHINFO *child = headerGlean(vl, end);
2534 addToListBack(&w->components, child);
2535 w->end = end = vl;
2536 goto textonly;
2537 } /* found mail message inside */
2538 vl = 0;
2539 } /* empty line */
2540 if (first == ' ' || first == '\t')
2541 continue; /* indented */
2542 for (q = s; isalnumByte(*q) || *q == '_' || *q == '-'; ++q) ;
2543 if (q == s || *q != ':') {
2544 vl = 0;
2545 continue;
2546 }
2547 /* looks like header: stuff */
2548 if (!vl) {
2549 vl = s;
2550 j = k = 0;
2551 }
2552 ++j;
2553 for (n = 0; mhwords[n]; ++n)
2554 if (memEqualCI(mhwords[n], s, q - s))
2555 break;
2556 if (mhwords[n])
2557 ++k;
2558 } /* loop over lines */
2559
2560 /* Header could be at the very end */
2561 if (vl && k >= 4 && k * 2 >= j) {
2562 struct MHINFO *child = headerGlean(vl, end);
2563 addToListBack(&w->components, child);
2564 w->end = end = vl;
2565 }
2566
2567 textonly:
2568 /* Any additional processing of the text, from start to end, can go here. */
2569 /* Remove leading blank lines or lines with useless words */
2570 for (s = start; s < end; s = t + 1) {
2571 t = strchr(s, '\n');
2572 if (!t)
2573 t = end - 1; /* should never happen */
2574 vl = s, vr = t;
2575 if (vr - vl >= 4 && memEqualCI(vr - 4, "<br>", 4))
2576 vr -= 4;
2577 while (vl < vr) {
2578 if (isalnumByte(*vl))
2579 break;
2580 ++vl;
2581 }
2582 while (vl < vr) {
2583 if (isalnumByte(vr[-1]))
2584 break;
2585 --vr;
2586 }
2587 if (vl == vr)
2588 continue; /* empty */
2589 if (memEqualCI(vl, "forwarded message", vr - vl))
2590 continue;
2591 if (memEqualCI(vl, "original message", vr - vl))
2592 continue;
2593 break; /* something real */
2594 }
2595 w->start = start = s;
2596
2597 return w;
2598 } /* headerGlean */
2599
headerShow(struct MHINFO * w,bool top)2600 static char *headerShow(struct MHINFO *w, bool top)
2601 {
2602 static char buf[(MHLINE + 30) * 4];
2603 static char lastsubject[MHLINE];
2604 char *s;
2605 bool lines = false;
2606 buf[0] = 0;
2607
2608 if (!(w->subject[0] | w->from[0] | w->reply[0]))
2609 return buf;
2610
2611 if (!top) {
2612 strcpy(buf, "Message");
2613 lines = true;
2614 if (w->from[0]) {
2615 strcat(buf, " from ");
2616 strcat(buf, w->from);
2617 }
2618 if (w->subject[0]) {
2619 if (stringEqual(w->subject, lastsubject)) {
2620 strcat(buf, " with the same subject");
2621 } else {
2622 strcat(buf, " with subject: ");
2623 strcat(buf, w->subject);
2624 }
2625 } else
2626 strcat(buf, " with no subject");
2627 if (mailIsHtml) { /* trash & < > */
2628 for (s = buf; *s; ++s) {
2629 /* This is quick and stupid */
2630 if (*s == '<')
2631 *s = '(';
2632 if (*s == '>')
2633 *s = ')';
2634 if (*s == '&')
2635 *s = '*';
2636 }
2637 }
2638 /* need a dot at the end? */
2639 s = buf + strlen(buf);
2640 if (isalnumByte(s[-1]))
2641 *s++ = '.';
2642 strcpy(s, mailIsHtml ? "\n<br>" : "\n");
2643 if (w->date[0]) {
2644 strcat(buf, "Sent ");
2645 strcat(buf, w->date);
2646 }
2647 if (w->reply[0]) {
2648 if (!w->date[0])
2649 strcat(buf, "From ");
2650 else
2651 strcat(buf, " from ");
2652 strcat(buf, w->reply);
2653 }
2654 if (w->date[0] | w->reply[0]) { /* second line */
2655 strcat(buf, "\n");
2656 }
2657 } else {
2658
2659 /* This is at the top of the file */
2660 if (w->subject[0]) {
2661 sprintf(buf, "Subject: %s\n", w->subject);
2662 lines = true;
2663 }
2664 if (nattach && ismc) {
2665 char atbuf[20];
2666 if (lines & mailIsHtml)
2667 strcat(buf, "<br>");
2668 lines = true;
2669 if (nimages) {
2670 sprintf(atbuf, "%d images\n", nimages);
2671 if (nimages == 1)
2672 strcpy(atbuf, "1 image");
2673 strcat(buf, atbuf);
2674 if (nattach > nimages)
2675 strcat(buf, " + ");
2676 }
2677 if (nattach == nimages + 1) {
2678 strcat(buf, "1 attachment");
2679 if (firstAttach && firstAttach[0]) {
2680 strcat(buf, " ");
2681 strcat(buf, firstAttach);
2682 }
2683 }
2684 if (nattach > nimages + 1) {
2685 sprintf(atbuf, "%d attachments\n",
2686 nattach - nimages);
2687 strcat(buf, atbuf);
2688 }
2689 strcat(buf, "\n");
2690 }
2691 /* attachments */
2692 if (w->to[0] && !ismc) {
2693 if (lines & mailIsHtml)
2694 strcat(buf, "<br>");
2695 lines = true;
2696 strcat(buf, "To ");
2697 strcat(buf, w->to);
2698 if (w->andOthers)
2699 strcat(buf, " and others");
2700 strcat(buf, "\n");
2701 }
2702 if (w->from[0]) {
2703 if (lines & mailIsHtml)
2704 strcat(buf, "<br>");
2705 lines = true;
2706 strcat(buf, "From ");
2707 strcat(buf, w->from);
2708 strcat(buf, "\n");
2709 }
2710 if (w->date[0] && !ismc) {
2711 if (lines & mailIsHtml)
2712 strcat(buf, "<br>");
2713 lines = true;
2714 strcat(buf, "Mail sent ");
2715 strcat(buf, w->date);
2716 strcat(buf, "\n");
2717 }
2718 if (w->reply[0]) {
2719 if (lines & mailIsHtml)
2720 strcat(buf, "<br>");
2721 lines = true;
2722 strcat(buf, "Reply to ");
2723 strcat(buf, w->reply);
2724 strcat(buf, "\n");
2725 }
2726 }
2727
2728 if (lines)
2729 strcat(buf, mailIsHtml ? "<P>\n" : "\n");
2730 strcpy(lastsubject, w->subject);
2731 return buf;
2732 } /* headerShow */
2733
2734 /* Depth first block of text determines the type */
mailTextType(struct MHINFO * w)2735 static int mailTextType(struct MHINFO *w)
2736 {
2737 struct MHINFO *v;
2738 int texttype = CT_OTHER, rc;
2739
2740 if (w->doAttach)
2741 return CT_OTHER;
2742
2743 /* jump right into the hard part, multi/alt */
2744 if (w->ct >= CT_MULTI) {
2745 foreach(v, w->components) {
2746 rc = mailTextType(v);
2747 if (rc == CT_HTML)
2748 return rc;
2749 if (rc == CT_OTHER)
2750 continue;
2751 if (w->ct == CT_MULTI)
2752 return rc;
2753 texttype = rc;
2754 }
2755 return texttype;
2756 }
2757
2758 /* multi */
2759 /* If there is no text, return nothing */
2760 if (w->start == w->end)
2761 return CT_OTHER;
2762 /* I don't know if this is right, but I override the type,
2763 * and make it html, if we start out with <html> */
2764 if (memEqualCI(w->start, "<html>", 6))
2765 return CT_HTML;
2766 return w->ct == CT_HTML ? CT_HTML : CT_TEXT;
2767 } /* mailTextType */
2768
formatMail(struct MHINFO * w,bool top)2769 static void formatMail(struct MHINFO *w, bool top)
2770 {
2771 struct MHINFO *v;
2772 int ct = w->ct;
2773 int j, best;
2774
2775 if (w->doAttach)
2776 return;
2777 debugPrint(5, "format headers for content %d subject %s", ct,
2778 w->subject);
2779 stringAndString(&fm, &fm_l, headerShow(w, top));
2780
2781 if (ct < CT_MULTI) {
2782 char *start = w->start;
2783 char *end = w->end;
2784 int newlen;
2785 /* If mail is not in html, reformat it */
2786 if (start < end) {
2787 if (ct == CT_TEXT) {
2788 breakLineSetup();
2789 if (breakLine(start, end - start, &newlen)) {
2790 start = breakLineResult;
2791 end = start + newlen;
2792 }
2793 }
2794 if (mailIsHtml && ct != CT_HTML)
2795 stringAndString(&fm, &fm_l, "<pre>");
2796 stringAndBytes(&fm, &fm_l, start, end - start);
2797 if (mailIsHtml && ct != CT_HTML)
2798 stringAndString(&fm, &fm_l, "</pre>\n");
2799 }
2800
2801 /* text present */
2802 /* There could be a mail message inline */
2803 foreach(v, w->components) {
2804 if (end > start)
2805 stringAndString(&fm, &fm_l,
2806 mailIsHtml ? "<P>\n" : "\n");
2807 formatMail(v, false);
2808 }
2809
2810 return;
2811 }
2812
2813 if (ct == CT_MULTI) {
2814 foreach(v, w->components)
2815 formatMail(v, false);
2816 return;
2817 }
2818
2819 /* alternate presentations, pick the best one */
2820 best = j = 0;
2821 foreach(v, w->components) {
2822 int subtype = mailTextType(v);
2823 ++j;
2824 if (subtype != CT_OTHER)
2825 best = j;
2826 if ((mailIsHtml && subtype == CT_HTML) ||
2827 (!mailIsHtml && subtype == CT_TEXT))
2828 break;
2829 }
2830
2831 if (!best)
2832 best = 1;
2833 j = 0;
2834 foreach(v, w->components) {
2835 ++j;
2836 if (j != best)
2837 continue;
2838 formatMail(v, false);
2839 break;
2840 }
2841 } /* formatMail */
2842
2843 /* Browse the email file. */
emailParse(char * buf)2844 char *emailParse(char *buf)
2845 {
2846 struct MHINFO *w;
2847 nattach = nimages = 0;
2848 firstAttach = 0;
2849 mailIsHtml = ignoreImages = false;
2850 fm = initString(&fm_l);
2851 w = headerGlean(buf, buf + strlen(buf));
2852 mailIsHtml = (mailTextType(w) == CT_HTML);
2853 if (mailIsHtml)
2854 stringAndString(&fm, &fm_l, "<html>\n");
2855 formatMail(w, true);
2856 /* Remember, we always need a nonzero buffer */
2857 if (!fm_l || fm[fm_l - 1] != '\n')
2858 stringAndChar(&fm, &fm_l, '\n');
2859 cw->mailInfo =
2860 allocMem(strlen(w->ref) + strlen(w->mid) +
2861 strlen(w->tolist) + strlen(w->cclist) +
2862 strlen(w->reply) + 6);
2863 sprintf(cw->mailInfo, "%s>%s>%s>%s>%s>", w->reply, w->tolist,
2864 w->cclist, w->ref, w->mid);
2865 if (!ismc) {
2866 writeAttachments(w);
2867 freeMailInfo(w);
2868 nzFree(buf);
2869 debugPrint(5, "mailInfo: %s", cw->mailInfo);
2870 } else {
2871 lastMailInfo = w;
2872 lastMailText = buf;
2873 }
2874 return fm;
2875 } /* emailParse */
2876
2877 /*********************************************************************
2878 Set up for a reply.
2879 This looks at the first 5 lines, which could contain
2880 subject
2881 to
2882 reply to
2883 from
2884 mail send
2885 in no particular order.
2886 Move replyt to the top and get rid of the others.
2887 Then, if you have browsed a mail file,
2888 grab the message id and reference it.
2889 Also, if mailing to all, stick in the other recipients.
2890 *********************************************************************/
2891
setupReply(bool all)2892 bool setupReply(bool all)
2893 {
2894 int subln, repln;
2895 char linetype[8];
2896 int j;
2897 char *out, *s, *t;
2898 bool rc;
2899
2900 /* basic sanity */
2901 if (cw->dirMode) {
2902 setError(MSG_ReDir);
2903 return false;
2904 }
2905
2906 if (cw->sqlMode) {
2907 setError(MSG_ReDB);
2908 return false;
2909 }
2910
2911 if (!cw->dol) {
2912 setError(MSG_ReEmpty);
2913 return false;
2914 }
2915
2916 if (cw->binMode) {
2917 setError(MSG_ReBinary);
2918 return false;
2919 }
2920
2921 subln = repln = 0;
2922 strcpy(linetype, " xxxxxx");
2923 for (j = 1; j <= 6; ++j) {
2924 char *p;
2925 if (j > cw->dol)
2926 break;
2927
2928 p = (char *)fetchLine(j, 1);
2929
2930 if (memEqualCI(p, "subject:", 8)) {
2931 linetype[j] = 's';
2932 subln = j;
2933 goto nextline;
2934 }
2935
2936 if (memEqualCI(p, "to ", 3)) {
2937 linetype[j] = 't';
2938 goto nextline;
2939 }
2940
2941 if (memEqualCI(p, "from ", 5)) {
2942 linetype[j] = 'f';
2943 goto nextline;
2944 }
2945
2946 if (memEqualCI(p, "mail sent ", 10)) {
2947 linetype[j] = 'w';
2948 goto nextline;
2949 }
2950
2951 if (memEqualCI(p, "references:", 11)) {
2952 linetype[j] = 'v';
2953 goto nextline;
2954 }
2955
2956 if (memEqualCI(p, "reply to ", 9)) {
2957 linetype[j] = 'r';
2958 repln = j;
2959 goto nextline;
2960 }
2961
2962 /* This one has to be last. */
2963 s = p;
2964 while (isdigitByte(*s))
2965 ++s;
2966 if (memEqualCI(s, " attachment", 11)
2967 || memEqualCI(s, " image", 6)) {
2968 linetype[j] = 'a';
2969 goto nextline;
2970 }
2971
2972 /* line doesn't match anything we know */
2973 nzFree(p);
2974 break;
2975
2976 nextline:
2977 nzFree(p);
2978 }
2979
2980 if (!subln || !repln) {
2981 setError(MSG_ReSubjectReply);
2982 return false;
2983 }
2984
2985 /* delete the lines we don't need */
2986 linetype[j] = 0;
2987 for (--j; j >= 1; --j) {
2988 if (strchr("srv", linetype[j]))
2989 continue;
2990 delText(j, j);
2991 strmove(linetype + j, linetype + j + 1);
2992 }
2993
2994 /* move reply to 1, if it isn't already there */
2995 repln = strchr(linetype, 'r') - linetype;
2996 subln = strchr(linetype, 's') - linetype;
2997 if (repln != 1) {
2998 struct lineMap *map = cw->map;
2999 struct lineMap swap;
3000 struct lineMap *q1 = map + 1;
3001 struct lineMap *q2 = map + repln;
3002 swap = *q1;
3003 *q1 = *q2;
3004 *q2 = swap;
3005 if (subln == 1)
3006 subln = repln;
3007 repln = 1;
3008 }
3009
3010 j = strlen(linetype) - 1;
3011 if (j != subln) {
3012 struct lineMap *map = cw->map;
3013 struct lineMap swap;
3014 struct lineMap *q1 = map + j;
3015 struct lineMap *q2 = map + subln;
3016 swap = *q1;
3017 *q1 = *q2;
3018 *q2 = swap;
3019 }
3020
3021 readReplyInfo();
3022
3023 if (!cw->mailInfo) {
3024 if (all) {
3025 setError(MSG_ReNoInfo);
3026 return false;
3027 }
3028 return true; /* that's all we can do */
3029 }
3030
3031 /* Build the header lines and put them in the buffer */
3032 out = initString(&j);
3033 /* step through the to list */
3034 s = strchr(cw->mailInfo, '>') + 1;
3035 while (*s != '>') {
3036 t = strchr(s, ',');
3037 if (all) {
3038 stringAndString(&out, &j, "to: ");
3039 stringAndBytes(&out, &j, s, t - s);
3040 stringAndChar(&out, &j, '\n');
3041 }
3042 s = t + 1;
3043 }
3044
3045 /* step through the cc list */
3046 ++s;
3047 while (*s != '>') {
3048 t = strchr(s, ',');
3049 if (all) {
3050 stringAndString(&out, &j, "cc: ");
3051 stringAndBytes(&out, &j, s, t - s);
3052 stringAndChar(&out, &j, '\n');
3053 }
3054 s = t + 1;
3055 }
3056
3057 ++s;
3058 t = strchr(s, '>');
3059 if (t[1] == '>') {
3060 i_puts(MSG_ReNoID);
3061 } else {
3062 stringAndString(&out, &j, "References: <");
3063 if (*s != '>') {
3064 stringAndBytes(&out, &j, s, t - s);
3065 stringAndString(&out, &j, "> <");
3066 }
3067 stringAndString(&out, &j, t + 1);
3068 stringAndChar(&out, &j, '\n');
3069 }
3070
3071 rc = true;
3072 if (j)
3073 rc = addTextToBuffer((unsigned char *)out, j, 1, false);
3074 nzFree(out);
3075 return rc;
3076 } /* setupReply */
3077
writeReplyInfo(const char * addstring)3078 static void writeReplyInfo(const char *addstring)
3079 {
3080 int rfh; /* reply file handle */
3081 rfh = open(mailReply, O_WRONLY | O_APPEND | O_CREAT, MODE_private);
3082 if (rfh < 0)
3083 return;
3084 write(rfh, addstring, 12);
3085 write(rfh, cw->mailInfo, strlen(cw->mailInfo));
3086 write(rfh, "\n", 1);
3087 close(rfh);
3088 } /* writeReplyInfo */
3089
readReplyInfo(void)3090 static void readReplyInfo(void)
3091 {
3092 int rfh; /* reply file handle */
3093 const char *p;
3094 int ln, major, minor;
3095 char prestring[20];
3096 char *buf;
3097 int buflen;
3098 char *s, *t;
3099
3100 if (cw->mailInfo)
3101 return; /* already there */
3102
3103 /* scan through the buffer looking for the Unformatted line,
3104 * but stop if you hit an email divider. */
3105 for (ln = 1; ln <= cw->dol; ++ln) {
3106 p = (char *)fetchLine(ln, -1);
3107 if (!memcmp
3108 (p,
3109 "======================================================================\n",
3110 71))
3111 return;
3112 if (pstLength((pst) p) == 24
3113 && sscanf(p, "Unformatted %d.%d", &major, &minor) == 2)
3114 goto found;
3115 }
3116 return;
3117
3118 found:
3119 /* prestring is the key */
3120 sprintf(prestring, "%05d.%05d:", major, minor);
3121 rfh = open(mailReply, O_RDONLY);
3122 if (rfh < 0)
3123 return;
3124 if (!fdIntoMemory(rfh, &buf, &buflen))
3125 return;
3126 close(rfh);
3127
3128 /* loop through lines looking for the key */
3129 for (s = buf; *s; s = t + 1) {
3130 t = strchr(s, '\n');
3131 if (!t)
3132 break;
3133 if (memcmp(s, prestring, 12))
3134 continue;
3135 /* key match, put this string into mailInfo */
3136 s += 12;
3137 cw->mailInfo = pullString(s, t - s);
3138 break;
3139 }
3140
3141 nzFree(buf);
3142 } /* readReplyInfo */
3143