xref: /openbsd-src/usr.sbin/smtpd/smtpc.c (revision f1dd7b858388b4a23f4f67a4957ec5ff656ebbe8)
1 /*	$OpenBSD: smtpc.c,v 1.15 2021/04/10 10:19:19 eric 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 <tls.h>
33 #include <unistd.h>
34 
35 #include "smtp.h"
36 #include "log.h"
37 
38 static void parse_server(char *);
39 static void parse_message(FILE *);
40 static void resume(void);
41 
42 static int verbose = 1;
43 static int done = 0;
44 static int noaction = 0;
45 static struct addrinfo *res0, *ai;
46 static struct smtp_params params;
47 static struct smtp_mail mail;
48 static const char *servname = NULL;
49 static struct tls_config *tls_config;
50 
51 static void
52 usage(void)
53 {
54 	extern char *__progname;
55 
56 	fprintf(stderr, "usage: %s [-Chnv] [-a authfile] [-F from] [-H helo] "
57 	    "[-s server] [recipient ...]\n", __progname);
58 	exit(1);
59 }
60 
61 int
62 main(int argc, char **argv)
63 {
64 	char hostname[256];
65 	FILE *authfile;
66 	int ch, i;
67 	char *server = "localhost";
68 	char *authstr = NULL;
69 	size_t alloc = 0;
70 	ssize_t len;
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:a: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 'a':
109 			if ((authfile = fopen(optarg, "r")) == NULL)
110 				fatal("%s: open", optarg);
111 			if ((len = getline(&authstr, &alloc, authfile)) == -1)
112 				fatal("%s: Failed to read username", optarg);
113 			if (authstr[len - 1] == '\n')
114 				authstr[len - 1] = '\0';
115 			params.auth_user = authstr;
116 			authstr = NULL;
117 			len = 0;
118 			if ((len = getline(&authstr, &alloc, authfile)) == -1)
119 				fatal("%s: Failed to read password", optarg);
120 			if (authstr[len - 1] == '\n')
121 				authstr[len - 1] = '\0';
122 			params.auth_pass = authstr;
123 			fclose(authfile);
124 			break;
125 		case 'h':
126 			usage();
127 			break;
128 		case 'n':
129 			noaction = 1;
130 			break;
131 		case 's':
132 			server = optarg;
133 			break;
134 		case 'v':
135 			verbose++;
136 			break;
137 		default:
138 			usage();
139 		}
140 	}
141 
142 	log_setverbose(verbose);
143 
144 	argc -= optind;
145 	argv += optind;
146 
147 	if (argc) {
148 		mail.rcpt = calloc(argc, sizeof(*mail.rcpt));
149 		if (mail.rcpt == NULL)
150 			fatal("calloc");
151 		for (i = 0; i < argc; i++)
152 			mail.rcpt[i].to = argv[i];
153 		mail.rcptcount = argc;
154 	}
155 
156 	tls_init();
157 	event_init();
158 
159 	tls_config = tls_config_new();
160 	if (tls_config == NULL)
161 		fatal("tls_config_new");
162 	if (tls_config_set_ca_file(tls_config, tls_default_ca_cert_file()) == -1)
163 		fatal("tls_set_ca_file");
164 	if (!params.tls_verify) {
165 		tls_config_insecure_noverifycert(tls_config);
166 		tls_config_insecure_noverifyname(tls_config);
167 		tls_config_insecure_noverifytime(tls_config);
168 	} else
169 		tls_config_verify(tls_config);
170 
171 	if (pledge("stdio inet dns tmppath", NULL) == -1)
172 		fatal("pledge");
173 
174 	if (!noaction)
175 		parse_message(stdin);
176 
177 	if (pledge("stdio inet dns", NULL) == -1)
178 		fatal("pledge");
179 
180 	parse_server(server);
181 
182 	if (pledge("stdio inet", NULL) == -1)
183 		fatal("pledge");
184 
185 	resume();
186 
187 	log_debug("done...");
188 
189 	return 0;
190 }
191 
192 static void
193 parse_server(char *server)
194 {
195 	struct addrinfo hints;
196 	char *scheme, *creds, *host, *port, *p, *c;
197 	int error;
198 
199 	creds = NULL;
200 	host = NULL;
201 	port = NULL;
202 	scheme = server;
203 
204 	p = strstr(server, "://");
205 	if (p) {
206 		*p = '\0';
207 		p += 3;
208 		/* check for credentials */
209 		c = strrchr(p, '@');
210 		if (c) {
211 			creds = p;
212 			*c = '\0';
213 			host = c + 1;
214 		} else
215 			host = p;
216 	} else {
217 		/* Assume a simple server name */
218 		scheme = "smtp";
219 		host = server;
220 	}
221 
222 	if (host[0] == '[') {
223 		/* IPV6 address? */
224 		p = strchr(host, ']');
225 		if (p) {
226 			if (p[1] == ':' || p[1] == '\0') {
227 				*p++ = '\0';	/* remove ']' */
228 				host++;		/* skip '[' */
229 				if (*p == ':')
230 					port = p + 1;
231 			}
232 		}
233 	}
234 	else {
235 		port = strchr(host, ':');
236 		if (port)
237 			*port++ = '\0';
238 	}
239 
240 	if (port && port[0] == '\0')
241 		port = NULL;
242 
243 	if (creds) {
244 		p = strchr(creds, ':');
245 		if (p == NULL)
246 			fatalx("invalid credentials");
247 		*p = '\0';
248 
249 		params.auth_user = creds;
250 		params.auth_pass = p + 1;
251 	}
252 	params.tls_req = TLS_YES;
253 
254 	if (!strcmp(scheme, "lmtp")) {
255 		params.lmtp = 1;
256 	}
257 	else if (!strcmp(scheme, "lmtp+tls")) {
258 		params.lmtp = 1;
259 		params.tls_req = TLS_FORCE;
260 	}
261 	else if (!strcmp(scheme, "lmtp+notls")) {
262 		params.lmtp = 1;
263 		params.tls_req = TLS_NO;
264 	}
265 	else if (!strcmp(scheme, "smtps")) {
266 		params.tls_req = TLS_SMTPS;
267 		if (port == NULL)
268 			port = "smtps";
269 	}
270 	else if (!strcmp(scheme, "smtp")) {
271 	}
272 	else if (!strcmp(scheme, "smtp+tls")) {
273 		params.tls_req = TLS_FORCE;
274 	}
275 	else if (!strcmp(scheme, "smtp+notls")) {
276 		params.tls_req = TLS_NO;
277 	}
278 	else
279 		fatalx("invalid url scheme %s", scheme);
280 
281 	if (port == NULL)
282 		port = "smtp";
283 
284 	if (servname == NULL)
285 		servname = host;
286 	params.tls_servname = servname;
287 
288 	memset(&hints, 0, sizeof(hints));
289 	hints.ai_family = AF_UNSPEC;
290 	hints.ai_socktype = SOCK_STREAM;
291 	error = getaddrinfo(host, port, &hints, &res0);
292 	if (error)
293 		fatalx("%s: %s", host, gai_strerror(error));
294 	ai = res0;
295 }
296 
297 void
298 parse_message(FILE *ifp)
299 {
300 	char *line = NULL;
301 	size_t linesz = 0;
302 	ssize_t len;
303 
304 	if ((mail.fp = tmpfile()) == NULL)
305 		fatal("tmpfile");
306 
307 	for (;;) {
308 		if ((len = getline(&line, &linesz, ifp)) == -1) {
309 			if (feof(ifp))
310 				break;
311 			fatal("getline");
312 		}
313 
314 		if (len >= 2 && line[len - 2] == '\r' && line[len - 1] == '\n')
315 			line[--len - 1] = '\n';
316 
317 		if (fwrite(line, 1, len, mail.fp) != len)
318 			fatal("fwrite");
319 
320 		if (line[len - 1] != '\n' && fputc('\n', mail.fp) == EOF)
321 			fatal("fputc");
322 	}
323 
324 	fclose(ifp);
325 	rewind(mail.fp);
326 }
327 
328 void
329 resume(void)
330 {
331 	static int started = 0;
332 	char host[256];
333 	char serv[16];
334 
335 	if (done) {
336 		event_loopexit(NULL);
337 		return;
338 	}
339 
340 	if (ai == NULL)
341 		fatalx("no more host");
342 
343 	getnameinfo(ai->ai_addr, ai->ai_addr->sa_len,
344 	    host, sizeof(host), serv, sizeof(serv),
345 	    NI_NUMERICHOST | NI_NUMERICSERV);
346 	log_debug("trying host %s port %s...", host, serv);
347 
348 	params.dst = ai->ai_addr;
349 	if (smtp_connect(&params, NULL) == NULL)
350 		fatal("smtp_connect");
351 
352 	if (started == 0) {
353 		started = 1;
354 		event_loop(0);
355 	}
356 }
357 
358 void
359 log_trace(int lvl, const char *emsg, ...)
360 {
361 	va_list ap;
362 
363 	if (verbose > lvl) {
364 		va_start(ap, emsg);
365 		vlog(LOG_DEBUG, emsg, ap);
366 		va_end(ap);
367 	}
368 }
369 
370 void
371 smtp_require_tls(void *tag, struct smtp_client *proto)
372 {
373 	struct tls *tls;
374 
375 	tls = tls_client();
376 	if (tls == NULL)
377 		fatal("tls_client");
378 
379 	if (tls_configure(tls, tls_config) == -1)
380 		fatal("tls_configure");
381 
382 	smtp_set_tls(proto, tls);
383 }
384 
385 void
386 smtp_ready(void *tag, struct smtp_client *proto)
387 {
388 	log_debug("connection ready...");
389 
390 	if (done || noaction)
391 		smtp_quit(proto);
392 	else
393 		smtp_sendmail(proto, &mail);
394 }
395 
396 void
397 smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail)
398 {
399 	switch (failure) {
400 	case FAIL_INTERNAL:
401 		log_warnx("internal error: %s", detail);
402 		break;
403 	case FAIL_CONN:
404 		log_warnx("connection error: %s", detail);
405 		break;
406 	case FAIL_PROTO:
407 		log_warnx("protocol error: %s", detail);
408 		break;
409 	case FAIL_IMPL:
410 		log_warnx("missing feature: %s", detail);
411 		break;
412 	case FAIL_RESP:
413 		log_warnx("rejected by server: %s", detail);
414 		break;
415 	default:
416 		fatalx("unknown failure %d: %s", failure, detail);
417 	}
418 }
419 
420 void
421 smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status)
422 {
423 	log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status);
424 }
425 
426 void
427 smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail)
428 {
429 	int i;
430 
431 	log_debug("mail done...");
432 
433 	if (noaction)
434 		return;
435 
436 	for (i = 0; i < mail->rcptcount; i++)
437 		if (!mail->rcpt[i].done)
438 			return;
439 
440 	done = 1;
441 }
442 
443 void
444 smtp_closed(void *tag, struct smtp_client *proto)
445 {
446 	log_debug("connection closed...");
447 
448 	ai = ai->ai_next;
449 	if (noaction && ai == NULL)
450 		done = 1;
451 
452 	resume();
453 }
454