xref: /openbsd-src/usr.sbin/smtpd/smtp.c (revision 50b7afb2c2c0993b0894d4e34bf857cb13ed9c80)
1 /*	$OpenBSD: smtp.c,v 1.139 2014/07/08 14:38:17 eric Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Gilles Chehade <gilles@poolp.org>
5  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
6  * Copyright (c) 2009 Jacek Masiulaniec <jacekm@dobremiasto.net>
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19  */
20 
21 #include <sys/types.h>
22 #include <sys/queue.h>
23 #include <sys/tree.h>
24 #include <sys/socket.h>
25 
26 #include <err.h>
27 #include <errno.h>
28 #include <event.h>
29 #include <imsg.h>
30 #include <netdb.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 
38 #include <openssl/ssl.h>
39 
40 #include "smtpd.h"
41 #include "log.h"
42 #include "ssl.h"
43 
44 static void smtp_setup_events(void);
45 static void smtp_pause(void);
46 static void smtp_resume(void);
47 static void smtp_accept(int, short, void *);
48 static int smtp_enqueue(uid_t *);
49 static int smtp_can_accept(void);
50 static void smtp_setup_listeners(void);
51 
52 #define	SMTP_FD_RESERVE	5
53 static size_t	sessions;
54 
55 void
56 smtp_imsg(struct mproc *p, struct imsg *imsg)
57 {
58 	if (p->proc == PROC_LKA) {
59 		switch (imsg->hdr.type) {
60 		case IMSG_SMTP_DNS_PTR:
61 		case IMSG_SMTP_EXPAND_RCPT:
62 		case IMSG_SMTP_LOOKUP_HELO:
63 		case IMSG_SMTP_AUTHENTICATE:
64 		case IMSG_SMTP_SSL_INIT:
65 		case IMSG_SMTP_SSL_VERIFY:
66 			smtp_session_imsg(p, imsg);
67 			return;
68 		}
69 	}
70 
71 	if (p->proc == PROC_QUEUE) {
72 		switch (imsg->hdr.type) {
73 		case IMSG_SMTP_MESSAGE_COMMIT:
74 		case IMSG_SMTP_MESSAGE_CREATE:
75 		case IMSG_SMTP_MESSAGE_OPEN:
76 		case IMSG_QUEUE_ENVELOPE_SUBMIT:
77 		case IMSG_QUEUE_ENVELOPE_COMMIT:
78 			smtp_session_imsg(p, imsg);
79 			return;
80 
81 		case IMSG_QUEUE_SMTP_SESSION:
82 			m_compose(p, IMSG_QUEUE_SMTP_SESSION, 0, 0,
83 			    smtp_enqueue(NULL), imsg->data,
84 			    imsg->hdr.len - sizeof imsg->hdr);
85 			return;
86 		}
87 	}
88 
89 	if (p->proc == PROC_CONTROL) {
90 		switch (imsg->hdr.type) {
91 		case IMSG_CTL_SMTP_SESSION:
92 			m_compose(p, IMSG_CTL_SMTP_SESSION, imsg->hdr.peerid, 0,
93 			    smtp_enqueue(imsg->data), NULL, 0);
94 			return;
95 
96 		case IMSG_CTL_PAUSE_SMTP:
97 			log_debug("debug: smtp: pausing listening sockets");
98 			smtp_pause();
99 			env->sc_flags |= SMTPD_SMTP_PAUSED;
100 			return;
101 
102 		case IMSG_CTL_RESUME_SMTP:
103 			log_debug("debug: smtp: resuming listening sockets");
104 			env->sc_flags &= ~SMTPD_SMTP_PAUSED;
105 			smtp_resume();
106 			return;
107 		}
108 	}
109 
110 	errx(1, "smtp_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type));
111 }
112 
113 void
114 smtp_postfork(void)
115 {
116 	smtp_setup_listeners();
117 }
118 
119 void
120 smtp_postprivdrop(void)
121 {
122 }
123 
124 void
125 smtp_configure(void)
126 {
127 	smtp_setup_events();
128 }
129 
130 static void
131 smtp_setup_listeners(void)
132 {
133 	struct listener	       *l;
134 	int			opt;
135 
136 	TAILQ_FOREACH(l, env->sc_listeners, entry) {
137 		if ((l->fd = socket(l->ss.ss_family, SOCK_STREAM, 0)) == -1) {
138 			if (errno == EAFNOSUPPORT) {
139 				log_warn("smtpd: socket");
140 				continue;
141 			}
142 			fatal("smtpd: socket");
143 		}
144 		opt = 1;
145 		if (setsockopt(l->fd, SOL_SOCKET, SO_REUSEADDR, &opt,
146 			sizeof(opt)) < 0)
147 			fatal("smtpd: setsockopt");
148 		if (bind(l->fd, (struct sockaddr *)&l->ss, l->ss.ss_len) == -1)
149 			fatal("smtpd: bind");
150 	}
151 }
152 
153 static void
154 smtp_setup_events(void)
155 {
156 	struct listener *l;
157 	struct pki	*pki;
158 	SSL_CTX		*ssl_ctx;
159 	void		*iter;
160 	const char	*k;
161 
162 	TAILQ_FOREACH(l, env->sc_listeners, entry) {
163 		log_debug("debug: smtp: listen on %s port %d flags 0x%01x"
164 		    " pki \"%s\"", ss_to_text(&l->ss), ntohs(l->port),
165 		    l->flags, l->pki_name);
166 
167 		session_socket_blockmode(l->fd, BM_NONBLOCK);
168 		if (listen(l->fd, SMTPD_BACKLOG) == -1)
169 			fatal("listen");
170 		event_set(&l->ev, l->fd, EV_READ|EV_PERSIST, smtp_accept, l);
171 
172 		if (!(env->sc_flags & SMTPD_SMTP_PAUSED))
173 			event_add(&l->ev, NULL);
174 	}
175 
176 	iter = NULL;
177 	while (dict_iter(env->sc_pki_dict, &iter, &k, (void **)&pki)) {
178 		if (! ssl_setup((SSL_CTX **)&ssl_ctx, pki))
179 			fatal("smtp_setup_events: ssl_setup failure");
180 		dict_xset(env->sc_ssl_dict, k, ssl_ctx);
181 	}
182 
183 	purge_config(PURGE_PKI_KEYS);
184 
185 	log_debug("debug: smtp: will accept at most %d clients",
186 	    (getdtablesize() - getdtablecount())/2 - SMTP_FD_RESERVE);
187 }
188 
189 static void
190 smtp_pause(void)
191 {
192 	struct listener *l;
193 
194 	if (env->sc_flags & (SMTPD_SMTP_DISABLED|SMTPD_SMTP_PAUSED))
195 		return;
196 
197 	TAILQ_FOREACH(l, env->sc_listeners, entry)
198 		event_del(&l->ev);
199 }
200 
201 static void
202 smtp_resume(void)
203 {
204 	struct listener *l;
205 
206 	if (env->sc_flags & (SMTPD_SMTP_DISABLED|SMTPD_SMTP_PAUSED))
207 		return;
208 
209 	TAILQ_FOREACH(l, env->sc_listeners, entry)
210 		event_add(&l->ev, NULL);
211 }
212 
213 static int
214 smtp_enqueue(uid_t *euid)
215 {
216 	static struct listener	 local, *listener = NULL;
217 	char			 buf[SMTPD_MAXHOSTNAMELEN], *hostname;
218 	int			 fd[2];
219 
220 	if (listener == NULL) {
221 		listener = &local;
222 		(void)strlcpy(listener->tag, "local", sizeof(listener->tag));
223 		listener->ss.ss_family = AF_LOCAL;
224 		listener->ss.ss_len = sizeof(struct sockaddr *);
225 		(void)strlcpy(listener->hostname, "localhost",
226 		    sizeof(listener->hostname));
227 	}
228 
229 	/*
230 	 * Some enqueue requests buffered in IMSG may still arrive even after
231 	 * call to smtp_pause() because enqueue listener is not a real socket
232 	 * and thus cannot be paused properly.
233 	 */
234 	if (env->sc_flags & SMTPD_SMTP_PAUSED)
235 		return (-1);
236 
237 	/* XXX dont' fatal here */
238 	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fd))
239 		fatal("socketpair");
240 
241 	hostname = "localhost";
242 	if (euid) {
243 		(void)snprintf(buf, sizeof(buf), "%d@localhost", *euid);
244 		hostname = buf;
245 	}
246 
247 	if ((smtp_session(listener, fd[0], &listener->ss, hostname)) == -1) {
248 		close(fd[0]);
249 		close(fd[1]);
250 		return (-1);
251 	}
252 
253 	sessions++;
254 	stat_increment("smtp.session", 1);
255 	stat_increment("smtp.session.local", 1);
256 
257 	return (fd[1]);
258 }
259 
260 static void
261 smtp_accept(int fd, short event, void *p)
262 {
263 	struct listener		*listener = p;
264 	struct sockaddr_storage	 ss;
265 	socklen_t		 len;
266 	int			 sock;
267 
268 	if (env->sc_flags & SMTPD_SMTP_PAUSED)
269 		fatalx("smtp_session: unexpected client");
270 
271 	if (! smtp_can_accept()) {
272 		log_warnx("warn: Disabling incoming SMTP connections: "
273 		    "Client limit reached");
274 		goto pause;
275 	}
276 
277 	len = sizeof(ss);
278 	if ((sock = accept(fd, (struct sockaddr *)&ss, &len)) == -1) {
279 		if (errno == ENFILE || errno == EMFILE) {
280 			log_warn("warn: Disabling incoming SMTP connections");
281 			goto pause;
282 		}
283 		if (errno == EINTR || errno == EWOULDBLOCK ||
284 		    errno == ECONNABORTED)
285 			return;
286 		fatal("smtp_accept");
287 	}
288 
289 	if (smtp_session(listener, sock, &ss, NULL) == -1) {
290 		log_warn("warn: Failed to create SMTP session");
291 		close(sock);
292 		return;
293 	}
294 	io_set_blocking(sock, 0);
295 
296 	sessions++;
297 	stat_increment("smtp.session", 1);
298 	if (listener->ss.ss_family == AF_LOCAL)
299 		stat_increment("smtp.session.local", 1);
300 	if (listener->ss.ss_family == AF_INET)
301 		stat_increment("smtp.session.inet4", 1);
302 	if (listener->ss.ss_family == AF_INET6)
303 		stat_increment("smtp.session.inet6", 1);
304 	return;
305 
306 pause:
307 	smtp_pause();
308 	env->sc_flags |= SMTPD_SMTP_DISABLED;
309 	return;
310 }
311 
312 static int
313 smtp_can_accept(void)
314 {
315 	size_t max;
316 
317 	max = (getdtablesize() - getdtablecount()) / 2 - SMTP_FD_RESERVE;
318 
319 	return (sessions < max);
320 }
321 
322 void
323 smtp_collect(void)
324 {
325 	sessions--;
326 	stat_decrement("smtp.session", 1);
327 
328 	if (!smtp_can_accept())
329 		return;
330 
331 	if (env->sc_flags & SMTPD_SMTP_DISABLED) {
332 		log_warnx("warn: smtp: "
333 		    "fd exhaustion over, re-enabling incoming connections");
334 		env->sc_flags &= ~SMTPD_SMTP_DISABLED;
335 		smtp_resume();
336 	}
337 }
338