commit b3e02120e4af1d0e9cfc0b66643de38a2a38d7e6 from: Stefan Sperling date: Mon Mar 11 09:49:08 2024 UTC send mail commit - cb984d1ca2f22aefd198679a29a9b7738c1188f2 commit + b3e02120e4af1d0e9cfc0b66643de38a2a38d7e6 blob - 1ee285b62e5729cc16778e0d8573748ace7475e5 blob + 13eb7042ee3ae10f0021d9550dc9b67d662373c2 --- gotd/libexec/got-notify-email/Makefile +++ gotd/libexec/got-notify-email/Makefile @@ -4,7 +4,7 @@ .include "../../../got-version.mk" PROG= got-notify-email -SRCS= got-notify-email.c +SRCS= got-notify-email.c sockaddr.c pollfd.c error.c hash.c CPPFLAGS = -I${.CURDIR}/../../../include -I${.CURDIR}/../../../lib blob - 5a1f5b95c4148b7d731e31226d25187b262c981b blob + 06961df72edfcd3d8cc732ac5350abe3ee91498a --- gotd/libexec/got-notify-email/got-notify-email.c +++ gotd/libexec/got-notify-email/got-notify-email.c @@ -14,19 +14,30 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include +#include +#include + #include #include #include +#include #include #include #include +#include #include +#include "got_error.h" +#include "got_sockaddr.h" + +#include "got_lib_poll.h" + __dead static void usage(void) { - fprintf(stderr, "usage: %s [-ds] [-f sender ] [-r repo-path] " - "[-R responder] recipient\n", getprogname()); + fprintf(stderr, "usage: %s [-f sender ] [-r responder] " + "[-s subject] recipient\n", getprogname()); exit(1); } @@ -58,37 +69,247 @@ set_default_fromaddr(void) return s; } +static const struct got_error * +readn(ssize_t *off, int fd, void *buf, size_t n) +{ + ssize_t r; + + *off = 0; + while (*off != n) { + r = read(fd, buf + *off, n - *off); + if (r == -1) + return got_error_from_errno("read"); + if (r == 0) + return got_error(GOT_ERR_EOF); + *off += r; + } + return NULL; +} + +static int +read_smtp_code(int s, const char *code) +{ + const struct got_error *error; + char buf[4]; + ssize_t off; + + error = readn(&off, s, buf, 3); + if (error) + errx(1, "read: %s", error->msg); + if (strncmp(buf, code, 3) != 0) { + buf[3] = '\0'; + warnx("unexpected SMTP message code: %s", buf); + return -1; + } + + return 0; +} + +static int +skip_to_crlf(int s) +{ + const struct got_error *error; + char buf[1]; + ssize_t off; + + for (;;) { + error = readn(&off, s, buf, 1); + if (error) + errx(1, "read: %s", error->msg); + if (buf[0] == '\r') { + error = readn(&off, s, buf, 1); + if (error) + errx(1, "read: %s", error->msg); + if (buf[0] == '\n') + return 0; + } + } + + return -1; +} + +static int +send_smtp_msg(int s, const char *fmt, ...) +{ + const struct got_error *error; + char buf[512]; + int len; + va_list ap; + + va_start(ap, fmt); + len = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (len < 0) { + warn("vsnprintf"); + return -1; + } + if (len >= sizeof(buf)) { + warnx("%s: buffer too small for message '%s...'", + __func__, buf); + return -1; + } + + error = got_poll_write_full(s, buf, len); + if (error) { + warnx("write: %s", error->msg); + return -1; + } + + return 0; +} + +static char * +get_datestr(time_t *time, char *datebuf) +{ + struct tm mytm, *tm; + char *p, *s; + + tm = gmtime_r(time, &mytm); + if (tm == NULL) + return NULL; + s = asctime_r(tm, datebuf); + if (s == NULL) + return NULL; + p = strchr(s, '\n'); + if (p) + *p = '\0'; + return s; +} + static void -send_email(const char *fromaddr, const char *recipient, - const char *replytoaddr) +send_email(const char *myfromaddr, const char *fromaddr, + const char *recipient, const char *replytoaddr, + const char *subject) { + const struct got_error *error; char *line = NULL; size_t linesize = 0; ssize_t linelen; + struct in_addr in_addr; + struct sockaddr_in sa_in; + int s = -1; + time_t now; + char datebuf[26]; + char *datestr; + now = time(NULL); + datestr = get_datestr(&now, datebuf); + + if (inet_aton("127.0.0.1", &in_addr) == 0) + err(1, "inet_aton"); + + got_sockaddr_inet_init(&sa_in, &in_addr); + + s = socket(AF_INET, SOCK_STREAM, 0); + if (s == -1) + err(1, "socket"); + + if (connect(s, (struct sockaddr *)&sa_in, sizeof(sa_in)) == -1) + err(1, "connect 127.0.0.1:25"); + + if (read_smtp_code(s, "220")) + errx(1, "unexpected SMTP greeting received"); + if (skip_to_crlf(s)) + errx(1, "invalid SMTP message received"); + + if (send_smtp_msg(s, "HELO localhost\r\n")) + errx(1, "could not send HELO"); + if (read_smtp_code(s, "250")) + errx(1, "unexpected SMTP response received"); + if (skip_to_crlf(s)) + errx(1, "invalid SMTP message received"); + + if (send_smtp_msg(s, "MAIL FROM:<%s>\r\n", myfromaddr)) + errx(1, "could not send MAIL FROM"); + if (read_smtp_code(s, "250")) + errx(1, "unexpected SMTP response received"); + if (skip_to_crlf(s)) + errx(1, "invalid SMTP message received"); + + if (send_smtp_msg(s, "RCPT TO:<%s>\r\n", recipient)) + errx(1, "could not send MAIL FROM"); + if (read_smtp_code(s, "250")) + errx(1, "unexpected SMTP response received"); + if (skip_to_crlf(s)) + errx(1, "invalid SMTP message received"); + + if (send_smtp_msg(s, "DATA\r\n")) + errx(1, "could not send MAIL FROM"); + if (read_smtp_code(s, "354")) + errx(1, "unexpected SMTP response received"); + if (skip_to_crlf(s)) + errx(1, "invalid SMTP message received"); + + if (send_smtp_msg(s, "From: %s\r\n", fromaddr)) + errx(1, "could not send From header"); + if (send_smtp_msg(s, "To: %s\r\n", recipient)) + errx(1, "could not send To header"); + if (replytoaddr) { + if (send_smtp_msg(s, "Reply-To: %s\r\n", replytoaddr)) + errx(1, "could not send Reply-To header"); + } + if (send_smtp_msg(s, "Date: %s\r\n", datestr)) + errx(1, "could not send Date header"); + + if (send_smtp_msg(s, "Subject: %s\r\n", subject)) + errx(1, "could not send Subject header"); + + if (send_smtp_msg(s, "\r\n")) + errx(1, "could not send body delimiter"); + while ((linelen = getline(&line, &linesize, stdin)) != -1) { + if (line[0] == '.') { /* dot stuffing */ + error = got_poll_write_full(s, ".", 1); + if (error) + errx(1, "write: %s", error->msg); + } + error = got_poll_write_full(s, line, linelen); + if (error) + errx(1, "write: %s", error->msg); } + if (send_smtp_msg(s, "\r\n.\r\n")) + errx(1, "could not send data terminator"); + if (read_smtp_code(s, "250")) + errx(1, "unexpected SMTP response received"); + if (skip_to_crlf(s)) + errx(1, "invalid SMTP message received"); + + if (send_smtp_msg(s, "QUIT\r\n")) + errx(1, "could not send QUIT"); + + if (read_smtp_code(s, "211")) + errx(1, "unexpected SMTP response received"); + if (skip_to_crlf(s)) + errx(1, "invalid SMTP message received"); + + close(s); free(line); } int main(int argc, char *argv[]) { - char *fromaddr = NULL; - const char *recipient = NULL, *replytoaddr = NULL; + char *default_fromaddr = NULL; + const char *fromaddr = NULL, *recipient = NULL, *replytoaddr = NULL; + const char *subject = "gotd notification"; int ch; - while ((ch = getopt(argc, argv, "f:r:")) != -1) { + while ((ch = getopt(argc, argv, "f:r:s:")) != -1) { switch (ch) { case 'f': - fromaddr = strdup(optarg); - if (fromaddr == NULL) - err(1, "strdup"); + fromaddr = optarg; break; case 'r': replytoaddr = optarg; break; + case 's': + subject = optarg; + break; + default: + usage(); + /* NOTREACHED */ + break; } } @@ -101,19 +322,23 @@ main(int argc, char *argv[]) if (pledge("stdio dns inet pwd", NULL) == -1) err(1, "pledge"); #endif + default_fromaddr = set_default_fromaddr(); + +#ifndef PROFILE + if (pledge("stdio dns inet", NULL) == -1) + err(1, "pledge"); +#endif + recipient = argv[0]; if (!validate_email_addr(recipient)) goto done; if (fromaddr == NULL) - fromaddr = set_default_fromaddr(); + fromaddr = default_fromaddr; -#ifndef PROFILE - if (pledge("stdio dns inet", NULL) == -1) - err(1, "pledge"); -#endif - send_email(fromaddr, recipient, replytoaddr); + send_email(default_fromaddr, fromaddr, recipient, replytoaddr, + subject); done: - free(fromaddr); + free(default_fromaddr); return 0; }