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