aboutsummaryrefslogtreecommitdiffstats
path: root/usr.sbin/smtpd/smtpc.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.sbin/smtpd/smtpc.c')
-rw-r--r--usr.sbin/smtpd/smtpc.c465
1 files changed, 465 insertions, 0 deletions
diff --git a/usr.sbin/smtpd/smtpc.c b/usr.sbin/smtpd/smtpc.c
new file mode 100644
index 00000000..59479703
--- /dev/null
+++ b/usr.sbin/smtpd/smtpc.c
@@ -0,0 +1,465 @@
+/* $OpenBSD: smtpc.c,v 1.10 2019/09/21 09:04:08 semarie Exp $ */
+
+/*
+ * Copyright (c) 2018 Eric Faurot <eric@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "includes.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <event.h>
+#include <limits.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <resolv.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#include <openssl/ssl.h>
+
+#include "smtp.h"
+#include "ssl.h"
+#include "log.h"
+
+static void parse_server(char *);
+static void parse_message(FILE *);
+static void resume(void);
+
+static int verbose = 1;
+static int done = 0;
+static int noaction = 0;
+static struct addrinfo *res0, *ai;
+static struct smtp_params params;
+static struct smtp_mail mail;
+static const char *servname = NULL;
+
+static SSL_CTX *ssl_ctx;
+
+static void
+usage(void)
+{
+ extern char *__progname;
+
+ fprintf(stderr,
+ "usage: %s [-Chnv] [-F from] [-H helo] [-s server] [-S name] rcpt ...\n",
+ __progname);
+ exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+ char hostname[256];
+ int ch, i;
+ char *server = "localhost";
+ struct passwd *pw;
+
+ log_init(1, 0);
+
+ if (gethostname(hostname, sizeof(hostname)) == -1)
+ fatal("gethostname");
+
+ if ((pw = getpwuid(getuid())) == NULL)
+ fatal("getpwuid");
+
+ memset(&params, 0, sizeof(params));
+
+ params.linemax = 16392;
+ params.ibufmax = 65536;
+ params.obufmax = 65536;
+ params.timeout = 100000;
+ params.helo = hostname;
+
+ params.tls_verify = 1;
+
+ memset(&mail, 0, sizeof(mail));
+ mail.from = pw->pw_name;
+
+ while ((ch = getopt(argc, argv, "CF:H:S:hns:v")) != -1) {
+ switch (ch) {
+ case 'C':
+ params.tls_verify = 0;
+ break;
+ case 'F':
+ mail.from = optarg;
+ break;
+ case 'H':
+ params.helo = optarg;
+ break;
+ case 'S':
+ servname = optarg;
+ break;
+ case 'h':
+ usage();
+ break;
+ case 'n':
+ noaction = 1;
+ break;
+ case 's':
+ server = optarg;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ }
+ }
+
+ log_setverbose(verbose);
+
+ argc -= optind;
+ argv += optind;
+
+ if (argc) {
+ mail.rcpt = calloc(argc, sizeof(*mail.rcpt));
+ if (mail.rcpt == NULL)
+ fatal("calloc");
+ for (i = 0; i < argc; i++)
+ mail.rcpt[i].to = argv[i];
+ mail.rcptcount = argc;
+ }
+
+ ssl_init();
+ event_init();
+
+ ssl_ctx = ssl_ctx_create(NULL, NULL, 0, NULL);
+ if (!SSL_CTX_load_verify_locations(ssl_ctx,
+ X509_get_default_cert_file(), NULL))
+ fatal("SSL_CTX_load_verify_locations");
+ if (!SSL_CTX_set_ssl_version(ssl_ctx, SSLv23_client_method()))
+ fatal("SSL_CTX_set_ssl_version");
+ SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE , NULL);
+
+#if HAVE_PLEDGE
+ if (pledge("stdio inet dns tmppath", NULL) == -1)
+ fatal("pledge");
+#endif
+
+ if (!noaction)
+ parse_message(stdin);
+
+#if HAVE_PLEDGE
+ if (pledge("stdio inet dns", NULL) == -1)
+ fatal("pledge");
+#endif
+
+ parse_server(server);
+
+#if HAVE_PLEDGE
+ if (pledge("stdio inet", NULL) == -1)
+ fatal("pledge");
+#endif
+
+ resume();
+
+ log_debug("done...");
+
+ return 0;
+}
+
+static void
+parse_server(char *server)
+{
+ struct addrinfo hints;
+ char *scheme, *creds, *host, *port, *p, *c;
+ int error;
+
+ creds = NULL;
+ host = NULL;
+ port = NULL;
+ scheme = server;
+
+ p = strstr(server, "://");
+ if (p) {
+ *p = '\0';
+ p += 3;
+ /* check for credentials */
+ c = strchr(p, '@');
+ if (c) {
+ creds = p;
+ *c = '\0';
+ host = c + 1;
+ } else
+ host = p;
+ } else {
+ /* Assume a simple server name */
+ scheme = "smtp";
+ host = server;
+ }
+
+ if (host[0] == '[') {
+ /* IPV6 address? */
+ p = strchr(host, ']');
+ if (p) {
+ if (p[1] == ':' || p[1] == '\0') {
+ *p++ = '\0'; /* remove ']' */
+ host++; /* skip '[' */
+ if (*p == ':')
+ port = p + 1;
+ }
+ }
+ }
+ else {
+ port = strchr(host, ':');
+ if (port)
+ *port++ = '\0';
+ }
+
+ if (port && port[0] == '\0')
+ port = NULL;
+
+ if (creds) {
+ p = strchr(creds, ':');
+ if (p == NULL)
+ fatalx("invalid credentials");
+ *p = '\0';
+
+ params.auth_user = creds;
+ params.auth_pass = p + 1;
+ }
+ params.tls_req = TLS_YES;
+
+ if (!strcmp(scheme, "lmtp")) {
+ params.lmtp = 1;
+ }
+ else if (!strcmp(scheme, "lmtp+tls")) {
+ params.lmtp = 1;
+ params.tls_req = TLS_FORCE;
+ }
+ else if (!strcmp(scheme, "lmtp+notls")) {
+ params.lmtp = 1;
+ params.tls_req = TLS_NO;
+ }
+ else if (!strcmp(scheme, "smtps")) {
+ params.tls_req = TLS_SMTPS;
+ if (port == NULL)
+ port = "smtps";
+ }
+ else if (!strcmp(scheme, "smtp")) {
+ }
+ else if (!strcmp(scheme, "smtp+tls")) {
+ params.tls_req = TLS_FORCE;
+ }
+ else if (!strcmp(scheme, "smtp+notls")) {
+ params.tls_req = TLS_NO;
+ }
+ else
+ fatalx("invalid url scheme %s", scheme);
+
+ if (port == NULL)
+ port = "smtp";
+
+ if (servname == NULL)
+ servname = host;
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ error = getaddrinfo(host, port, &hints, &res0);
+ if (error)
+ fatalx("%s: %s", host, gai_strerror(error));
+ ai = res0;
+}
+
+void
+parse_message(FILE *ifp)
+{
+ char *line = NULL;
+ size_t linesz = 0;
+ ssize_t len;
+
+ if ((mail.fp = tmpfile()) == NULL)
+ fatal("tmpfile");
+
+ for (;;) {
+ if ((len = getline(&line, &linesz, ifp)) == -1) {
+ if (feof(ifp))
+ break;
+ fatal("getline");
+ }
+
+ if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n')
+ line[--len - 1] = '\n';
+
+ if (fwrite(line, 1, len, mail.fp) != (size_t)len)
+ fatal("fwrite");
+
+ if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF)
+ fatal("fputc");
+ }
+
+ fclose(ifp);
+ rewind(mail.fp);
+}
+
+void
+resume(void)
+{
+ static int started = 0;
+ char host[256];
+ char serv[16];
+
+ if (done) {
+ event_loopexit(NULL);
+ return;
+ }
+
+ if (ai == NULL)
+ fatalx("no more host");
+
+ getnameinfo(ai->ai_addr, SA_LEN(ai->ai_addr),
+ host, sizeof(host), serv, sizeof(serv),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ log_debug("trying host %s port %s...", host, serv);
+
+ params.dst = ai->ai_addr;
+ if (smtp_connect(&params, NULL) == NULL)
+ fatal("smtp_connect");
+
+ if (started == 0) {
+ started = 1;
+ event_loop(0);
+ }
+}
+
+void
+log_trace(int lvl, const char *emsg, ...)
+{
+ va_list ap;
+
+ if (verbose > lvl) {
+ va_start(ap, emsg);
+ vlog(LOG_DEBUG, emsg, ap);
+ va_end(ap);
+ }
+}
+
+void
+smtp_verify_server_cert(void *tag, struct smtp_client *proto, void *ctx)
+{
+ SSL *ssl = ctx;
+ X509 *cert;
+ long res;
+ int match;
+
+ if ((cert = SSL_get_peer_certificate(ssl))) {
+ (void)ssl_check_name(cert, servname, &match);
+ X509_free(cert);
+ res = SSL_get_verify_result(ssl);
+ if (res == X509_V_OK) {
+ if (match) {
+ log_debug("valid certificate");
+ smtp_cert_verified(proto, CERT_OK);
+ }
+ else {
+ log_debug("certificate does not match hostname");
+ smtp_cert_verified(proto, CERT_INVALID);
+ }
+ return;
+ }
+ log_debug("certificate validation error %ld", res);
+ }
+ else
+ log_debug("no certificate provided");
+
+ smtp_cert_verified(proto, CERT_INVALID);
+}
+
+void
+smtp_require_tls(void *tag, struct smtp_client *proto)
+{
+ SSL *ssl = NULL;
+
+ if ((ssl = SSL_new(ssl_ctx)) == NULL)
+ fatal("SSL_new");
+ smtp_set_tls(proto, ssl);
+}
+
+void
+smtp_ready(void *tag, struct smtp_client *proto)
+{
+ log_debug("connection ready...");
+
+ if (done || noaction)
+ smtp_quit(proto);
+ else
+ smtp_sendmail(proto, &mail);
+}
+
+void
+smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail)
+{
+ switch (failure) {
+ case FAIL_INTERNAL:
+ log_warnx("internal error: %s", detail);
+ break;
+ case FAIL_CONN:
+ log_warnx("connection error: %s", detail);
+ break;
+ case FAIL_PROTO:
+ log_warnx("protocol error: %s", detail);
+ break;
+ case FAIL_IMPL:
+ log_warnx("missing feature: %s", detail);
+ break;
+ case FAIL_RESP:
+ log_warnx("rejected by server: %s", detail);
+ break;
+ default:
+ fatalx("unknown failure %d: %s", failure, detail);
+ }
+}
+
+void
+smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status)
+{
+ log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status);
+}
+
+void
+smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail)
+{
+ int i;
+
+ log_debug("mail done...");
+
+ if (noaction)
+ return;
+
+ for (i = 0; i < mail->rcptcount; i++)
+ if (!mail->rcpt[i].done)
+ return;
+
+ done = 1;
+}
+
+void
+smtp_closed(void *tag, struct smtp_client *proto)
+{
+ log_debug("connection closed...");
+
+ ai = ai->ai_next;
+ if (noaction && ai == NULL)
+ done = 1;
+
+ resume();
+}