Mercurial > email
diff mail.c @ 0:33bf7ee17644
Initial import from some old code
author | Adam Chlipala <adam@chlipala.net> |
---|---|
date | Sat, 03 Mar 2012 14:59:04 -0500 |
parents | |
children | bf58106560ba |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mail.c Sat Mar 03 14:59:04 2012 -0500 @@ -0,0 +1,508 @@ +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <urweb.h> + +struct headers { + uw_Basis_string from, to, cc, bcc, subject; +}; + +typedef struct headers *uw_Mail_headers; + +uw_Mail_headers uw_Mail_empty = NULL; + +static void header(uw_context ctx, uw_Basis_string s) { + if (strlen(s) > 100) + uw_error(ctx, FATAL, "Header value too long"); + + for (; *s; ++s) + if (*s == '\r' || *s == '\n') + uw_error(ctx, FATAL, "Header value contains newline"); +} + +static void address(uw_context ctx, uw_Basis_string s) { + header(ctx, s); + + if (strchr(s, ',')) + uw_error(ctx, FATAL, "E-mail address contains comma"); +} + +uw_Mail_headers uw_Mail_from(uw_context ctx, uw_Basis_string s, uw_Mail_headers h) { + // char **allowed = uw_get_global(ctx, "mail_from"); + // Might add this policy checking (or some expanded version of it) back later. + uw_Mail_headers h2 = uw_malloc(ctx, sizeof(struct headers)); + + if (h) + *h2 = *h; + else + memset(h2, 0, sizeof(*h2)); + + if (h2->from) + uw_error(ctx, FATAL, "Duplicate From header"); + + /* + if (!allowed) + uw_error(ctx, FATAL, "No From address whitelist has been set. Perhaps you are not authorized to send e-mail."); + + if (!(allowed[0] && !strcmp(allowed[0], "*"))) { + for (; *allowed; ++allowed) + if (!strcmp(*allowed, s)) + goto ok; + + uw_error(ctx, FATAL, "From address is not in whitelist"); + } + + ok: + */ + address(ctx, s); + h2->from = s; + + return h2; +} + +uw_Mail_headers uw_Mail_to(uw_context ctx, uw_Basis_string s, uw_Mail_headers h) { + uw_Mail_headers h2 = uw_malloc(ctx, sizeof(struct headers)); + if (h) + *h2 = *h; + else + memset(h2, 0, sizeof(*h2)); + + address(ctx, s); + if (h2->to) { + uw_Basis_string all = uw_malloc(ctx, strlen(h2->to) + 1 + strlen(s)); + sprintf(all, "%s,%s", h2->to, s); + h2->to = all; + } else + h2->to = s; + + return h2; +} + +uw_Mail_headers uw_Mail_cc(uw_context ctx, uw_Basis_string s, uw_Mail_headers h) { + uw_Mail_headers h2 = uw_malloc(ctx, sizeof(struct headers)); + if (h) + *h2 = *h; + else + memset(h2, 0, sizeof(*h2)); + + address(ctx, s); + if (h2->cc) { + uw_Basis_string all = uw_malloc(ctx, strlen(h2->cc) + 1 + strlen(s)); + sprintf(all, "%s,%s", h2->cc, s); + h2->cc = all; + } else + h2->cc = s; + + return h2; +} + +uw_Mail_headers uw_Mail_bcc(uw_context ctx, uw_Basis_string s, uw_Mail_headers h) { + uw_Mail_headers h2 = uw_malloc(ctx, sizeof(struct headers)); + if (h) + *h2 = *h; + else + memset(h2, 0, sizeof(*h2)); + + address(ctx, s); + if (h2->bcc) { + uw_Basis_string all = uw_malloc(ctx, strlen(h2->bcc) + 1 + strlen(s)); + sprintf(all, "%s,%s", h2->bcc, s); + h2->bcc = all; + } else + h2->bcc = s; + + return h2; +} + +uw_Mail_headers uw_Mail_subject(uw_context ctx, uw_Basis_string s, uw_Mail_headers h) { + uw_Mail_headers h2 = uw_malloc(ctx, sizeof(struct headers)); + + if (h) + *h2 = *h; + else + memset(h2, 0, sizeof(*h2)); + + if (h2->subject) + uw_error(ctx, FATAL, "Duplicate Subject header"); + + header(ctx, s); + h2->subject = s; + + return h2; +} + +typedef struct { + uw_context ctx; + uw_Mail_headers h; + uw_Basis_string body, xbody; +} job; + +#define BUFLEN 1024 + +static int smtp_read(uw_context ctx, int sock, char *buf, ssize_t *pos) { + char *s; + + while (1) { + ssize_t recvd; + + buf[*pos] = 0; + + if ((s = strchr(buf, '\n'))) { + int n; + + *s = 0; + + if (sscanf(buf, "%d ", &n) != 1) { + close(sock); + uw_set_error_message(ctx, "Mail server response does not begin with a code."); + return 0; + } + + *pos -= s - buf + 1; + memmove(buf, s+1, *pos); + + return n; + } + + recvd = recv(sock, buf + *pos, BUFLEN - *pos - 1, 0); + + if (recvd == 0) { + close(sock); + uw_set_error_message(ctx, "Mail server response ends unexpectedly."); + return 0; + } else if (recvd < 0) { + close(sock); + uw_set_error_message(ctx, "Error reading mail server response."); + return 0; + } + + *pos += recvd; + } +} + +static int really_string(int sock, const char *s) { + return uw_really_send(sock, s, strlen(s)); +} + +static int sendAddrs(const char *kind, uw_context ctx, int sock, char *s, char *buf, ssize_t *pos) { + char *p; + char out[BUFLEN]; + + if (!s) + return 0; + + for (p = strchr(s, ','); p; p = strchr(p+1, ',')) { + *p = 0; + + snprintf(out, sizeof(out), "RCPT TO:%s\n", s); + out[sizeof(out)-1] = 0; + *p = ','; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(ctx, "Error sending RCPT TO for %s", kind); + return 1; + } + + if (smtp_read(ctx, sock, buf, pos) != 250) { + close(sock); + uw_set_error_message(ctx, "Mail server doesn't respond to %s RCPT TO with code 250.", kind); + return 1; + } + } + + if (*s) { + snprintf(out, sizeof(out), "RCPT TO:%s\n", s); + out[sizeof(out)-1] = 0; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(ctx, "Error sending RCPT TO for %s", kind); + return 1; + } + + if (smtp_read(ctx, sock, buf, pos) != 250) { + close(sock); + uw_set_error_message(ctx, "Mail server doesn't respond to %s RCPT TO with code 250.", kind); + return 1; + } + } + + return 0; +} + +static void commit(void *data) { + job *j = data; + int sock; + struct sockaddr_in my_addr; + char buf[BUFLEN], out[BUFLEN]; + ssize_t pos = 0; + char *s; + + if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + uw_set_error_message(j->ctx, "Can't create socket for mail server connection"); + return; + } + + my_addr.sin_family = AF_INET; + my_addr.sin_port = htons(25); + my_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + memset(my_addr.sin_zero, 0, sizeof my_addr.sin_zero); + + if (connect(sock, (struct sockaddr *)&my_addr, sizeof my_addr) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error connecting to mail server"); + return; + } + + if (smtp_read(j->ctx, sock, buf, &pos) != 220) { + close(sock); + uw_set_error_message(j->ctx, "Mail server doesn't greet with code 220."); + return; + } + + if (really_string(sock, "HELO localhost\n") < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending HELO"); + return; + } + + if (smtp_read(j->ctx, sock, buf, &pos) != 250) { + close(sock); + uw_set_error_message(j->ctx, "Mail server doesn't respond to HELO with code 250."); + return; + } + + snprintf(out, sizeof(out), "MAIL FROM:%s\n", j->h->from); + out[sizeof(out)-1] = 0; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending MAIL FROM"); + return; + } + + if (smtp_read(j->ctx, sock, buf, &pos) != 250) { + close(sock); + uw_set_error_message(j->ctx, "Mail server doesn't respond to MAIL FROM with code 250."); + return; + } + + if (sendAddrs("To", j->ctx, sock, j->h->to, buf, &pos)) return; + if (sendAddrs("Cc", j->ctx, sock, j->h->cc, buf, &pos)) return; + if (sendAddrs("Bcc", j->ctx, sock, j->h->bcc, buf, &pos)) return; + + if (really_string(sock, "DATA\n") < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending DATA"); + return; + } + + if (smtp_read(j->ctx, sock, buf, &pos) != 354) { + close(sock); + uw_set_error_message(j->ctx, "Mail server doesn't respond to DATA with code 354."); + return; + } + + snprintf(out, sizeof(out), "From: %s\r\n", j->h->from); + out[sizeof(out)-1] = 0; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending From"); + return; + } + + if (j->h->subject) { + snprintf(out, sizeof(out), "Subject: %s\r\n", j->h->subject); + out[sizeof(out)-1] = 0; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending Subject"); + return; + } + } + + if (j->h->to) { + snprintf(out, sizeof(out), "To: %s\r\n", j->h->to); + out[sizeof(out)-1] = 0; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending To"); + return; + } + } + + if (j->h->cc) { + snprintf(out, sizeof(out), "Cc: %s\r\n", j->h->cc); + out[sizeof(out)-1] = 0; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending Cc"); + return; + } + } + + if (j->h->bcc) { + snprintf(out, sizeof(out), "Bcc: %s\r\n", j->h->bcc); + out[sizeof(out)-1] = 0; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending Bcc"); + return; + } + } + + if ((s = uw_get_global(j->ctx, "extra_mail_headers"))) { + if (really_string(sock, s) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending extra headers"); + return; + } + } + + if (j->xbody) { + char separator[11]; + separator[sizeof(separator)-1] = 0; + + do { + int i; + + for (i = 0; i < sizeof(separator)-1; ++i) + separator[i] = 'A' + (rand() % 26); + } while (strstr(j->body, separator) || strstr(j->xbody, separator)); + + snprintf(out, sizeof(out), "MIME-Version: 1.0\r\n" + "Content-Type: multipart/alternative; boundary=\"%s\"\r\n" + "\r\n" + "--%s\r\n" + "Content-Type: text/plain\r\n" + "\r\n", + separator, separator); + out[sizeof(out)-1] = 0; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending multipart beginning"); + return; + } + + if (really_string(sock, j->body) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending message text body"); + return; + } + + snprintf(out, sizeof(out), "\r\n" + "--%s\r\n" + "Content-Type: text/html\r\n" + "\r\n", + separator); + out[sizeof(out)-1] = 0; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending multipart middle"); + return; + } + + if (really_string(sock, j->xbody) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending message HTML body"); + return; + } + + snprintf(out, sizeof(out), "\r\n" + "--%s--", + separator); + out[sizeof(out)-1] = 0; + + if (really_string(sock, out) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending multipart end"); + return; + } + } else { + if (really_string(sock, "Content-Type: text/plain\r\n\r\n") < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending text Content-Type"); + return; + } + + if (really_string(sock, j->body) < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending message body"); + return; + } + } + + if (really_string(sock, "\r\n.\r\n") < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending message terminator"); + return; + } + + if (smtp_read(j->ctx, sock, buf, &pos) != 250) { + close(sock); + uw_set_error_message(j->ctx, "Mail server doesn't respond to end of message with code 250."); + return; + } + + if (really_string(sock, "QUIT\n") < 0) { + close(sock); + uw_set_error_message(j->ctx, "Error sending QUIT"); + return; + } + + if (smtp_read(j->ctx, sock, buf, &pos) != 221) { + close(sock); + uw_set_error_message(j->ctx, "Mail server doesn't respond to QUIT with code 221."); + return; + } + + close(sock); +} + +uw_unit uw_Mail_send(uw_context ctx, uw_Mail_headers h, uw_Basis_string body, uw_Basis_string xbody) { + job *j; + char *s; + + if (!h || !h->from) + uw_error(ctx, FATAL, "No From address set for e-mail message"); + + if (!h->to && !h->cc && !h->bcc) + uw_error(ctx, FATAL, "No recipients specified for e-mail message"); + + for (s = strchr(body, '.'); s; s = strchr(s+1, '.')) + if ((s[1] == '\n' || s[1] == '\r') + && (s <= body || s[-1] == '\n' || s[-1] == '\r')) + uw_error(ctx, FATAL, "Message body contains a line with just a period"); + + if (xbody) { + for (s = strchr(xbody, '.'); s; s = strchr(s+1, '.')) + if ((s[1] == '\n' || s[1] == '\r') + && (s <= xbody || s[-1] == '\n' || s[-1] == '\r')) + uw_error(ctx, FATAL, "HTML message body contains a line with just a period"); + } + + j = uw_malloc(ctx, sizeof(job)); + + j->ctx = ctx; + j->h = h; + j->body = body; + j->xbody = xbody; + + uw_register_transactional(ctx, j, commit, NULL, NULL); + + return uw_unit_v; +}