xref: /openbsd-src/usr.sbin/smtpd/smtpc.c (revision fc405d53b73a2d73393cb97f684863d17b583e38)
1 /*	$OpenBSD: smtpc.c,v 1.20 2023/05/16 17:48:52 op 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/socket.h>
20 
21 #include <event.h>
22 #include <netdb.h>
23 #include <pwd.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <syslog.h>
28 #include <tls.h>
29 #include <unistd.h>
30 
31 #include "smtp.h"
32 #include "log.h"
33 
34 static void parse_server(char *);
35 static void parse_message(FILE *);
36 static void resume(void);
37 
38 static int verbose = 1;
39 static int done = 0;
40 static int noaction = 0;
41 static struct addrinfo *res0, *ai;
42 static struct smtp_params params;
43 static struct smtp_mail mail;
44 static const char *servname = NULL;
45 static struct tls_config *tls_config;
46 
47 static int nosni = 0;
48 static const char *cafile = NULL;
49 static const char *protocols = NULL;
50 static const char *ciphers = NULL;
51 
52 static void
53 usage(void)
54 {
55 	extern char *__progname;
56 
57 	fprintf(stderr, "usage: %s [-Chnv] [-a authfile] [-F from] [-H helo] "
58 	    "[-s server] [-T params] [recipient ...]\n", __progname);
59 	exit(1);
60 }
61 
62 static void
63 parse_tls_options(char *opt)
64 {
65 	static char * const tokens[] = {
66 #define CAFILE 0
67 		"cafile",
68 #define CIPHERS 1
69 		"ciphers",
70 #define NOSNI 2
71 		"nosni",
72 #define NOVERIFY 3
73 		"noverify",
74 #define PROTOCOLS 4
75 		"protocols",
76 #define SERVERNAME 5
77 		"servername",
78 		NULL };
79 	char *value;
80 
81 	while (*opt) {
82 		switch (getsubopt(&opt, tokens, &value)) {
83 		case CAFILE:
84 			if (value == NULL)
85 				fatalx("missing value for cafile");
86 			cafile = value;
87 			break;
88 		case CIPHERS:
89 			if (value == NULL)
90 				fatalx("missing value for ciphers");
91 			ciphers = value;
92 			break;
93 		case NOSNI:
94 			if (value != NULL)
95 				fatalx("no value expected for nosni");
96 			nosni = 1;
97 			break;
98 		case NOVERIFY:
99 			if (value != NULL)
100 				fatalx("no value expected for noverify");
101 			params.tls_verify = 0;
102 			break;
103 		case PROTOCOLS:
104 			if (value == NULL)
105 				fatalx("missing value for protocols");
106 			protocols = value;
107 			break;
108 		case SERVERNAME:
109 			if (value == NULL)
110 				fatalx("missing value for servername");
111 			servname = value;
112 			break;
113 		case -1:
114 			if (suboptarg)
115 				fatalx("invalid TLS option \"%s\"", suboptarg);
116 			fatalx("missing TLS option");
117 		}
118 	}
119 }
120 
121 int
122 main(int argc, char **argv)
123 {
124 	char hostname[256];
125 	FILE *authfile;
126 	int ch, i;
127 	uint32_t protos;
128 	char *server = "localhost";
129 	char *authstr = NULL;
130 	size_t alloc = 0;
131 	ssize_t len;
132 	struct passwd *pw;
133 
134 	log_init(1, 0);
135 
136 	if (gethostname(hostname, sizeof(hostname)) == -1)
137 		fatal("gethostname");
138 
139 	if ((pw = getpwuid(getuid())) == NULL)
140 		fatal("getpwuid");
141 
142 	memset(&params, 0, sizeof(params));
143 
144 	params.linemax = 16392;
145 	params.ibufmax = 65536;
146 	params.obufmax = 65536;
147 	params.timeout = 100000;
148 	params.helo = hostname;
149 
150 	params.tls_verify = 1;
151 
152 	memset(&mail, 0, sizeof(mail));
153 	mail.from = pw->pw_name;
154 
155 	while ((ch = getopt(argc, argv, "CF:H:S:T:a:hns:v")) != -1) {
156 		switch (ch) {
157 		case 'C':
158 			params.tls_verify = 0;
159 			break;
160 		case 'F':
161 			mail.from = optarg;
162 			break;
163 		case 'H':
164 			params.helo = optarg;
165 			break;
166 		case 'S':
167 			servname = optarg;
168 			break;
169 		case 'T':
170 			parse_tls_options(optarg);
171 			break;
172 		case 'a':
173 			if ((authfile = fopen(optarg, "r")) == NULL)
174 				fatal("%s: open", optarg);
175 			if ((len = getline(&authstr, &alloc, authfile)) == -1)
176 				fatal("%s: Failed to read username", optarg);
177 			if (authstr[len - 1] == '\n')
178 				authstr[len - 1] = '\0';
179 			params.auth_user = authstr;
180 			authstr = NULL;
181 			len = 0;
182 			if ((len = getline(&authstr, &alloc, authfile)) == -1)
183 				fatal("%s: Failed to read password", optarg);
184 			if (authstr[len - 1] == '\n')
185 				authstr[len - 1] = '\0';
186 			params.auth_pass = authstr;
187 			fclose(authfile);
188 			break;
189 		case 'h':
190 			usage();
191 			break;
192 		case 'n':
193 			noaction = 1;
194 			break;
195 		case 's':
196 			server = optarg;
197 			break;
198 		case 'v':
199 			verbose++;
200 			break;
201 		default:
202 			usage();
203 		}
204 	}
205 
206 	log_setverbose(verbose);
207 
208 	argc -= optind;
209 	argv += optind;
210 
211 	if (argc) {
212 		mail.rcpt = calloc(argc, sizeof(*mail.rcpt));
213 		if (mail.rcpt == NULL)
214 			fatal("calloc");
215 		for (i = 0; i < argc; i++)
216 			mail.rcpt[i].to = argv[i];
217 		mail.rcptcount = argc;
218 	}
219 
220 	event_init();
221 
222 	tls_config = tls_config_new();
223 	if (tls_config == NULL)
224 		fatal("tls_config_new");
225 
226 	if (protocols) {
227 		if (tls_config_parse_protocols(&protos, protocols) == -1)
228 			fatalx("failed to parse protocol '%s'", protocols);
229 		if (tls_config_set_protocols(tls_config, protos) == -1)
230 			fatalx("tls_config_set_protocols: %s",
231 			    tls_config_error(tls_config));
232 	}
233 	if (ciphers && tls_config_set_ciphers(tls_config, ciphers) == -1)
234 		fatalx("tls_config_set_ciphers: %s",
235 		    tls_config_error(tls_config));
236 
237 	if (cafile == NULL)
238 		cafile = tls_default_ca_cert_file();
239 	if (tls_config_set_ca_file(tls_config, cafile) == -1)
240 		fatalx("tls_set_ca_file: %s", tls_config_error(tls_config));
241 	if (!params.tls_verify) {
242 		tls_config_insecure_noverifycert(tls_config);
243 		tls_config_insecure_noverifyname(tls_config);
244 		tls_config_insecure_noverifytime(tls_config);
245 	} else
246 		tls_config_verify(tls_config);
247 
248 	if (pledge("stdio inet dns tmppath", NULL) == -1)
249 		fatal("pledge");
250 
251 	if (!noaction)
252 		parse_message(stdin);
253 
254 	if (pledge("stdio inet dns", NULL) == -1)
255 		fatal("pledge");
256 
257 	parse_server(server);
258 
259 	if (pledge("stdio inet", NULL) == -1)
260 		fatal("pledge");
261 
262 	resume();
263 
264 	log_debug("done...");
265 
266 	return 0;
267 }
268 
269 static void
270 parse_server(char *server)
271 {
272 	struct addrinfo hints;
273 	char *scheme, *creds, *host, *port, *p, *c;
274 	int error;
275 
276 	creds = NULL;
277 	host = NULL;
278 	port = NULL;
279 	scheme = server;
280 
281 	p = strstr(server, "://");
282 	if (p) {
283 		*p = '\0';
284 		p += 3;
285 		/* check for credentials */
286 		c = strrchr(p, '@');
287 		if (c) {
288 			creds = p;
289 			*c = '\0';
290 			host = c + 1;
291 		} else
292 			host = p;
293 	} else {
294 		/* Assume a simple server name */
295 		scheme = "smtp";
296 		host = server;
297 	}
298 
299 	if (host[0] == '[') {
300 		/* IPV6 address? */
301 		p = strchr(host, ']');
302 		if (p) {
303 			if (p[1] == ':' || p[1] == '\0') {
304 				*p++ = '\0';	/* remove ']' */
305 				host++;		/* skip '[' */
306 				if (*p == ':')
307 					port = p + 1;
308 			}
309 		}
310 	}
311 	else {
312 		port = strchr(host, ':');
313 		if (port)
314 			*port++ = '\0';
315 	}
316 
317 	if (port && port[0] == '\0')
318 		port = NULL;
319 
320 	if (creds) {
321 		p = strchr(creds, ':');
322 		if (p == NULL)
323 			fatalx("invalid credentials");
324 		*p = '\0';
325 
326 		params.auth_user = creds;
327 		params.auth_pass = p + 1;
328 	}
329 	params.tls_req = TLS_YES;
330 
331 	if (!strcmp(scheme, "lmtp")) {
332 		params.lmtp = 1;
333 	}
334 	else if (!strcmp(scheme, "lmtp+tls")) {
335 		params.lmtp = 1;
336 		params.tls_req = TLS_FORCE;
337 	}
338 	else if (!strcmp(scheme, "lmtp+notls")) {
339 		params.lmtp = 1;
340 		params.tls_req = TLS_NO;
341 	}
342 	else if (!strcmp(scheme, "smtps")) {
343 		params.tls_req = TLS_SMTPS;
344 		if (port == NULL)
345 			port = "smtps";
346 	}
347 	else if (!strcmp(scheme, "smtp")) {
348 	}
349 	else if (!strcmp(scheme, "smtp+tls")) {
350 		params.tls_req = TLS_FORCE;
351 	}
352 	else if (!strcmp(scheme, "smtp+notls")) {
353 		params.tls_req = TLS_NO;
354 	}
355 	else
356 		fatalx("invalid url scheme %s", scheme);
357 
358 	if (port == NULL)
359 		port = "smtp";
360 
361 	if (servname == NULL)
362 		servname = host;
363 	if (nosni == 0)
364 		params.tls_servname = servname;
365 
366 	memset(&hints, 0, sizeof(hints));
367 	hints.ai_family = AF_UNSPEC;
368 	hints.ai_socktype = SOCK_STREAM;
369 	error = getaddrinfo(host, port, &hints, &res0);
370 	if (error)
371 		fatalx("%s: %s", host, gai_strerror(error));
372 	ai = res0;
373 }
374 
375 void
376 parse_message(FILE *ifp)
377 {
378 	char *line = NULL;
379 	size_t linesz = 0;
380 	ssize_t len;
381 
382 	if ((mail.fp = tmpfile()) == NULL)
383 		fatal("tmpfile");
384 
385 	for (;;) {
386 		if ((len = getline(&line, &linesz, ifp)) == -1) {
387 			if (feof(ifp))
388 				break;
389 			fatal("getline");
390 		}
391 
392 		if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n')
393 			line[--len - 1] = '\n';
394 
395 		if (fwrite(line, 1, len, mail.fp) != len)
396 			fatal("fwrite");
397 
398 		if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF)
399 			fatal("fputc");
400 	}
401 
402 	fclose(ifp);
403 	rewind(mail.fp);
404 }
405 
406 void
407 resume(void)
408 {
409 	static int started = 0;
410 	char host[256];
411 	char serv[16];
412 
413 	if (done) {
414 		event_loopexit(NULL);
415 		return;
416 	}
417 
418 	if (ai == NULL)
419 		fatalx("no more host");
420 
421 	getnameinfo(ai->ai_addr, ai->ai_addr->sa_len,
422 	    host, sizeof(host), serv, sizeof(serv),
423 	    NI_NUMERICHOST | NI_NUMERICSERV);
424 	log_debug("trying host %s port %s...", host, serv);
425 
426 	params.dst = ai->ai_addr;
427 	if (smtp_connect(&params, NULL) == NULL)
428 		fatal("smtp_connect");
429 
430 	if (started == 0) {
431 		started = 1;
432 		event_loop(0);
433 	}
434 }
435 
436 void
437 log_trace(int lvl, const char *emsg, ...)
438 {
439 	va_list ap;
440 
441 	if (verbose > lvl) {
442 		va_start(ap, emsg);
443 		vlog(LOG_DEBUG, emsg, ap);
444 		va_end(ap);
445 	}
446 }
447 
448 void
449 smtp_require_tls(void *tag, struct smtp_client *proto)
450 {
451 	struct tls *tls;
452 
453 	tls = tls_client();
454 	if (tls == NULL)
455 		fatal("tls_client");
456 
457 	if (tls_configure(tls, tls_config) == -1)
458 		fatalx("tls_configure: %s", tls_error(tls));
459 
460 	smtp_set_tls(proto, tls);
461 }
462 
463 void
464 smtp_ready(void *tag, struct smtp_client *proto)
465 {
466 	log_debug("connection ready...");
467 
468 	if (done || noaction)
469 		smtp_quit(proto);
470 	else
471 		smtp_sendmail(proto, &mail);
472 }
473 
474 void
475 smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail)
476 {
477 	switch (failure) {
478 	case FAIL_INTERNAL:
479 		log_warnx("internal error: %s", detail);
480 		break;
481 	case FAIL_CONN:
482 		log_warnx("connection error: %s", detail);
483 		break;
484 	case FAIL_PROTO:
485 		log_warnx("protocol error: %s", detail);
486 		break;
487 	case FAIL_IMPL:
488 		log_warnx("missing feature: %s", detail);
489 		break;
490 	case FAIL_RESP:
491 		log_warnx("rejected by server: %s", detail);
492 		break;
493 	default:
494 		fatalx("unknown failure %d: %s", failure, detail);
495 	}
496 }
497 
498 void
499 smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status)
500 {
501 	log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status);
502 }
503 
504 void
505 smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail)
506 {
507 	int i;
508 
509 	log_debug("mail done...");
510 
511 	if (noaction)
512 		return;
513 
514 	for (i = 0; i < mail->rcptcount; i++)
515 		if (!mail->rcpt[i].done)
516 			return;
517 
518 	done = 1;
519 }
520 
521 void
522 smtp_closed(void *tag, struct smtp_client *proto)
523 {
524 	log_debug("connection closed...");
525 
526 	ai = ai->ai_next;
527 	if (noaction && ai == NULL)
528 		done = 1;
529 
530 	resume();
531 }
532