xref: /openbsd-src/usr.sbin/smtpd/smtpc.c (revision d1df930ffab53da22f3324c32bed7ac5709915e6)
1 /*	$OpenBSD: smtpc.c,v 1.4 2018/09/20 11:42:28 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 <netdb.h>
24 #include <pwd.h>
25 #include <resolv.h>
26 #include <stdarg.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <syslog.h>
31 #include <unistd.h>
32 
33 #include "smtp.h"
34 #include "log.h"
35 
36 void ssl_init(void);
37 void *ssl_mta_init(void *, char *, off_t, const char *);
38 
39 static void parse_server(char *);
40 static void parse_message(FILE *);
41 static void resume(void);
42 
43 static int verbose = 1;
44 static int done = 0;
45 static int noaction = 0;
46 static struct addrinfo *res0, *ai;
47 static struct smtp_params params;
48 static struct smtp_mail mail;
49 
50 static void
51 usage(void)
52 {
53 	extern char *__progname;
54 
55 	fprintf(stderr,
56 	    "usage: %s [-Chnv] [-F from] [-H helo] [-s server] rcpt ...\n",
57 	    __progname);
58 	exit(1);
59 }
60 
61 int
62 main(int argc, char **argv)
63 {
64 	char hostname[256];
65 	int ch, i;
66 	char *server = "localhost";
67 	struct passwd *pw;
68 
69 	log_init(1, 0);
70 
71 	if (gethostname(hostname, sizeof(hostname)) == -1)
72 		fatal("gethostname");
73 
74 	if ((pw = getpwuid(getuid())) == NULL)
75 		fatal("getpwuid");
76 
77 	memset(&params, 0, sizeof(params));
78 
79 	params.linemax = 16392;
80 	params.ibufmax = 65536;
81 	params.obufmax = 65536;
82 	params.timeout = 100000;
83 	params.helo = hostname;
84 
85 	params.tls_verify = 1;
86 
87 	memset(&mail, 0, sizeof(mail));
88 	mail.from = pw->pw_name;
89 
90 	while ((ch = getopt(argc, argv, "CF:H:hns:v")) != -1) {
91 		switch (ch) {
92 		case 'C':
93 			params.tls_verify = 0;
94 			break;
95 		case 'F':
96 			mail.from = optarg;
97 			break;
98 		case 'H':
99 			params.helo = optarg;
100 			break;
101 		case 'h':
102 			usage();
103 			break;
104 		case 'n':
105 			noaction = 1;
106 			break;
107 		case 's':
108 			server = optarg;
109 			break;
110 		case 'v':
111 			verbose++;
112 			break;
113 		default:
114 			usage();
115 		}
116 	}
117 
118 	log_setverbose(verbose);
119 
120 	argc -= optind;
121 	argv += optind;
122 
123 	if (argc) {
124 		mail.rcpt = calloc(argc, sizeof(*mail.rcpt));
125 		if (mail.rcpt == NULL)
126 			fatal("calloc");
127 		for (i = 0; i < argc; i++)
128 			mail.rcpt[i].to = argv[i];
129 		mail.rcptcount = argc;
130 	}
131 
132 	ssl_init();
133 	event_init();
134 
135 	if (pledge("stdio inet dns tmppath", NULL) == -1)
136 		fatal("pledge");
137 
138 	if (!noaction)
139 		parse_message(stdin);
140 
141 	if (pledge("stdio inet dns", NULL) == -1)
142 		fatal("pledge");
143 
144 	parse_server(server);
145 
146 	if (pledge("stdio inet", NULL) == -1)
147 		fatal("pledge");
148 
149 	resume();
150 
151 	log_debug("done...");
152 
153 	return 0;
154 }
155 
156 static void
157 parse_server(char *server)
158 {
159 	struct addrinfo hints;
160 	char *scheme, *creds, *host, *port, *p, *c;
161 	int error;
162 
163 	creds = NULL;
164 	host = NULL;
165 	port = NULL;
166 	scheme = server;
167 
168 	p = strstr(server, "://");
169 	if (p) {
170 		*p = '\0';
171 		p += 3;
172 		/* check for credentials */
173 		c = strchr(p, '@');
174 		if (c) {
175 			creds = p;
176 			*c = '\0';
177 			host = c + 1;
178 		} else
179 			host = p;
180 	} else {
181 		/* Assume a simple server name */
182 		scheme = "smtp";
183 		host = server;
184 	}
185 
186 	if (host[0] == '[') {
187 		/* IPV6 address? */
188 		p = strchr(host, ']');
189 		if (p) {
190 			if (p[1] == ':' || p[1] == '\0') {
191 				*p++ = '\0';	/* remove ']' */
192 				host++;		/* skip '[' */
193 				if (*p == ':')
194 					port = p + 1;
195 			}
196 		}
197 	}
198 	else {
199 		port = strchr(host, ':');
200 		if (port)
201 			*port++ = '\0';
202 	}
203 
204 	if (port && port[0] == '\0')
205 		port = NULL;
206 
207 	if (creds) {
208 		p = strchr(creds, ':');
209 		if (p == NULL)
210 			fatalx("invalid credentials");
211 		*p = '\0';
212 
213 		params.auth_user = creds;
214 		params.auth_pass = p + 1;
215 	}
216 	params.tls_req = TLS_YES;
217 
218 	if (!strcmp(scheme, "lmtp")) {
219 		params.lmtp = 1;
220 	}
221 	else if (!strcmp(scheme, "lmtp+tls")) {
222 		params.lmtp = 1;
223 		params.tls_req = TLS_FORCE;
224 	}
225 	else if (!strcmp(scheme, "lmtp+notls")) {
226 		params.lmtp = 1;
227 		params.tls_req = TLS_NO;
228 	}
229 	else if (!strcmp(scheme, "smtps")) {
230 		params.tls_req = TLS_SMTPS;
231 		if (port == NULL)
232 			port = "submission";
233 	}
234 	else if (!strcmp(scheme, "smtp")) {
235 	}
236 	else if (!strcmp(scheme, "smtp+tls")) {
237 		params.tls_req = TLS_FORCE;
238 	}
239 	else if (!strcmp(scheme, "smtp+notls")) {
240 		params.tls_req = TLS_NO;
241 	}
242 	else
243 		fatalx("invalid url scheme %s", scheme);
244 
245 	if (port == NULL)
246 		port = "smtp";
247 
248 	if (params.tls_req != TLS_NO) {
249 		params.tls_ctx = ssl_mta_init(NULL, NULL, 0, NULL);
250 		if (params.tls_ctx == NULL)
251 			fatal("ssl_mta_init");
252 	}
253 
254 	memset(&hints, 0, sizeof(hints));
255 	hints.ai_family = AF_UNSPEC;
256 	hints.ai_socktype = SOCK_STREAM;
257 	error = getaddrinfo(host, port, &hints, &res0);
258 	if (error)
259 		fatalx("%s: %s", host, gai_strerror(error));
260 	ai = res0;
261 }
262 
263 void
264 parse_message(FILE *ifp)
265 {
266 	char sfn[] = "/tmp/smtp.XXXXXXXXXX";
267 	char *line = NULL;
268 	size_t linesz = 0;
269 	ssize_t len;
270 	int fd;
271 
272 	if ((fd = mkstemp(sfn)) == -1)
273 		fatal("mkstemp");
274 	unlink(sfn);
275 
276 	mail.fp = fdopen(fd, "w+");
277 	if ((mail.fp) == NULL)
278 		fatal("fdopen");
279 
280 	for (;;) {
281 		if ((len = getline(&line, &linesz, ifp)) == -1) {
282 			if (feof(ifp))
283 				break;
284 			fatal("getline");
285 		}
286 		if (fwrite(line, 1, len, mail.fp) != len)
287 			fatal("frwite");
288 	}
289 
290 	fclose(ifp);
291 	rewind(mail.fp);
292 }
293 
294 void
295 resume(void)
296 {
297 	static int started = 0;
298 	char host[256];
299 	char serv[16];
300 
301 	if (done) {
302 		event_loopexit(NULL);
303 		return;
304 	}
305 
306 	if (ai == NULL)
307 		fatalx("no more host");
308 
309 	getnameinfo(ai->ai_addr, ai->ai_addr->sa_len,
310 	    host, sizeof(host), serv, sizeof(serv),
311 	    NI_NUMERICHOST | NI_NUMERICSERV);
312 	log_debug("trying host %s port %s...", host, serv);
313 
314 	params.dst = ai->ai_addr;
315 	if (smtp_connect(&params, NULL) == NULL)
316 		fatal("smtp_connect");
317 
318 	if (started == 0) {
319 		started = 1;
320 		event_loop(0);
321 	}
322 }
323 
324 void
325 log_trace(int lvl, const char *emsg, ...)
326 {
327 	va_list ap;
328 
329 	if (verbose > lvl) {
330 		va_start(ap, emsg);
331 		vlog(LOG_DEBUG, emsg, ap);
332 		va_end(ap);
333 	}
334 }
335 
336 void
337 smtp_verify_server_cert(void *tag, struct smtp_client *proto, void *ctx)
338 {
339 	log_debug("validating server certificate...");
340 
341 	/* Not implemented for now. */
342 	smtp_cert_verified(proto, CERT_UNKNOWN);
343 }
344 
345 void
346 smtp_ready(void *tag, struct smtp_client *proto)
347 {
348 	log_debug("connection ready...");
349 
350 	if (done || noaction)
351 		smtp_quit(proto);
352 	else
353 		smtp_sendmail(proto, &mail);
354 }
355 
356 void
357 smtp_failed(void *tag, struct smtp_client *proto, int failure, const char *detail)
358 {
359 	switch (failure) {
360 	case FAIL_INTERNAL:
361 		log_warnx("internal error: %s", detail);
362 		break;
363 	case FAIL_CONN:
364 		log_warnx("connection error: %s", detail);
365 		break;
366 	case FAIL_PROTO:
367 		log_warnx("protocol error: %s", detail);
368 		break;
369 	case FAIL_IMPL:
370 		log_warnx("missing feature: %s", detail);
371 		break;
372 	case FAIL_RESP:
373 		log_warnx("rejected by server: %s", detail);
374 		break;
375 	default:
376 		fatalx("unknown failure %d: %s", failure, detail);
377 	}
378 }
379 
380 void
381 smtp_status(void *tag, struct smtp_client *proto, struct smtp_status *status)
382 {
383 	log_info("%s: %s: %s", status->rcpt->to, status->cmd, status->status);
384 }
385 
386 void
387 smtp_done(void *tag, struct smtp_client *proto, struct smtp_mail *mail)
388 {
389 	int i;
390 
391 	log_debug("mail done...");
392 
393 	if (noaction)
394 		return;
395 
396 	for (i = 0; i < mail->rcptcount; i++)
397 		if (!mail->rcpt[i].done)
398 			return;
399 
400 	done = 1;
401 }
402 
403 void
404 smtp_closed(void *tag, struct smtp_client *proto)
405 {
406 	log_debug("connection closed...");
407 
408 	ai = ai->ai_next;
409 	if (noaction && ai == NULL)
410 		done = 1;
411 
412 	resume();
413 }
414