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