1 /* sendmail.c
2  * Send mail using the smtp protocol.
3  * Send the contents of a file, or the current edbrowse buffer.
4  * This file is part of the edbrowse project, released under GPL.
5  */
6 
7 #include "eb.h"
8 
9 #include <time.h>
10 
11 #define MAXRECAT 100		/* max number of recipients or attachments */
12 #define MAXMSLINE 1024		/* max mail server line */
13 
14 static char serverLine[MAXMSLINE];
15 static bool doSignature;
16 static char subjectLine[400];
17 static int mailAccount;
18 
19 static struct ALIAS {
20 	char name[16];
21 	char email[64];
22 } *addressList;
23 static int nads;		/* number of addresses */
24 static time_t adbooktime;
25 
26 /* read and/or refresh the address book */
loadAddressBook(void)27 bool loadAddressBook(void)
28 {
29 	char *buf, *bufend, *v, *last, *s, *t;
30 	bool cmt = false;
31 	char state = 0, c;
32 	int j, buflen, ln = 1;
33 	time_t mtime;
34 
35 	if (!addressFile ||
36 	    (mtime = fileTimeByName(addressFile)) == -1 || mtime <= adbooktime)
37 		return true;
38 
39 	debugPrint(3, "loading address book");
40 	nzFree(addressList);
41 	addressList = 0;
42 	nads = 0;
43 	if (!fileIntoMemory(addressFile, &buf, &buflen))
44 		return false;
45 	bufend = buf + buflen;
46 
47 	for (s = t = last = buf; s < bufend; ++s) {
48 		c = *s;
49 		if (cmt) {
50 			if (c != '\n')
51 				continue;
52 			cmt = false;
53 		}
54 		if (c == ':') {	/* delimiter */
55 			if (state == 0) {
56 				setError(MSG_ABNoAlias, ln);
57 freefail:
58 				nzFree(buf);
59 				nads = 0;
60 				return false;
61 			}
62 			while (t[-1] == ' ' || t[-1] == '\t')
63 				--t;
64 			if (state == 1) {
65 				*t++ = c;
66 				state = 2;
67 				continue;
68 			}
69 			c = '#';	/* extra fields are ignored */
70 		}		/* : */
71 		if (c == '#') {
72 			cmt = true;
73 			continue;
74 		}
75 		if (c == '\n') {
76 			++ln;
77 			if (state == 0)
78 				continue;
79 			if (state == 1) {
80 				setError(MSG_ABNoColon, ln - 1);
81 				goto freefail;
82 			}
83 			if (state == 3) {
84 				++nads;
85 				while (isspaceByte(t[-1]))
86 					--t;
87 				*t = 0;
88 				v = strchr(last, ':');
89 				if (v - last >= 16) {
90 					setError(MSG_ABAliasLong, ln - 1);
91 					goto freefail;
92 				}
93 				++v;
94 				if (t - v >= 64) {
95 					setError(MSG_ABMailLong, ln - 1);
96 					goto freefail;
97 				}
98 				if (!strchr(v, '@')) {
99 					setError(MSG_ABNoAt, ln - 1);
100 					goto freefail;
101 				}
102 				if (strpbrk(v, " \t")) {
103 					setError(MSG_ABMailSpaces, ln - 1);
104 					goto freefail;
105 				}
106 
107 				while (last < t) {
108 					if (!isprintByte(*last)) {
109 						setError(MSG_AbMailUnprintable,
110 							 ln - 1);
111 						goto freefail;
112 					}
113 					++last;
114 				}
115 				*t++ = c;
116 			} else
117 				t = last;	/* back it up */
118 			last = t;
119 			state = 0;
120 			continue;
121 		}
122 		/* nl */
123 		if ((c == ' ' || c == '\t') && (state == 0 || state == 2))
124 			continue;
125 		if (state == 0)
126 			state = 1;
127 		if (state == 2)
128 			state = 3;
129 		*t++ = c;
130 	}
131 
132 	*t = 0;
133 	if (state) {
134 		setError(MSG_ABUnterminated);
135 		goto freefail;
136 	}
137 
138 	if (nads) {
139 		addressList = allocMem(nads * sizeof(struct ALIAS));
140 		j = 0;
141 		for (s = buf; *s; s = t + 1, ++j) {
142 			t = strchr(s, ':');
143 			memcpy(addressList[j].name, s, t - s);
144 			addressList[j].name[t - s] = 0;
145 			s = t + 1;
146 			t = strchr(s, '\n');
147 			memcpy(addressList[j].email, s, t - s);
148 			addressList[j].email[t - s] = 0;
149 		}
150 	}
151 	/* aliases are present */
152 	nzFree(buf);
153 	adbooktime = mtime;
154 	return true;
155 }				/* loadAddressBook */
156 
reverseAlias(const char * reply)157 const char *reverseAlias(const char *reply)
158 {
159 	int i;
160 	for (i = 0; i < nads; ++i)
161 		if (stringEqual(reply, addressList[i].email)) {
162 			const char *a = addressList[i].name;
163 			if (*a == '!')
164 				break;
165 			return a;
166 		}
167 	return 0;		/* not found */
168 }				/* reverseAlias */
169 
qpEncode(const char * line)170 static char *qpEncode(const char *line)
171 {
172 	char *newbuf;
173 	int l;
174 	const char *s;
175 	char c;
176 
177 	newbuf = initString(&l);
178 	for (s = line; (c = *s); ++s) {
179 		if ((c < '\n' && c != '\t') || c == '=') {
180 			char expand[4];
181 			sprintf(expand, "=%02X", (uchar) c);
182 			stringAndString(&newbuf, &l, expand);
183 		} else {
184 			stringAndChar(&newbuf, &l, c);
185 		}
186 	}
187 
188 	return newbuf;
189 }				/* qpEncode */
190 
191 /* Return 0 if there was no need to encode */
isoEncode(char * start,char * end)192 static char *isoEncode(char *start, char *end)
193 {
194 	int nacount = 0, count = 0, len;
195 	char *s, *t;
196 	char c, code;
197 
198 	for (s = start; s < end; ++s) {
199 		c = *s;
200 		if (c == 0)
201 			*s = ' ';
202 		if (isspaceByte(c))
203 			*s = ' ';
204 	}
205 
206 	for (s = start; s < end; ++s) {
207 		c = *s;
208 		++count;
209 		if (!isprintByte(c) && c != ' ')
210 			++nacount;
211 	}
212 
213 	if (!nacount)
214 		return 0;
215 
216 	if (nacount * 4 >= count && count > 8) {
217 		code = 'B';
218 		s = base64Encode(start, end - start, false);
219 		goto package;
220 	}
221 
222 	code = 'Q';
223 	s = qpEncode(start);
224 
225 package:
226 	len = strlen(s);
227 	t = allocMem(len + 20);
228 	sprintf(t, "=?ISO-8859-1?%c?%s?=", code, s);
229 	nzFree(s);
230 	return t;
231 }				/* isoEncode */
232 
233 /*********************************************************************
234 Return a string that defines the charset of the outgoing mail.
235 This just looks at your language setting - reaaly dumb.
236 It should interrogate each file/attachment.
237 Well, this will get us started.
238 *********************************************************************/
239 
charsetString(const char * ct,const char * ce)240 static char *charsetString(const char *ct, const char *ce)
241 {
242 	static char buf[24];
243 	buf[0] = 0;
244 	if (!stringEqual(ce, "7bit") &&
245 	    (stringEqual(ct, "text/plain") || stringEqual(ct, "text/html"))) {
246 		if (cons_utf8)
247 			strcpy(buf, "; charset=utf-8");
248 		else
249 			sprintf(buf, "; charset=iso-8859-%d", type8859);
250 	}
251 	return buf;
252 }				/* charsetString */
253 
254 /* Read a file into memory, mime encode it,
255  * and return the type of encoding and the encoded data.
256  * Last three parameters are result parameters.
257  * If ismail is nonzero, the file is the mail, not an attachment.
258  * In fact ismail indicates the line that holds the subject.
259  * If ismail is negative, then -ismail indicates the subject line,
260  * and the string file is not the filename, but rather, the mail to send. */
261 bool
encodeAttachment(const char * file,int ismail,bool webform,const char ** type_p,const char ** enc_p,char ** data_p)262 encodeAttachment(const char *file, int ismail, bool webform,
263 		 const char **type_p, const char **enc_p, char **data_p)
264 {
265 	char *buf;
266 	char c;
267 	bool longline;
268 	char *s, *t, *v;
269 	char *ct, *ce;		/* content type, content encoding */
270 	int buflen, i, cx;
271 	int nacount, nullcount, nlcount;
272 
273 	if (ismail < 0) {
274 		buf = cloneString(file);
275 		buflen = strlen(buf);
276 		ismail = -ismail;
277 		file = emptyString;
278 	} else {
279 
280 		if (!ismc && (cx = stringIsNum(file)) >= 0) {
281 			static char newfilename[16];
282 			if (!unfoldBuffer(cx, false, &buf, &buflen))
283 				return false;
284 			if (!buflen) {
285 				if (webform) {
286 empty:
287 					buf = emptyString;
288 					ct = "text/plain";
289 					ce = "7bit";
290 					goto success;
291 				}
292 				setError(MSG_BufferXEmpty, cx);
293 				goto freefail;
294 			}
295 			sprintf(newfilename, "<buffer %d>", cx);
296 			file = newfilename;
297 			if (sessionList[cx].lw->f0.fileName)
298 				file = sessionList[cx].lw->f0.fileName;
299 		} else {
300 			if (!fileIntoMemory(file, &buf, &buflen))
301 				return false;
302 			if (!buflen) {
303 				if (webform)
304 					goto empty;
305 				setError(MSG_FileXEmpty, file);
306 				goto freefail;
307 			}
308 		}
309 	}			/* ismail negative or normal */
310 
311 	if (ismail) {
312 /* Put newline at the end.  Yes, the buffer is allocated
313  * with space for newline and null. */
314 		if (buf[buflen - 1] != '\n')
315 			buf[buflen++] = '\n';
316 /* check for subject: line */
317 		s = buf;
318 		i = ismail;
319 		while (--i) {
320 			while (*s != '\n')
321 				++s;
322 			++s;
323 		}
324 		while (*s == ' ' || *s == '\t')
325 			++s;
326 		if (!memEqualCI(s, "subject:", 8)) {
327 			setError(MSG_SubjectStart);
328 			goto freefail;
329 		}
330 		s += 8;
331 		while (*s == ' ' || *s == '\t')
332 			++s;
333 		t = s;
334 		while (*s != '\n')
335 			++s;
336 		v = s;
337 		while (s > t && isspaceByte(s[-1]))
338 			--s;
339 		if (s - t >= sizeof(subjectLine)) {
340 			setError(MSG_SubjectLong, sizeof(subjectLine) - 1);
341 			goto freefail;
342 		}
343 		if (s > t)
344 			memcpy(subjectLine, t, s - t);
345 		subjectLine[s - t] = 0;
346 		if (subjectLine[0]) {
347 			char *subjiso = isoEncode(subjectLine,
348 						  subjectLine +
349 						  strlen(subjectLine));
350 			if (subjiso) {
351 				if (strlen(subjiso) >= sizeof(subjectLine)) {
352 					nzFree(subjiso);
353 					setError(MSG_SubjectLong,
354 						 sizeof(subjectLine) - 1);
355 					goto freefail;
356 				}
357 				strcpy(subjectLine, subjiso);
358 				nzFree(subjiso);
359 			}
360 		}
361 		debugPrint(6, "subject = %s", subjectLine);
362 /* Blank lines after subject are optional, and ignored. */
363 		for (t = buf + buflen; v < t; ++v)
364 			if (*v != '\r' && *v != '\n')
365 				break;
366 		buflen -= (v - buf);
367 		if (buflen)
368 			memmove(buf, v, buflen);
369 		buf[buflen] = 0;
370 
371 		if (doSignature) {	/* Append .signature file. */
372 /* Try account specific .signature file, then fall back to .signature */
373 			sprintf(sigFileEnd, "%d", mailAccount);
374 			c = fileTypeByName(sigFile, false);
375 			if (!c) {
376 				*sigFileEnd = 0;
377 				c = fileTypeByName(sigFile, false);
378 			}
379 			if (c != 0) {
380 				int fd, n;
381 				if (c != 'f') {
382 					setError(MSG_SigRegular);
383 					goto freefail;
384 				}
385 				n = fileSizeByName(sigFile);
386 				if (n > 0) {
387 					buf = reallocMem(buf, buflen + n + 1);
388 					fd = open(sigFile, O_RDONLY);
389 					if (fd < 0) {
390 						setError(MSG_SigAccess);
391 						goto freefail;
392 					}
393 					read(fd, buf + buflen, n);
394 					close(fd);
395 					buflen += n;
396 					buf[buflen] = 0;
397 				}
398 			}
399 		}		/* .signature */
400 	}
401 
402 	/* Infer content type from the filename */
403 	ct = 0;
404 	s = strrchr(file, '.');
405 	if (s && s[1]) {
406 		++s;
407 		if (stringEqualCI(s, "ps"))
408 			ct = "application/PostScript";
409 		if (stringEqualCI(s, "jpeg"))
410 			ct = "image/jpeg";
411 		if (stringEqualCI(s, "gif"))
412 			ct = "image/gif";
413 		if (stringEqualCI(s, "wav"))
414 			ct = "audio/basic";
415 		if (stringEqualCI(s, "mpeg"))
416 			ct = "video/mpeg";
417 		if (stringEqualCI(s, "rtf"))
418 			ct = "text/richtext";
419 		if (stringEqualCI(s, "htm") ||
420 		    stringEqualCI(s, "html") ||
421 		    stringEqualCI(s, "shtm") ||
422 		    stringEqualCI(s, "shtml") || stringEqualCI(s, "asp"))
423 			ct = "text/html";
424 	}
425 
426 /* Count the nonascii characters */
427 	nacount = nullcount = nlcount = 0;
428 	longline = false;
429 	s = 0;
430 	for (i = 0; i < buflen; ++i) {
431 		c = buf[i];
432 		if (c == '\0')
433 			++nullcount;
434 		if (c < 0)
435 			++nacount;
436 		if (c != '\n')
437 			continue;
438 		++nlcount;
439 		t = buf + i;
440 		if (s && t - s > 120)
441 			longline = true;
442 		if (!s && i > 120)
443 			longline = true;
444 		s = t;
445 	}
446 	t = buf + i;
447 	if (s && t - s > 120)
448 		longline = true;
449 	if (!s && i > 120)
450 		longline = true;
451 	debugPrint(6, "attaching %s length %d nonascii %d nulls %d longline %d",
452 		   file, buflen, nacount, nullcount, longline);
453 	nacount += nullcount;
454 
455 /* Set the type of attachment */
456 	if (buflen > 20 && nacount * 5 > buflen) {
457 		if (!ct)
458 			ct = "application/octet-stream";	/* default type for binary */
459 	}
460 	if (!ct)
461 		ct = "text/plain";
462 
463 /* Criteria for base64 encode.
464  * files uploaded from a web form need not be encoded, unless they contain
465  * nulls, which is a quirk of my slapped together software. */
466 
467 	if ((!webform && buflen > 20 && nacount * 5 > buflen) ||
468 	    (webform && nullcount)) {
469 		if (ismail) {
470 			setError(MSG_MailBinary, file);
471 			goto freefail;
472 		}
473 		s = base64Encode(buf, buflen, true);
474 		nzFree(buf);
475 		buf = s;
476 		ce = "base64";
477 		goto success;
478 	}
479 
480 	if (!webform) {
481 /* Switch to unix newlines - we'll switch back to dos later. */
482 		v = buf + buflen;
483 		for (s = t = buf; s < v; ++s) {
484 			c = *s;
485 			if (c == '\r' && s < v - 1 && s[1] == '\n')
486 				continue;
487 			*t++ = c;
488 		}
489 		buflen = t - buf;
490 
491 /* Do we need to use quoted-printable? */
492 /* Perhaps this hshould read (nacount > 0) */
493 		if (nacount * 20 > buflen || nullcount || longline) {
494 			char *newbuf;
495 			int l, colno = 0, space = 0;
496 
497 			newbuf = initString(&l);
498 			v = buf + buflen;
499 			for (s = buf; s < v; ++s) {
500 				c = *s;
501 /* do we have to =expand this character? */
502 				if ((c < '\n' && c != '\t') ||
503 				    c == '=' ||
504 				    c == '\xff' ||
505 				    ((c == ' ' || c == '\t') &&
506 				     s < v - 1 && s[1] == '\n')) {
507 					char expand[4];
508 					sprintf(expand, "=%02X", (uchar) c);
509 					stringAndString(&newbuf, &l, expand);
510 					colno += 3;
511 				} else {
512 					stringAndChar(&newbuf, &l, c);
513 					++colno;
514 				}
515 				if (c == '\n') {
516 					colno = space = 0;
517 					continue;
518 				}
519 				if (c == ' ' || c == '\t')
520 					space = l;
521 				if (colno < 72)
522 					continue;
523 				if (s == v - 1)
524 					continue;
525 /* If newline's coming up anyways, don't force another one. */
526 				if (s[1] == '\n')
527 					continue;
528 				i = l;
529 				if (!space || space == i) {
530 					stringAndString(&newbuf, &l, "=\n");
531 					colno = space = 0;
532 					continue;
533 				}
534 				colno = i - space;
535 				stringAndString(&newbuf, &l, "**");	/* make room */
536 				while (i > space) {
537 					newbuf[i + 1] = newbuf[i - 1];
538 					--i;
539 				}
540 				newbuf[space] = '=';
541 				newbuf[space + 1] = '\n';
542 				space = 0;
543 			}	/* loop over characters */
544 
545 			nzFree(buf);
546 			buf = newbuf;
547 			ce = "quoted-printable";
548 			goto success;
549 		}
550 	}
551 
552 	buf[buflen] = 0;
553 	ce = (nacount ? "8bit" : "7bit");
554 
555 success:
556 	debugPrint(6, "encoded %s %s length %d", ct, ce, strlen(buf));
557 	*enc_p = ce;
558 	*type_p = ct;
559 	*data_p = buf;
560 	return true;
561 
562 freefail:
563 	nzFree(buf);
564 	return false;
565 }				/* encodeAttachment */
566 
mailTimeString(void)567 static char *mailTimeString(void)
568 {
569 	static char buf[48];
570 	struct tm *cur_tm;
571 	time_t now;
572 	static const char months[] =
573 	    "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
574 	static const char wdays[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
575 
576 	time(&now);
577 	cur_tm = localtime(&now);
578 
579 /*********************************************************************
580 	strftime(buf, sizeof(buf), "%a, %d %b %Y %H:%M:%S %z", cur_tm);
581 That's what I use to do, but a user in France always got a date
582 with French month and weekday, even though I setlocale(LC_TIME, "C");
583 I changed C to en_US, still the French shines through.
584 I don't understand it, so I just work around it.
585 This is an internet standard, it's suppose to be English.
586 *********************************************************************/
587 
588 	sprintf(buf, "%s, %02d %s ",
589 		wdays + cur_tm->tm_wday * 4,
590 		cur_tm->tm_mday, months + cur_tm->tm_mon * 4);
591 // and strftime can do the rest.
592 	strftime(buf + 12, sizeof(buf) - 12, "%Y %H:%M:%S %z", cur_tm);
593 
594 	return buf;
595 }				/* mailTimeString */
596 
messageTimeID(void)597 static char *messageTimeID(void)
598 {
599 	static char buf[48];
600 	struct tm *cur_tm;
601 	time_t now;
602 	time(&now);
603 	cur_tm = localtime(&now);
604 	sprintf(buf, "%04d%02d%02d%02d%02d%02d",
605 		cur_tm->tm_year + 1900, cur_tm->tm_mon, cur_tm->tm_mday,
606 		cur_tm->tm_hour, cur_tm->tm_min, cur_tm->tm_sec);
607 	return buf;
608 }				/* messageTimeID */
609 
appendAttachment(const char * s,char ** out,int * l)610 static void appendAttachment(const char *s, char **out, int *l)
611 {
612 	const char *t;
613 	int n;
614 	while (*s) {		/* another line */
615 		t = strchr(s, '\n');
616 		if (!t)
617 			t = s + strlen(s);
618 		n = t - s;
619 		if (t[-1] == '\r')
620 			--n;
621 		if (n)
622 			memcpy(serverLine, s, n);
623 		serverLine[n] = 0;
624 		strcat(serverLine, eol);
625 		stringAndString(out, l, serverLine);
626 		if (*t)
627 			++t;
628 		s = t;
629 	}
630 /* Small bug here - an attachment that is not base64 encoded,
631  * and had no newline at the end, now has one. */
632 }				/* appendAttachment */
633 
makeBoundary(void)634 char *makeBoundary(void)
635 {
636 	static char boundary[24];
637 	sprintf(boundary, "nextpart-eb-%06d", rand() % 1000000);
638 	return boundary;
639 }				/* makeBoundary */
640 
641 struct smtp_upload {
642 	const char *data;
643 /* These really need to be size_t, not int!
644  * fixme when edbrowse uses size_t consistently */
645 	int length;
646 	int pos;
647 };
648 
smtp_upload_callback(char * buffer_for_curl,size_t size,size_t nmem,struct smtp_upload * upload)649 static size_t smtp_upload_callback(char *buffer_for_curl, size_t size,
650 				   size_t nmem, struct smtp_upload *upload)
651 {
652 	size_t out_buffer_size = size * nmem;
653 	size_t remaining = upload->length - upload->pos;
654 	size_t to_send, cur_pos;
655 
656 	if (upload->pos >= upload->length)
657 		return 0;
658 
659 	if (out_buffer_size < remaining)
660 		to_send = out_buffer_size;
661 	else
662 		to_send = remaining;
663 
664 	memcpy(buffer_for_curl, upload->data + upload->pos, to_send);
665 	cur_pos = upload->pos + to_send;
666 	upload->pos = cur_pos;
667 	return to_send;
668 }				/* smtp_upload_callback */
669 
buildSMTPURL(const struct MACCOUNT * account)670 static char *buildSMTPURL(const struct MACCOUNT *account)
671 {
672 	char *url = NULL;
673 	const char *scheme;
674 	const char *smlogin = strchr(account->login, '\\');
675 
676 	if (smlogin)
677 		++smlogin;
678 	else
679 		smlogin = "unknown";
680 
681 	if (account->outssl & 1)
682 		scheme = "smtps";
683 	else
684 		scheme = "smtp";
685 
686 	if (asprintf
687 	    (&url, "%s://%s:%d/%s", scheme, account->outurl, account->outport,
688 	     smlogin) == -1)
689 		i_printfExit(MSG_NoMem);
690 
691 	return url;
692 }				/* buildSMTPURL */
693 
buildRecipientSList(const char ** recipients)694 static struct curl_slist *buildRecipientSList(const char **recipients)
695 {
696 	struct curl_slist *recipient_slist = NULL;
697 	const char **r;
698 
699 	for (r = recipients; *r; r++) {
700 		recipient_slist = curl_slist_append(recipient_slist, *r);
701 		if (recipient_slist == NULL)
702 			i_printfExit(MSG_NoMem);
703 	}
704 
705 	return recipient_slist;
706 }				/* buildRecipientSList */
707 
newSendmailHandle(const struct MACCOUNT * account,const char * outurl,const char * reply,struct curl_slist * recipients)708 static CURL *newSendmailHandle(const struct MACCOUNT *account,
709 			       const char *outurl, const char *reply,
710 			       struct curl_slist *recipients)
711 {
712 	static struct i_get g;
713 	CURLcode res = CURLE_OK;
714 	CURL *handle = curl_easy_init();
715 	if (!handle) {
716 		setError(MSG_LibcurlNoInit);
717 		return NULL;
718 	}
719 
720 	curl_easy_setopt(handle, CURLOPT_CONNECTTIMEOUT, mailTimeout);
721 	res = setCurlURL(handle, outurl);
722 	if (res != CURLE_OK) {
723 		goto new_handle_cleanup;
724 	}
725 
726 	if (debugLevel >= 4)
727 		curl_easy_setopt(handle, CURLOPT_VERBOSE, 1);
728 	curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, ebcurl_debug_handler);
729 	curl_easy_setopt(handle, CURLOPT_DEBUGDATA, &g);
730 
731 	if (account->outssl == 2)
732 		curl_easy_setopt(handle, CURLOPT_USE_SSL, CURLUSESSL_ALL);
733 
734 	if (account->outssl) {
735 		res =
736 		    curl_easy_setopt(handle, CURLOPT_USERNAME, account->login);
737 		if (res != CURLE_OK) {
738 			goto new_handle_cleanup;
739 		}
740 
741 		res =
742 		    curl_easy_setopt(handle, CURLOPT_PASSWORD,
743 				     account->password);
744 		if (res != CURLE_OK) {
745 			goto new_handle_cleanup;
746 		}
747 	}
748 
749 	res = curl_easy_setopt(handle, CURLOPT_MAIL_FROM, reply);
750 	if (res != CURLE_OK) {
751 		goto new_handle_cleanup;
752 	}
753 
754 	res = curl_easy_setopt(handle, CURLOPT_MAIL_RCPT, recipients);
755 	if (res != CURLE_OK) {
756 		goto new_handle_cleanup;
757 	}
758 
759 new_handle_cleanup:
760 	if (res != CURLE_OK) {
761 		ebcurl_setError(res, outurl, 0, emptyString);
762 		curl_easy_cleanup(handle);
763 		handle = NULL;
764 	}
765 
766 	return handle;
767 }				/* newSendmailHandle */
768 
769 typedef struct tagsmtp_upload {
770 	const char *data;
771 	size_t length;
772 	size_t pos;
773 } smtp_upload;
774 
775 static bool
sendMailSMTP(const struct MACCOUNT * account,const char * reply,const char ** recipients,const char * message)776 sendMailSMTP(const struct MACCOUNT *account, const char *reply,
777 	     const char **recipients, const char *message)
778 {
779 	CURL *handle = 0;
780 	CURLcode res = CURLE_OK;
781 	bool smtp_success = false;
782 	char *smtp_url = buildSMTPURL(account);
783 	struct curl_slist *recipient_slist = buildRecipientSList(recipients);
784 	smtp_upload upload;
785 	upload.data = message;
786 	upload.length = strlen(message);
787 	upload.pos = 0;
788 	handle = newSendmailHandle(account, smtp_url, reply, recipient_slist);
789 
790 	if (!handle)
791 		goto smtp_cleanup;
792 
793 	curl_easy_setopt(handle, CURLOPT_READFUNCTION, smtp_upload_callback);
794 	curl_easy_setopt(handle, CURLOPT_READDATA, &upload);
795 	curl_easy_setopt(handle, CURLOPT_UPLOAD, 1L);
796 
797 	res = curl_easy_perform(handle);
798 	if (res == CURLE_OK)
799 		smtp_success = true;
800 
801 smtp_cleanup:
802 	if (res != CURLE_OK)
803 		ebcurl_setError(res, smtp_url, 0, emptyString);
804 	if (handle)
805 		curl_easy_cleanup(handle);
806 	curl_slist_free_all(recipient_slist);
807 	nzFree(smtp_url);
808 	return smtp_success;
809 }				/* sendMailSMTP */
810 
811 /* Send mail to the smtp server. */
812 bool
sendMail(int account,const char ** recipients,const char * body,int subjat,const char ** attachments,const char * refline,int nalt,bool dosig)813 sendMail(int account, const char **recipients, const char *body,
814 	 int subjat, const char **attachments, const char *refline,
815 	 int nalt, bool dosig)
816 {
817 	char *from, *fromiso, *reply;
818 	const struct MACCOUNT *a, *ao, *localMail;
819 	const char *s, *boundary;
820 	char reccc[MAXRECAT];
821 	char *t;
822 	int nat, cx, i, j;
823 	char *out = 0;
824 	bool sendmail_success = false;
825 	bool mustmime = false;
826 	bool firstrec;
827 	const char *ct, *ce;
828 	char *encoded = 0;
829 
830 	if (!validAccount(account))
831 		return false;
832 	mailAccount = account;
833 	localMail = accounts + localAccount - 1;
834 
835 	a = accounts + account - 1;
836 	from = a->from;
837 	reply = a->reply;
838 	ao = a->outssl ? a : localMail;
839 	doSignature = dosig;
840 
841 	nat = 0;		/* number of attachments */
842 	if (attachments) {
843 		while (attachments[nat])
844 			++nat;
845 	}
846 	if (nat)
847 		mustmime = true;
848 	if (nalt && nalt < nat) {
849 		setError(MSG_AttAlternate);
850 		return false;
851 	}
852 
853 	if (!loadAddressBook())
854 		return false;
855 
856 /* set copy flags */
857 	for (j = 0; (s = recipients[j]); ++j) {
858 		char cc = 0;
859 		if (*s == '^' || *s == '?')
860 			cc = *s++;
861 		if (j == MAXRECAT) {
862 			setError(MSG_RecipMany, MAXRECAT);
863 			return false;
864 		}
865 		recipients[j] = s;
866 		reccc[j] = cc;
867 	}
868 
869 /* Look up aliases in the address book */
870 	for (j = 0; (s = recipients[j]); ++j) {
871 		if (strchr(s, '@'))
872 			continue;
873 		t = 0;
874 		for (i = 0; i < nads; ++i) {
875 			const char *a = addressList[i].name;
876 			if (*a == '-' || *a == '!')
877 				++a;
878 			if (!stringEqual(s, a))
879 				continue;
880 			t = addressList[i].email;
881 			debugPrint(3, " %s becomes %s", s, t);
882 			break;
883 		}
884 		if (t) {
885 			recipients[j] = t;
886 			continue;
887 		}
888 		if (!addressFile) {
889 			setError(MSG_ABMissing);
890 			return false;
891 		}
892 		setError(MSG_ABNoAlias2, s);
893 		return false;
894 	}			/* recipients */
895 
896 	if (!j) {
897 		setError(MSG_RecipNone);
898 		return false;
899 	}
900 
901 /* verify attachments are readable */
902 	for (j = 0; (s = attachments[j]); ++j) {
903 		if (!ismc && (cx = stringIsNum(s)) >= 0) {
904 			if (!cxCompare(cx) || !cxActive(cx))
905 				return false;
906 			if (!sessionList[cx].lw->dol) {
907 				setError(MSG_AttSessionEmpty, cx);
908 				return false;
909 			}
910 		} else {
911 			char ftype = fileTypeByName(s, false);
912 			if (!ftype) {
913 				setError(MSG_AttAccess, s);
914 				return false;
915 			}
916 			if (ftype != 'f') {
917 				setError(MSG_AttRegular, s);
918 				return false;
919 			}
920 			if (!fileSizeByName(s)) {
921 				setError(MSG_AttEmpty2, s);
922 				return false;
923 			}
924 		}
925 	}			/* loop over attachments */
926 
927 	if (!encodeAttachment(body, subjat, false, &ct, &ce, &encoded))
928 		return false;
929 	if (ce[0] == 'q')
930 		mustmime = true;
931 
932 	boundary = makeBoundary();
933 
934 /* Build the outgoing mail, as one string. */
935 	out = initString(&j);
936 
937 	firstrec = true;
938 	for (i = 0; (s = recipients[i]); ++i) {
939 		if (reccc[i])
940 			continue;
941 		stringAndString(&out, &j, firstrec ? "To:" : ",\r\n  ");
942 		stringAndString(&out, &j, s);
943 		firstrec = false;
944 	}
945 	if (!firstrec)
946 		stringAndString(&out, &j, eol);
947 
948 	firstrec = true;
949 	for (i = 0; (s = recipients[i]); ++i) {
950 		if (reccc[i] != '^')
951 			continue;
952 		stringAndString(&out, &j, firstrec ? "CC:" : ",\r\n  ");
953 		stringAndString(&out, &j, s);
954 		firstrec = false;
955 	}
956 	if (!firstrec)
957 		stringAndString(&out, &j, eol);
958 
959 	firstrec = true;
960 	for (i = 0; (s = recipients[i]); ++i) {
961 		if (reccc[i] != '?')
962 			continue;
963 		stringAndString(&out, &j, firstrec ? "BCC:" : ",\r\n  ");
964 		stringAndString(&out, &j, s);
965 		firstrec = false;
966 	}
967 	if (!firstrec)
968 		stringAndString(&out, &j, eol);
969 
970 	fromiso = isoEncode(from, from + strlen(from));
971 	if (!fromiso)
972 		fromiso = from;
973 	sprintf(serverLine, "From: %s <%s>%s", fromiso, reply, eol);
974 	stringAndString(&out, &j, serverLine);
975 	sprintf(serverLine, "Reply-to: %s <%s>%s", fromiso, reply, eol);
976 	stringAndString(&out, &j, serverLine);
977 	if (fromiso != from)
978 		nzFree(fromiso);
979 	if (refline) {
980 		s = strchr(refline, '\n');
981 		if (!s)		/* should never happen */
982 			s = refline + strlen(refline);
983 		stringAndBytes(&out, &j, refline, s - refline);
984 		stringAndString(&out, &j, eol);
985 	}
986 	sprintf(serverLine, "User-Agent: %s%s", currentAgent, eol);
987 	stringAndString(&out, &j, serverLine);
988 	if (subjectLine[0]) {
989 		sprintf(serverLine, "Subject: %s%s", subjectLine, eol);
990 		stringAndString(&out, &j, serverLine);
991 	}
992 	sprintf(serverLine,
993 		"Date: %s%sMessage-ID: <%s.%s>%sMime-Version: 1.0%s",
994 		mailTimeString(), eol, messageTimeID(), reply, eol, eol);
995 	stringAndString(&out, &j, serverLine);
996 
997 	if (!mustmime) {
998 /* no mime components required, we can just send the mail. */
999 		sprintf(serverLine,
1000 			"Content-Type: %s%s%sContent-Transfer-Encoding: %s%s%s",
1001 			ct, charsetString(ct, ce), eol, ce, eol, eol);
1002 		stringAndString(&out, &j, serverLine);
1003 	} else {
1004 		sprintf(serverLine,
1005 			"Content-Type: multipart/%s; boundary=%s%sContent-Transfer-Encoding: 7bit%s%s",
1006 			nalt ? "alternative" : "mixed", boundary, eol, eol,
1007 			eol);
1008 		stringAndString(&out, &j, serverLine);
1009 		stringAndString(&out, &j,
1010 				"This message is in MIME format. Since your mail reader does not understand\r\n\
1011 this format, some or all of this message may not be legible.\r\n\r\n--");
1012 		stringAndString(&out, &j, boundary);
1013 		sprintf(serverLine,
1014 			"%sContent-Type: %s%s%sContent-Transfer-Encoding: %s%s%s",
1015 			eol, ct, charsetString(ct, ce), eol, ce, eol, eol);
1016 		stringAndString(&out, &j, serverLine);
1017 	}
1018 
1019 /* Now send the body, line by line. */
1020 	appendAttachment(encoded, &out, &j);
1021 	nzFree(encoded);
1022 	encoded = 0;
1023 
1024 	if (mustmime) {
1025 		for (i = 0; (s = attachments[i]); ++i) {
1026 			if (!encodeAttachment(s, 0, false, &ct, &ce, &encoded))
1027 				return false;
1028 			sprintf(serverLine, "%s--%s%sContent-Type: %s%s", eol,
1029 				boundary, eol, ct, charsetString(ct, ce));
1030 			stringAndString(&out, &j, serverLine);
1031 /* If the filename has a quote in it, forget it. */
1032 /* Also, suppress filename if this is an alternate presentation. */
1033 			if (!nalt && !strchr(s, '"')
1034 			    && (ismc || stringIsNum(s) < 0)) {
1035 				sprintf(serverLine, "; name=\"%s\"", s);
1036 				stringAndString(&out, &j, serverLine);
1037 			}
1038 			sprintf(serverLine,
1039 				"%sContent-Transfer-Encoding: %s%s%s", eol, ce,
1040 				eol, eol);
1041 			stringAndString(&out, &j, serverLine);
1042 			appendAttachment(encoded, &out, &j);
1043 			nzFree(encoded);
1044 			encoded = 0;
1045 		}		/* loop over attachments */
1046 
1047 /* The last boundary */
1048 		sprintf(serverLine, "%s--%s--%s", eol, boundary, eol);
1049 		stringAndString(&out, &j, serverLine);
1050 	}
1051 
1052 	/* mime format */
1053 
1054 	sendmail_success = sendMailSMTP(ao, reply, recipients, out);
1055 	nzFree(out);
1056 	return sendmail_success;
1057 }				/* sendMail */
1058 
validAccount(int n)1059 bool validAccount(int n)
1060 {
1061 	if (!maxAccount) {
1062 		setError(MSG_MailAccountsNone);
1063 		return false;
1064 	}
1065 	if (n <= 0 || n > maxAccount) {
1066 		setError(MSG_MailAccountBad, n, maxAccount);
1067 		return false;
1068 	}
1069 	return true;
1070 }				/* validAccount */
1071 
sendMailCurrent(int sm_account,bool dosig)1072 bool sendMailCurrent(int sm_account, bool dosig)
1073 {
1074 	const char *reclist[MAXRECAT + 1];
1075 	char *recmem;
1076 	const char *atlist[MAXRECAT + 1];
1077 	char *atmem;
1078 	char *s, *t;
1079 	char cxbuf[4];
1080 	int lr, la, ln;
1081 	char *refline = 0;
1082 	int nrec = 0, nat = 0, nalt = 0;
1083 	int account = localAccount;
1084 	int j;
1085 	bool rc = false;
1086 	bool subj = false;
1087 
1088 	if (cw->browseMode) {
1089 		setError(MSG_MailBrowse);
1090 		return false;
1091 	}
1092 	if (cw->sqlMode) {
1093 		setError(MSG_MailDB);
1094 		return false;
1095 	}
1096 	if (cw->dirMode) {
1097 		setError(MSG_MailDir);
1098 		return false;
1099 	}
1100 	if (cw->binMode) {
1101 		setError(MSG_MailBinary2);
1102 		return false;
1103 	}
1104 	if (!cw->dol) {
1105 		setError(MSG_MailEmpty);
1106 		return false;
1107 	}
1108 
1109 	if (!validAccount(account))
1110 		return false;
1111 
1112 	recmem = initString(&lr);
1113 	atmem = initString(&la);
1114 
1115 /* Gather recipients and attachments, until we reach subject: */
1116 	for (ln = 1; ln <= cw->dol; ++ln) {
1117 		char *line = (char *)fetchLine(ln, -1);
1118 
1119 		if (memEqualCI(line, "to:", 3) ||
1120 		    memEqualCI(line, "mailto:", 7) ||
1121 		    memEqualCI(line, "cc:", 3) ||
1122 		    memEqualCI(line, "bcc:", 4) ||
1123 		    memEqualCI(line, "reply to:", 9) ||
1124 		    memEqualCI(line, "reply to ", 9)) {
1125 			char cc = 0;
1126 			if (toupper(line[0]) == 'C')
1127 				cc = '^';
1128 			if (toupper(line[0]) == 'B')
1129 				cc = '?';
1130 			if (toupper(line[0]) == 'R')
1131 				line += 9;
1132 			else
1133 				line = strchr(line, ':') + 1;
1134 			while (*line == ' ' || *line == '\t')
1135 				++line;
1136 			if (*line == '\n') {
1137 				setError(MSG_RecipNone2, ln);
1138 				goto done;
1139 			}
1140 			if (nrec == MAXRECAT) {
1141 				setError(MSG_RecipMany, MAXRECAT);
1142 				goto done;
1143 			}
1144 			++nrec;
1145 			for (t = line; *t != '\n'; ++t) ;
1146 			if (cc) {
1147 				if (!lr) {
1148 					setError(MSG_MailFirstCC);
1149 					goto done;
1150 				}
1151 				stringAndChar(&recmem, &lr, cc);
1152 			}
1153 			stringAndBytes(&recmem, &lr, line, t + 1 - line);
1154 			continue;
1155 		}
1156 
1157 		if (memEqualCI(line, "attach:", 7)
1158 		    || memEqualCI(line, "alt:", 4)) {
1159 			if (toupper(line[1]) == 'T')
1160 				line += 7;
1161 			else
1162 				line += 4, ++nalt;
1163 			while (*line == ' ' || *line == '\t')
1164 				++line;
1165 			if (*line == '\n') {
1166 				setError(MSG_AttLineX, ln);
1167 				goto done;
1168 			}
1169 			if (nat == MAXRECAT) {
1170 				setError(MSG_RecipMany, MAXRECAT);
1171 				goto done;
1172 			}
1173 			++nat;
1174 			for (t = line; *t != '\n'; ++t) ;
1175 			stringAndBytes(&atmem, &la, line, t + 1 - line);
1176 			continue;
1177 		}
1178 
1179 		if (memEqualCI(line, "account:", 8)) {
1180 			line += 8;
1181 			while (*line == ' ' || *line == '\t')
1182 				++line;
1183 			if (!isdigitByte(*line) ||
1184 			    (account = strtol(line, &line, 10)) == 0 ||
1185 			    account > maxAccount || *line != '\n') {
1186 				setError(MSG_MailAccountBadLineX, ln);
1187 				goto done;
1188 			}
1189 			continue;
1190 		}
1191 
1192 		if (memEqualCI(line, "references:", 11)) {
1193 			if (!refline)
1194 				refline = line;
1195 			continue;
1196 		}
1197 
1198 		if (memEqualCI(line, "subject:", 8)) {
1199 			while (*line == ' ' || *line == '\t')
1200 				++line;
1201 			subj = true;
1202 		}
1203 
1204 		break;
1205 	}			/* loop over lines */
1206 
1207 	if (sm_account)
1208 		account = sm_account;
1209 	if (!subj) {
1210 		setError(((ln > cw->dol) + MSG_MailFirstLine), ln);
1211 		goto done;
1212 	}
1213 
1214 	if (nrec == 0) {
1215 		setError(MSG_RecipNone3);
1216 		goto done;
1217 	}
1218 
1219 	for (s = recmem, j = 0; *s; s = t + 1, ++j) {
1220 		t = strchr(s, '\n');
1221 		*t = 0;
1222 		reclist[j] = s;
1223 	}
1224 	reclist[j] = 0;
1225 	for (s = atmem, j = 0; *s; s = t + 1, ++j) {
1226 		t = strchr(s, '\n');
1227 		*t = 0;
1228 		atlist[j] = s;
1229 	}
1230 	atlist[j] = 0;
1231 
1232 	sprintf(cxbuf, "%d", context);
1233 	rc = sendMail(account, reclist, cxbuf, ln, atlist, refline, nalt,
1234 		      dosig);
1235 
1236 done:
1237 	nzFree(recmem);
1238 	nzFree(atmem);
1239 	if (!rc && intFlag)
1240 		setError(MSG_Interrupted);
1241 	if (rc)
1242 		i_puts(MSG_OK);
1243 	return rc;
1244 }				/* sendMailCurrent */
1245