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