xref: /openbsd-src/usr.sbin/smtpd/smtpc.c (revision 1a8dbaac879b9f3335ad7fb25429ce63ac1d6bac)
1 /*	$OpenBSD: smtpc.c,v 1.11 2020/09/14 18:32:11 millert Exp $	*/
2 
3 /*
4  * Copyright (c) 2018 Eric Faurot <eric@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/socket.h>
21 
22 #include <event.h>
23 #include <limits.h>
24 #include <netdb.h>
25 #include <pwd.h>
26 #include <resolv.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <syslog.h>
32 #include <unistd.h>
33 
34 #include <openssl/ssl.h>
35 
36 #include "smtp.h"
37 #include "ssl.h"
38 #include "log.h"
39 
40 static void parse_server(char *);
41 static void parse_message(FILE *);
42 static void resume(void);
43 
44 static int verbose = 1;
45 static int done = 0;
46 static int noaction = 0;
47 static struct addrinfo *res0, *ai;
48 static struct smtp_params params;
49 static struct smtp_mail mail;
50 static const char *servname = NULL;
51 
52 static SSL_CTX *ssl_ctx;
53 
54 static void
55 usage(void)
56 {
57 	extern char *__progname;
58 
59 	fprintf(stderr,
60 	    "usage: %s [-Chnv] [-F from] [-H helo] [-s server] [-S name] rcpt ...\n",
61 	    __progname);
62 	exit(1);
63 }
64 
65 int
66 main(int argc, char **argv)
67 {
68 	char hostname[256];
69 	int ch, i;
70 	char *server = "localhost";
71 	struct passwd *pw;
72 
73 	log_init(1, 0);
74 
75 	if (gethostname(hostname, sizeof(hostname)) == -1)
76 		fatal("gethostname");
77 
78 	if ((pw = getpwuid(getuid())) == NULL)
79 		fatal("getpwuid");
80 
81 	memset(&params, 0, sizeof(params));
82 
83 	params.linemax = 16392;
84 	params.ibufmax = 65536;
85 	params.obufmax = 65536;
86 	params.timeout = 100000;
87 	params.helo = hostname;
88 
89 	params.tls_verify = 1;
90 
91 	memset(&mail, 0, sizeof(mail));
92 	mail.from = pw->pw_name;
93 
94 	while ((ch = getopt(argc, argv, "CF:H:S:hns:v")) != -1) {
95 		switch (ch) {
96 		case 'C':
97 			params.tls_verify = 0;
98 			break;
99 		case 'F':
100 			mail.from = optarg;
101 			break;
102 		case 'H':
103 			params.helo = optarg;
104 			break;
105 		case 'S':
106 			servname = optarg;
107 			break;
108 		case 'h':
109 			usage();
110 			break;
111 		case 'n':
112 			noaction = 1;
113 			break;
114 		case 's':
115 			server = optarg;
116 			break;
117 		case 'v':
118 			verbose++;
119 			break;
120 		default:
121 			usage();
122 		}
123 	}
124 
125 	log_setverbose(verbose);
126 
127 	argc -= optind;
128 	argv += optind;
129 
130 	if (argc) {
131 		mail.rcpt = calloc(argc, sizeof(*mail.rcpt));
132 		if (mail.rcpt == NULL)
133 			fatal("calloc");
134 		for (i = 0; i < argc; i++)
135 			mail.rcpt[i].to = argv[i];
136 		mail.rcptcount = argc;
137 	}
138 
139 	ssl_init();
140 	event_init();
141 
142 	ssl_ctx = ssl_ctx_create(NULL, NULL, 0, NULL);
143 	if (!SSL_CTX_load_verify_locations(ssl_ctx,
144 	    X509_get_default_cert_file(), NULL))
145 		fatal("SSL_CTX_load_verify_locations");
146 	if (!SSL_CTX_set_ssl_version(ssl_ctx, SSLv23_client_method()))
147 		fatal("SSL_CTX_set_ssl_version");
148 	SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE , NULL);
149 
150 	if (pledge("stdio inet dns tmppath", NULL) == -1)
151 		fatal("pledge");
152 
153 	if (!noaction)
154 		parse_message(stdin);
155 
156 	if (pledge("stdio inet dns", NULL) == -1)
157 		fatal("pledge");
158 
159 	parse_server(server);
160 
161 	if (pledge("stdio inet", NULL) == -1)
162 		fatal("pledge");
163 
164 	resume();
165 
166 	log_debug("done...");
167 
168 	return 0;
169 }
170 
171 static void
172 parse_server(char *server)
173 {
174 	struct addrinfo hints;
175 	char *scheme, *creds, *host, *port, *p, *c;
176 	int error;
177 
178 	creds = NULL;
179 	host = NULL;
180 	port = NULL;
181 	scheme = server;
182 
183 	p = strstr(server, "://");
184 	if (p) {
185 		*p = '\0';
186 		p += 3;
187 		/* check for credentials */
188 		c = strrchr(p, '@');
189 		if (c) {
190 			creds = p;
191 			*c = '\0';
192 			host = c + 1;
193 		} else
194 			host = p;
195 	} else {
196 		/* Assume a simple server name */
197 		scheme = "smtp";
198 		host = server;
199 	}
200 
201 	if (host[0] == '[') {
202 		/* IPV6 address? */
203 		p = strchr(host, ']');
204 		if (p) {
205 			if (p[1] == ':' || p[1] == '\0') {
206 				*p++ = '\0';	/* remove ']' */
207 				host++;		/* skip '[' */
208 				if (*p == ':')
209 					port = p + 1;
210 			}
211 		}
212 	}
213 	else {
214 		port = strchr(host, ':');
215 		if (port)
216 			*port++ = '\0';
217 	}
218 
219 	if (port && port[0] == '\0')
220 		port = NULL;
221 
222 	if (creds) {
223 		p = strchr(creds, ':');
224 		if (p == NULL)
225 			fatalx("invalid credentials");
226 		*p = '\0';
227 
228 		params.auth_user = creds;
229 		params.auth_pass = p + 1;
230 	}
231 	params.tls_req = TLS_YES;
232 
233 	if (!strcmp(scheme, "lmtp")) {
234 		params.lmtp = 1;
235 	}
236 	else if (!strcmp(scheme, "lmtp+tls")) {
237 		params.lmtp = 1;
238 		params.tls_req = TLS_FORCE;
239 	}
240 	else if (!strcmp(scheme, "lmtp+notls")) {
241 		params.lmtp = 1;
242 		params.tls_req = TLS_NO;
243 	}
244 	else if (!strcmp(scheme, "smtps")) {
245 		params.tls_req = TLS_SMTPS;
246 		if (port == NULL)
247 			port = "smtps";
248 	}
249 	else if (!strcmp(scheme, "smtp")) {
250 	}
251 	else if (!strcmp(scheme, "smtp+tls")) {
252 		params.tls_req = TLS_FORCE;
253 	}
254 	else if (!strcmp(scheme, "smtp+notls")) {
255 		params.tls_req = TLS_NO;
256 	}
257 	else
258 		fatalx("invalid url scheme %s", scheme);
259 
260 	if (port == NULL)
261 		port = "smtp";
262 
263 	if (servname == NULL)
264 		servname = host;
265 
266 	memset(&hints, 0, sizeof(hints));
267 	hints.ai_family = AF_UNSPEC;
268 	hints.ai_socktype = SOCK_STREAM;
269 	error = getaddrinfo(host, port, &hints, &res0);
270 	if (error)
271 		fatalx("%s: %s", host, gai_strerror(error));
272 	ai = res0;
273 }
274 
275 void
276 parse_message(FILE *ifp)
277 {
278 	char *line = NULL;
279 	size_t linesz = 0;
280 	ssize_t len;
281 
282 	if ((mail.fp = tmpfile()) == NULL)
283 		fatal("tmpfile");
284 
285 	for (;;) {
286 		if ((len = getline(&line, &linesz, ifp)) == -1) {
287 			if (feof(ifp))
288 				break;
289 			fatal("getline");
290 		}
291 
292 		if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n')
293 			line[--len - 1] = '\n';
294 
295 		if (fwrite(line, 1, len, mail.fp) != len)
296 			fatal("fwrite");
297 
298 		if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF)
299 			fatal("fputc");
300 	}
301 
302 	fclose(ifp);
303 	rewind(mail.fp);
304 }
305 
306 void
307 resume(void)
308 {
309 	static int started = 0;
310 	char host[256];
311 	char serv[16];
312 
313 	if (done) {
314 		event_loopexit(NULL);
315 		return;
316 	}
317 
318 	if (ai == NULL)
319 		fatalx("no more host");
320 
321 	getnameinfo(ai->ai_addr, ai->ai_addr->sa_len,
322 	    host, sizeof(host), serv, sizeof(serv),
323 	    NI_NUMERICHOST | NI_NUMERICSERV);
324 	log_debug("trying host %s port %s...", host, serv);
325 
326 	params.dst = ai->ai_addr;
327 	if (smtp_connect(&params, NULL) == NULL)
328 		fatal("smtp_connect");
329 
330 	if (started == 0) {
331 		started = 1;
332 		event_loop(0);
333 	}
334 }
335 
336 void
337 log_trace(int lvl, const char *emsg, ...)
338 {
339 	va_list ap;
340 
341 	if (verbose > lvl) {
342 		va_start(ap, emsg);
343 		vlog(LOG_DEBUG, emsg, ap);
344 		va_end(ap);
345 	}
346 }
347 
348 void
349 smtp_verify_server_cert(void *tag, struct smtp_client *proto, void *ctx)
350 {
351 	SSL *ssl = ctx;
352 	X509 *cert;
353 	long res;
354 	int match;
355 
356 	if ((cert = SSL_get_peer_certificate(ssl))) {
357 		(void)ssl_check_name(cert, servname, &match);
358 		X509_free(cert);
359 		res = SSL_get_verify_result(ssl);
360 		if (res == X509_V_OK) {
361 			if (match) {
362 				log_debug("valid certificate");
363 				smtp_cert_verified(proto, CERT_OK);
364 			}
365 			else {
366 				log_debug("certificate does not match hostname");
367 				smtp_cert_verified(proto, CERT_INVALID);
368 			}
369 			return;
370 		}
371 		log_debug("certificate validation error %ld", res);
372 	}
373 	else
374 		log_debug("no certificate provided");
375 
376 	smtp_cert_verified(proto, CERT_INVALID);
377 }
378 
379 void
380 smtp_require_tls(void *tag, struct smtp_client *proto)
381 {
382 	SSL *ssl = NULL;
383 
384 	if ((ssl = SSL_new(ssl_ctx)) == NULL)
385 		fatal("SSL_new");
386 	smtp_set_tls(proto, ssl);
387 }
388 
389 void
390 smtp_ready(void *tag, struct smtp_client *proto)
391 {
392 	log_debug("connection ready...");
393 
394 	if (done || noaction)
395 		smtp_quit(proto);
396 	else
397 		smtp_sendmail(proto, &mail);
398 }
399 
400 void
401 smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail)
402 {
403 	switch (failure) {
404 	case FAIL_INTERNAL:
405 		log_warnx("internal error: %s", detail);
406 		break;
407 	case FAIL_CONN:
408 		log_warnx("connection error: %s", detail);
409 		break;
410 	case FAIL_PROTO:
411 		log_warnx("protocol error: %s", detail);
412 		break;
413 	case FAIL_IMPL:
414 		log_warnx("missing feature: %s", detail);
415 		break;
416 	case FAIL_RESP:
417 		log_warnx("rejected by server: %s", detail);
418 		break;
419 	default:
420 		fatalx("unknown failure %d: %s", failure, detail);
421 	}
422 }
423 
424 void
425 smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status)
426 {
427 	log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status);
428 }
429 
430 void
431 smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail)
432 {
433 	int i;
434 
435 	log_debug("mail done...");
436 
437 	if (noaction)
438 		return;
439 
440 	for (i = 0; i < mail->rcptcount; i++)
441 		if (!mail->rcpt[i].done)
442 			return;
443 
444 	done = 1;
445 }
446 
447 void
448 smtp_closed(void *tag, struct smtp_client *proto)
449 {
450 	log_debug("connection closed...");
451 
452 	ai = ai->ai_next;
453 	if (noaction && ai == NULL)
454 		done = 1;
455 
456 	resume();
457 }
458