2
* Send mail using the smtp protocol.
3
* Send the contents of a file, or the current edbrowse buffer.
4
* Copyright (c) Karl Dahlke, 2006
5
* This file is part of the edbrowse project, released under GPL.
12
char serverLine[MAXTTYLINE];
13
static char spareLine[MAXTTYLINE];
14
int mssock; /* server socket */
15
static bool doSignature;
16
static char subjectLine[81];
22
static int nads; /* number of addresses */
23
static time_t adbooktime;
25
/* read and/or refresh the address book */
29
char *buf, *bufend, *v, *last, *s, *t;
32
int j, buflen, ln = 1;
36
(mtime = fileTimeByName(addressFile)) == -1 || mtime <= adbooktime)
39
debugPrint(3, "loading address book");
43
if(!fileIntoMemory(addressFile, &buf, &buflen))
45
bufend = buf + buflen;
47
for(s = t = last = buf; s < bufend; ++s) {
54
if(c == ':') { /* delimiter */
56
setError("missing alias in address book line %d", ln);
61
while(t[-1] == ' ' || t[-1] == '\t')
68
c = '#'; /* extra fields are ignored */
79
setError("missing : in address book line %d", ln - 1);
84
while(isspaceByte(t[-1]))
87
v = strchr(last, ':');
90
("alias in address book line %d is too long, limit 15 characters",
97
("email in address book line %d is too long, limit 63 characters",
101
if(!strchr(v, '@')) {
103
(" email in address book line %d does not have an @",
107
if(strpbrk(v, " \t")) {
109
("cannot handle whitespace in email, address book line %d",
115
if(!isprintByte(*last)) {
117
("unprintable characters in your alias or email, address book line %d",
125
t = last; /* back it up */
131
if((c == ' ' || c == '\t') && (state == 0 || state == 2))
142
setError("last line of your address book is not terminated");
147
addressList = allocMem(nads * sizeof (struct ALIAS));
149
for(s = buf; *s; s = t + 1, ++j) {
151
memcpy(addressList[j].name, s, t - s);
152
addressList[j].name[t - s] = 0;
155
memcpy(addressList[j].email, s, t - s);
156
addressList[j].email[t - s] = 0;
159
/* aliases are present */
163
} /* loadAddressBook */
166
reverseAlias(const char *reply)
169
for(i = 0; i < nads; ++i)
170
if(stringEqual(reply, addressList[i].email)) {
171
const char *a = addressList[i].name;
176
return 0; /* not found */
180
/*********************************************************************
181
Put and get lines from the mail server.
182
Print the lines if the debug level calls for it.
183
*********************************************************************/
186
serverPutLine(const char *buf)
188
int n, len = strlen(buf);
190
if(debugLevel >= 4) {
192
for(n = 0; n < len; ++n) {
198
n = tcp_write(mssock, buf, len);
200
setError("cannot send data to the mail server");
204
} /* serverPutLine */
212
slen = strlen(spareLine);
213
strcpy(serverLine, spareLine);
214
s = strchr(serverLine, '\n');
217
tcp_read(mssock, serverLine + slen, sizeof (serverLine) - 1 - slen);
219
setError("cannot read data from the mail server");
223
serverLine[slen] = 0;
225
s = strchr(serverLine, '\n');
227
setError("line from the mail server is too long, or unterminated");
230
strcpy(spareLine, s + 1);
232
if(s > serverLine && s[-1] == '\r')
234
debugPrint(4, "< %s", serverLine);
236
} /* serverGetLine */
241
serverPutLine("quit\r\n");
247
/* Connect to the mail server */
249
mailConnect(const char *host, int port)
252
ip = tcp_name_ip(host);
254
setError(intFlag ? opint : "cannot locate the mail server %s", host);
257
debugPrint(4, "%s -> %s", host, tcp_ip_dots(ip));
258
mssock = tcp_connect(ip, port, mailTimeout);
260
setError(intFlag ? opint : "cannot connect to the mail server");
263
debugPrint(4, "connected to port %d", port);
268
static char base64_chars[] =
269
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
271
base64Encode(const char *inbuf, int inlen, bool lines)
274
uchar *in = (uchar *) inbuf;
276
int outlen = ((inlen / 3) + 1) * 4;
277
++outlen; /* zero on the end */
279
outlen += (inlen / 54) + 1;
280
outstr = out = allocMem(outlen);
283
*out++ = base64_chars[(int)(*in >> 2)];
284
*out++ = base64_chars[(int)((*in << 4 | *(in + 1) >> 4) & 63)];
285
*out++ = base64_chars[(int)((*(in + 1) << 2 | *(in + 2) >> 6) & 63)];
286
*out++ = base64_chars[(int)(*(in + 2) & 63)];
298
*out++ = base64_chars[(int)(*in >> 2)];
299
*out++ = base64_chars[(int)(*in << 4 & 63)];
305
*out++ = base64_chars[(int)(*in >> 2)];
306
*out++ = base64_chars[(int)((*in << 4 | *(in + 1) >> 4) & 63)];
307
*out++ = base64_chars[(int)((*(in + 1) << 2) & 63)];
311
/* finish the last line */
318
/* Read a file into memory, mime encode it,
319
* and return the type of encoding and the encoded data.
320
* Last three parameters are result parameters.
321
* If ismail is nonzero, the file is the mail, not an attachment.
322
* In fact ismail indicates the line that holds the subject.
323
* If ismail is negative, then -ismail indicates the subject line,
324
* and the string file is not the filename, but rather, the mail to send. */
326
encodeAttachment(const char *file, int ismail,
327
const char **type_p, const char **enc_p, char **data_p)
333
char *ct, *ce; /* content type, content encoding */
335
int nacount, nullcount, nlcount;
338
buf = cloneString(file);
339
buflen = strlen(buf);
344
if(!ismc && (cx = stringIsNum(file)) >= 0) {
345
static char newfilename[16];
346
if(!unfoldBuffer(cx, false, &buf, &buflen))
349
setError("buffer %d is empty", cx);
352
sprintf(newfilename, "<buffer %d>", cx);
354
if(sessionList[cx].lw->fileName)
355
file = sessionList[cx].lw->fileName;
357
if(!fileIntoMemory(file, &buf, &buflen))
360
setError("file %s is empty", file);
364
} /* ismail negative or normal */
367
/* Put newline at the end. Yes, the buffer is allocated
368
* with space for newline and null. */
369
if(buf[buflen - 1] != '\n')
370
buf[buflen++] = '\n';
371
/* check for subject: line */
379
while(*s == ' ' || *s == '\t')
381
if(!memEqualCI(s, "subject:", 8)) {
382
setError("your email should begin with subject:");
386
while(*s == ' ' || *s == '\t')
392
while(s > t && isspaceByte(s[-1]))
395
setError("empty subject line");
398
if(s - t >= sizeof (subjectLine)) {
399
setError("subject line too long, limit %d characters",
400
sizeof (subjectLine) - 1);
403
memcpy(subjectLine, t, s - t);
404
subjectLine[s - t] = 0;
405
t = subjectLine + (s - t);
406
for(s = subjectLine; s < t; ++s) {
408
if(!isprintByte(c) && c != ' ') {
410
("invalid characters in the subject line, please use only spaces and printable ascii text");
414
debugPrint(6, "subject = %s", subjectLine);
415
/* Blank lines after subject are optional, and ignored. */
416
for(t = buf + buflen; v < t; ++v)
417
if(*v != '\r' && *v != '\n')
421
memcpy(buf, v, buflen);
424
if(doSignature) { /* Append .signature file. */
425
c = fileTypeByName(sigFile, false);
429
setError(".signature is not a regular file");
432
n = fileSizeByName(sigFile);
434
buf = reallocMem(buf, buflen + n + 1);
435
fd = open(sigFile, O_RDONLY);
437
setError("cannot access .signature file");
440
read(fd, buf + buflen, n);
449
/* primary email message */
450
/* Infer content type from the filename */
452
s = strrchr(file, '.');
455
if(stringEqualCI(s, "ps"))
456
ct = "application/PostScript";
457
if(stringEqualCI(s, "jpeg"))
459
if(stringEqualCI(s, "gif"))
461
if(stringEqualCI(s, "wav"))
463
if(stringEqualCI(s, "mpeg"))
465
if(stringEqualCI(s, "rtf"))
466
ct = "text/richtext";
467
if(stringEqualCI(s, "htm") ||
468
stringEqualCI(s, "html") ||
469
stringEqualCI(s, "shtm") ||
470
stringEqualCI(s, "shtml") || stringEqualCI(s, "asp"))
474
/* Count the nonascii characters */
475
nacount = nullcount = nlcount = 0;
478
for(i = 0; i < buflen; ++i) {
492
debugPrint(6, "attaching %s length %d nonascii %d nulls %d longline %d",
493
file, buflen, nacount, nullcount, longline);
494
nacount += nullcount;
496
if(buflen > 20 && nacount * 5 > buflen) { /* binary file */
499
("cannot mail the binary file %s, perhaps this should be an attachment?",
504
s = base64Encode(buf, buflen, true);
508
ct = "application/octet-stream"; /* default type */
516
/* Switch to unix newlines - we'll switch back to dos later. */
518
for(s = t = buf; s < v; ++s) {
520
if(c == '\r' && s < v - 1 && s[1] == '\n')
526
/* Do we need to use quoted-printable? */
527
/* Perhaps this hshould read (nacount > 0) */
528
if(nacount * 20 > buflen || nullcount || longline) {
530
int l, colno = 0, space = 0;
532
newbuf = initString(&l);
534
for(s = buf; s < v; ++s) {
536
/* do we have to =expand this character? */
537
if(c < '\n' && c != '\t' ||
540
(c == ' ' || c == '\t') && s < v - 1 && s[1] == '\n') {
542
sprintf(expand, "=%02X", (uchar) c);
543
stringAndString(&newbuf, &l, expand);
546
stringAndChar(&newbuf, &l, c);
553
if(c == ' ' || c == '\t')
559
/* If newline's coming up anyways, don't force another one. */
563
if(!space || space == i) {
564
stringAndString(&newbuf, &l, "=\n");
569
stringAndString(&newbuf, &l, "**"); /* make room */
571
newbuf[i + 1] = newbuf[i - 1];
575
newbuf[space + 1] = '\n';
577
} /* loop over characters */
581
ce = "quoted-printable";
584
/* quoted printable */
586
ce = (nacount ? "8bit" : "7bit");
589
debugPrint(6, "encoded %s %s length %d", ct, ce, strlen(buf));
598
} /* encodeAttachment */
604
static const char wday[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
605
static const char month[] =
606
"Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec";
610
cur_tm = localtime(&now);
611
sprintf(buf, "%s, %02d %s %d %02d:%02d:%02d",
612
wday + cur_tm->tm_wday * 4,
614
month + cur_tm->tm_mon * 4,
615
cur_tm->tm_year + 1900, cur_tm->tm_hour, cur_tm->tm_min, cur_tm->tm_sec);
617
} /* mailTimeString */
626
cur_tm = localtime(&now);
627
sprintf(buf, "%04d%02d%02d%02d%02d%02d",
628
cur_tm->tm_year + 1900, cur_tm->tm_mon, cur_tm->tm_mday,
629
cur_tm->tm_hour, cur_tm->tm_min, cur_tm->tm_sec);
631
} /* messageTimeID */
634
appendAttachment(const char *s, char **out, int *l)
638
while(*s) { /* another line */
646
memcpy(serverLine, s, n);
648
if(n == 1 && serverLine[0] == '.') /* can't allow this */
649
strcpy(serverLine, " .");
650
strcat(serverLine, eol);
651
stringAndString(out, l, serverLine);
656
/* Small bug here - an attachment that is not base64 encoded,
657
* and had no newline at the end, now has one. */
658
} /* appendAttachment */
663
static char boundary[60];
664
sprintf(boundary, "nextpart-eb-%06d", rand() % 1000000);
668
/* Send mail to the smtp server. */
670
sendMail(int account, const char **recipients, const char *body,
671
int subjat, const char **attachments, int nalt, bool dosig)
673
const char *from, *reply, *login, *smlogin, *pass;
674
const struct MACCOUNT *a;
675
const char *s, *boundary;
679
bool mustmime = false;
682
struct MACCOUNT *localMail;
684
if(!validAccount(account))
686
localMail = accounts + localAccount - 1;
688
a = accounts + account - 1;
691
login = localMail->login;
692
smlogin = strchr(login, '\\');
697
pass = localMail->password;
700
nat = 0; /* number of attachments */
702
while(attachments[nat])
707
if(nalt && nalt < nat) {
709
("either none or all of the attachments must be declared \"alternative\"");
713
if(!loadAddressBook())
716
/* Look up aliases in the address book */
717
for(j = 0; s = recipients[j]; ++j) {
721
for(i = 0; i < nads; ++i) {
722
const char *a = addressList[i].name;
723
if(*a == '-' || *a == '!')
725
if(!stringEqual(s, a))
727
t = addressList[i].email;
728
debugPrint(3, " %s becomes %s", s, t);
737
("No address book specified, please check your .ebrc config file");
740
setError("alias %s not found in your address book", s);
744
setError("no recipients specified");
748
/* verify attachments are readable */
749
for(j = 0; s = attachments[j]; ++j) {
750
if(!ismc && (cx = stringIsNum(s)) >= 0) {
751
if(!cxCompare(cx) || !cxActive(cx))
753
if(!sessionList[cx].lw->dol) {
754
setError("session %d is empty, cannot atach", cx);
758
char ftype = fileTypeByName(s, false);
760
setError("cannot access attachment %s", s);
764
setError("file %s is not a regular file, cannot attach", s);
767
if(!fileSizeByName(s)) {
768
setError("file %s is empty, cannot attach", s);
772
} /* loop over attachments */
774
if(!encodeAttachment(body, subjat, &ct, &ce, &encoded))
779
boundary = makeBoundary();
781
if(!mailConnect(localMail->outurl, localMail->outport)) {
787
while(memEqualCI(serverLine, "220-", 4)) {
791
if(!memEqualCI(serverLine, "220 ", 4)) {
793
("unexpected prompt \"%s\" at the start of the sendmail session",
798
sprintf(serverLine, "helo %s%s", smlogin, eol);
799
if(!serverPutLine(serverLine))
803
if(!memEqualCI(serverLine, "250 ", 4)) {
804
setError("mail server doesn't recognize %s", login);
808
sprintf(serverLine, "mail from: <%s>%s", reply, eol);
809
if(!serverPutLine(serverLine))
813
if(!memEqualCI(serverLine, "250 ", 4)) {
814
setError("mail server rejected %s", reply);
818
for(j = 0; s = recipients[j]; ++j) {
819
sprintf(serverLine, "rcpt to: <%s>%s", s, eol);
820
if(!serverPutLine(serverLine))
824
if(!memEqualCI(serverLine, "250 ", 4)) {
825
setError("mail server rejected %s", s);
830
if(!serverPutLine("data\r\n"))
834
if(!memEqualCI(serverLine, "354 ", 4)) {
835
setError("mail server is not ready to receive data, %s", serverLine);
839
/* Build the outgoing mail, and send it in one go, as one string. */
840
out = initString(&j);
841
for(i = 0; s = recipients[i]; ++i) {
842
stringAndString(&out, &j, i ? " " : "To: ");
843
stringAndString(&out, &j, s);
844
if(recipients[i + 1])
845
stringAndChar(&out, &j, ',');
846
stringAndString(&out, &j, eol);
848
sprintf(serverLine, "From: %s <%s>%s", from, reply, eol);
849
stringAndString(&out, &j, serverLine);
850
sprintf(serverLine, "Reply-to: %s <%s>%s", from, reply, eol);
851
stringAndString(&out, &j, serverLine);
852
sprintf(serverLine, "Subject: %s%s", subjectLine, eol);
853
stringAndString(&out, &j, serverLine);
854
sprintf(serverLine, "Date: %s%sMessage-ID: <%s.%s>%sMime-Version: 1.0%s",
855
mailTimeString(), eol, messageTimeID(), reply, eol, eol);
856
stringAndString(&out, &j, serverLine);
859
/* no mime components required, we can just send the mail. */
861
"Content-type: %s%sContent-Transfer-Encoding: %s%s%s", ct, eol, ce,
863
stringAndString(&out, &j, serverLine);
866
"Content-Type: multipart/%s; boundary=%s%sContent-Transfer-Encoding: 7bit%s%s",
867
nalt ? "alternative" : "mixed", boundary, eol, eol, eol);
868
stringAndString(&out, &j, serverLine);
869
stringAndString(&out, &j,
870
"This message is in MIME format. Since your mail reader does not understand\r\n\
871
this format, some or all of this message may not be legible.\r\n\r\n--");
872
stringAndString(&out, &j, boundary);
874
"%sContent-type: %s%sContent-Transfer-Encoding: %s%s%s", eol, ct,
876
stringAndString(&out, &j, serverLine);
879
/* Now send the body, line by line. */
880
appendAttachment(encoded, &out, &j);
885
for(i = 0; s = attachments[i]; ++i) {
886
if(!encodeAttachment(s, 0, &ct, &ce, &encoded))
888
sprintf(serverLine, "%s--%s%sContent-Type: %s", eol, boundary, eol,
890
stringAndString(&out, &j, serverLine);
891
/* If the filename has a quote in it, forget it. */
892
/* Also, suppress filename if this is an alternate presentation. */
893
if(!nalt && !strchr(s, '"') && (ismc || stringIsNum(s) < 0)) {
894
sprintf(serverLine, "; name=\"%s\"", s);
895
stringAndString(&out, &j, serverLine);
897
sprintf(serverLine, "%sContent-Transfer-Encoding: %s%s%s", eol, ce,
899
stringAndString(&out, &j, serverLine);
900
appendAttachment(encoded, &out, &j);
903
} /* loop over attachments */
905
/* The last boundary */
906
sprintf(serverLine, "%s--%s--%s", eol, boundary, eol);
907
stringAndString(&out, &j, serverLine);
911
/* A dot alone ends the transmission */
912
stringAndString(&out, &j, ".\r\n");
913
if(!serverPutLine(out))
920
if(!memEqualCI(serverLine, "250 ", 4) &&
921
/* do these next two lines make any sense? */
922
!strstrCI(serverLine, "message accepted") &&
923
!strstrCI(serverLine, "message received")) {
924
setError("could not send mail message, %s", serverLine);
942
setError("no mail accounts specified, plese check your config file");
945
if(n <= 0 || n > maxAccount) {
946
setError("invalid account number %d, please use 1 through %d", n,
953
#define MAXRECAT 100 /* max number of recipients or attachments */
955
sendMailCurrent(int sm_account, bool dosig)
957
const char *reclist[MAXRECAT + 1];
959
const char *atlist[MAXRECAT + 1];
964
int nrec = 0, nat = 0, nalt = 0;
965
int account = localAccount;
971
setError("cannot send mail while in browse mode");
975
setError("cannot send mail from directory mode");
979
setError("cannot mail binary data, should this be an attachment?");
983
setError("cannot mail an empty file");
987
if(!validAccount(account))
990
recmem = initString(&lr);
991
atmem = initString(&la);
993
/* Gather recipients and attachments, until we reach subject: */
994
for(ln = 1; ln <= cw->dol; ++ln) {
995
char *line = (char *)fetchLine(ln, -1);
996
if(memEqualCI(line, "to:", 3) ||
997
memEqualCI(line, "mailto:", 7) ||
998
memEqualCI(line, "reply to:", 9) ||
999
memEqualCI(line, "reply to ", 9)) {
1000
if(toupper(line[0]) == 'R')
1002
else if(toupper(line[0]) == 'M')
1006
while(*line == ' ' || *line == '\t')
1009
setError("no recipient at line %d", ln);
1012
if(nrec == MAXRECAT) {
1013
setError("too many recipients, limit %d", MAXRECAT);
1017
for(t = line; *t != '\n'; ++t) ;
1018
stringAndBytes(&recmem, &lr, line, t + 1 - line);
1021
if(memEqualCI(line, "attach:", 7) || memEqualCI(line, "alt:", 4)) {
1022
if(toupper(line[1]) == 'T')
1026
while(*line == ' ' || *line == '\t')
1029
setError("no attachment at line %d", ln);
1032
if(nat == MAXRECAT) {
1033
setError("too many recipients, limit %d", MAXRECAT);
1037
for(t = line; *t != '\n'; ++t) ;
1038
stringAndBytes(&atmem, &la, line, t + 1 - line);
1041
if(memEqualCI(line, "account:", 8)) {
1043
while(*line == ' ' || *line == '\t')
1045
if(!isdigitByte(*line) ||
1046
(account = strtol(line, &line, 10)) == 0 ||
1047
account > maxAccount || *line != '\n') {
1048
setError("invalid account number at line %d", ln);
1053
if(memEqualCI(line, "subject:", 8)) {
1054
while(*line == ' ' || *line == '\t')
1057
setError("empty subject");
1063
} /* loop over lines */
1066
account = sm_account;
1068
setError(ln > cw->dol ?
1069
"there is no subject line" :
1070
"line %d should begin with to: attach: alt: account: or subject:",
1077
("no recipients specified, place to: emailaddress at the top of youre file");
1081
for(s = recmem, j = 0; *s; s = t + 1, ++j) {
1082
t = strchr(s, '\n');
1087
for(s = atmem, j = 0; *s; s = t + 1, ++j) {
1088
t = strchr(s, '\n');
1094
sprintf(cxbuf, "%d", context);
1095
rc = sendMail(account, reclist, cxbuf, ln, atlist, nalt, dosig);
1105
} /* sendMailCurrent */