xref: /openbsd-src/usr.sbin/smtpd/mda.c (revision 2b0358df1d88d06ef4139321dd05bd5e05d91eaf)
1 /*	$OpenBSD: mda.c,v 1.13 2009/03/29 14:18:20 jacekm Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org>
5  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 #include <sys/types.h>
21 #include <sys/queue.h>
22 #include <sys/tree.h>
23 #include <sys/param.h>
24 #include <sys/socket.h>
25 
26 #include <event.h>
27 #include <pwd.h>
28 #include <signal.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
33 
34 #include "smtpd.h"
35 
36 __dead void	mda_shutdown(void);
37 void		mda_sig_handler(int, short, void *);
38 void		mda_dispatch_parent(int, short, void *);
39 void		mda_dispatch_queue(int, short, void *);
40 void		mda_dispatch_runner(int, short, void *);
41 void		mda_setup_events(struct smtpd *);
42 void		mda_disable_events(struct smtpd *);
43 void		mda_timeout(int, short, void *);
44 void		mda_remove_message(struct smtpd *, struct batch *, struct message *x);
45 
46 void
47 mda_sig_handler(int sig, short event, void *p)
48 {
49 	switch (sig) {
50 	case SIGINT:
51 	case SIGTERM:
52 		mda_shutdown();
53 		break;
54 	default:
55 		fatalx("mda_sig_handler: unexpected signal");
56 	}
57 }
58 
59 void
60 mda_dispatch_parent(int sig, short event, void *p)
61 {
62 	struct smtpd		*env = p;
63 	struct imsgbuf		*ibuf;
64 	struct imsg		 imsg;
65 	ssize_t			 n;
66 
67 	ibuf = env->sc_ibufs[PROC_PARENT];
68 	switch (event) {
69 	case EV_READ:
70 		if ((n = imsg_read(ibuf)) == -1)
71 			fatal("imsg_read_error");
72 		if (n == 0) {
73 			/* this pipe is dead, so remove the event handler */
74 			event_del(&ibuf->ev);
75 			event_loopexit(NULL);
76 			return;
77 		}
78 		break;
79 	case EV_WRITE:
80 		if (msgbuf_write(&ibuf->w) == -1)
81 			fatal("msgbuf_write");
82 		imsg_event_add(ibuf);
83 		return;
84 	default:
85 		fatalx("unknown event");
86 	}
87 
88 	for (;;) {
89 		if ((n = imsg_get(ibuf, &imsg)) == -1)
90 			fatal("mda_dispatch_parent: imsg_read error");
91 		if (n == 0)
92 			break;
93 
94 		switch (imsg.hdr.type) {
95 		case IMSG_MDA_MAILBOX_FILE: {
96 			struct session	*s;
97 			struct batch	*batchp;
98 			struct message	*messagep;
99 			enum message_status status;
100 
101 			batchp = (struct batch *)imsg.data;
102 			messagep = &batchp->message;
103 			status = messagep->status;
104 
105 			batchp = batch_by_id(env, batchp->id);
106 			if (batchp == NULL)
107 				fatalx("mda_dispatch_parent: internal inconsistency.");
108 			s = batchp->sessionp;
109 
110 			messagep = message_by_id(env, batchp, messagep->id);
111 			if (messagep == NULL)
112 				fatalx("mda_dispatch_parent: internal inconsistency.");
113 			messagep->status = status;
114 
115 			s->mboxfd = imsg_get_fd(ibuf, &imsg);
116 			if (s->mboxfd == -1) {
117 				mda_remove_message(env, batchp, messagep);
118 				break;
119 			}
120 
121 			batchp->message = *messagep;
122 			imsg_compose(env->sc_ibufs[PROC_PARENT],
123 			    IMSG_PARENT_MESSAGE_OPEN, 0, 0, -1, batchp,
124 			    sizeof(struct batch));
125 			break;
126 		}
127 
128 		case IMSG_MDA_MESSAGE_FILE: {
129 			struct session	*s;
130 			struct batch	*batchp;
131 			struct message	*messagep;
132 			enum message_status status;
133 			int (*store)(struct batch *, struct message *) = store_write_message;
134 
135 			batchp = (struct batch *)imsg.data;
136 			messagep = &batchp->message;
137 			status = messagep->status;
138 
139 			batchp = batch_by_id(env, batchp->id);
140 			if (batchp == NULL)
141 				fatalx("mda_dispatch_parent: internal inconsistency.");
142 			s = batchp->sessionp;
143 
144 			messagep = message_by_id(env, batchp, messagep->id);
145 			if (messagep == NULL)
146 				fatalx("mda_dispatch_parent: internal inconsistency.");
147 			messagep->status = status;
148 
149 			s->messagefd = imsg_get_fd(ibuf, &imsg);
150 			if (s->messagefd == -1) {
151 				if (s->mboxfd != -1)
152 					close(s->mboxfd);
153 				mda_remove_message(env, batchp, messagep);
154 				break;
155 			}
156 
157 			/* If batch is a daemon message, override the default store function */
158 			if (batchp->type & T_DAEMON_BATCH) {
159 				store = store_write_daemon;
160 			}
161 
162 			if (store_message(batchp, messagep, store)) {
163 				if (batchp->message.recipient.rule.r_action == A_MAILDIR)
164 					imsg_compose(env->sc_ibufs[PROC_PARENT],
165 					    IMSG_PARENT_MAILBOX_RENAME, 0, 0, -1, batchp,
166 					    sizeof(struct batch));
167 			}
168 
169 			if (s->mboxfd != -1)
170 				close(s->mboxfd);
171 			if (s->messagefd != -1)
172 				close(s->messagefd);
173 
174 			mda_remove_message(env, batchp, messagep);
175 			break;
176 		}
177 		default:
178 			log_warnx("mda_dispatch_parent: got imsg %d",
179 			    imsg.hdr.type);
180 			fatalx("mda_dispatch_parent: unexpected imsg");
181 		}
182 		imsg_free(&imsg);
183 	}
184 	imsg_event_add(ibuf);
185 }
186 
187 void
188 mda_dispatch_queue(int sig, short event, void *p)
189 {
190 	struct smtpd		*env = p;
191 	struct imsgbuf		*ibuf;
192 	struct imsg		 imsg;
193 	ssize_t			 n;
194 
195 	ibuf = env->sc_ibufs[PROC_QUEUE];
196 	switch (event) {
197 	case EV_READ:
198 		if ((n = imsg_read(ibuf)) == -1)
199 			fatal("imsg_read_error");
200 		if (n == 0) {
201 			/* this pipe is dead, so remove the event handler */
202 			event_del(&ibuf->ev);
203 			event_loopexit(NULL);
204 			return;
205 		}
206 		break;
207 	case EV_WRITE:
208 		if (msgbuf_write(&ibuf->w) == -1)
209 			fatal("msgbuf_write");
210 		imsg_event_add(ibuf);
211 		return;
212 	default:
213 		fatalx("unknown event");
214 	}
215 
216 	for (;;) {
217 		if ((n = imsg_get(ibuf, &imsg)) == -1)
218 			fatal("parent_dispatch_queue: imsg_read error");
219 		if (n == 0)
220 			break;
221 
222 		switch (imsg.hdr.type) {
223 		default:
224 			log_warnx("mda_dispatch_queue: got imsg %d",
225 			    imsg.hdr.type);
226 			fatalx("mda_dispatch_queue: unexpected imsg");
227 		}
228 		imsg_free(&imsg);
229 	}
230 	imsg_event_add(ibuf);
231 }
232 
233 void
234 mda_dispatch_runner(int sig, short event, void *p)
235 {
236 	struct smtpd		*env = p;
237 	struct imsgbuf		*ibuf;
238 	struct imsg		 imsg;
239 	ssize_t			 n;
240 
241 	ibuf = env->sc_ibufs[PROC_RUNNER];
242 	switch (event) {
243 	case EV_READ:
244 		if ((n = imsg_read(ibuf)) == -1)
245 			fatal("imsg_read_error");
246 		if (n == 0) {
247 			/* this pipe is dead, so remove the event handler */
248 			event_del(&ibuf->ev);
249 			event_loopexit(NULL);
250 			return;
251 		}
252 		break;
253 	case EV_WRITE:
254 		if (msgbuf_write(&ibuf->w) == -1)
255 			fatal("msgbuf_write");
256 		imsg_event_add(ibuf);
257 		return;
258 	default:
259 		fatalx("unknown event");
260 	}
261 
262 	for (;;) {
263 		if ((n = imsg_get(ibuf, &imsg)) == -1)
264 			fatal("parent_dispatch_runner: imsg_read error");
265 		if (n == 0)
266 			break;
267 
268 		switch (imsg.hdr.type) {
269 		case IMSG_BATCH_CREATE: {
270 			struct session *s;
271 			struct batch *batchp;
272 
273 			/* create a client session */
274 			if ((s = calloc(1, sizeof(*s))) == NULL)
275 				fatal(NULL);
276 			s->s_state = S_INIT;
277 			s->s_env = env;
278 			s->s_id = queue_generate_id();
279 			SPLAY_INSERT(sessiontree, &s->s_env->sc_sessions, s);
280 
281 			/* create the batch for this session */
282 			batchp = calloc(1, sizeof (struct batch));
283 			if (batchp == NULL)
284 				fatal("mda_dispatch_runner: calloc");
285 
286 			*batchp = *(struct batch *)imsg.data;
287 			batchp->session_id = s->s_id;
288 			batchp->env = env;
289 			batchp->flags = 0;
290 			batchp->sessionp = s;
291 
292 			s->batch = batchp;
293 
294 			TAILQ_INIT(&batchp->messages);
295 			SPLAY_INSERT(batchtree, &env->batch_queue, batchp);
296 			break;
297 		}
298 
299 		case IMSG_BATCH_APPEND: {
300 			struct batch	*batchp;
301 			struct message	*messagep;
302 
303 			messagep = calloc(1, sizeof (struct message));
304 			if (messagep == NULL)
305 				fatal("mda_dispatch_runner: calloc");
306 
307 			*messagep = *(struct message *)imsg.data;
308 
309 			batchp = batch_by_id(env, messagep->batch_id);
310 			if (batchp == NULL)
311 				fatalx("mda_dispatch_runner: internal inconsistency.");
312 
313 			batchp->session_ss = messagep->session_ss;
314 			strlcpy(batchp->session_hostname,
315 			    messagep->session_hostname,
316 			    sizeof(batchp->session_hostname));
317 			strlcpy(batchp->session_helo, messagep->session_helo,
318 			    sizeof(batchp->session_helo));
319 
320  			TAILQ_INSERT_TAIL(&batchp->messages, messagep, entry);
321 			break;
322 		}
323 
324 		case IMSG_BATCH_CLOSE: {
325 			struct batch		*batchp;
326 			struct session		*s;
327 			struct batch	lookup;
328 			struct message	*messagep;
329 
330 			batchp = (struct batch *)imsg.data;
331 			batchp = batch_by_id(env, batchp->id);
332 			if (batchp == NULL)
333 				fatalx("mda_dispatch_runner: internal inconsistency.");
334 
335 			batchp->flags |= F_BATCH_COMPLETE;
336 			s = batchp->sessionp;
337 
338 			lookup = *batchp;
339 			TAILQ_FOREACH(messagep, &batchp->messages, entry) {
340 				lookup.message = *messagep;
341 				imsg_compose(env->sc_ibufs[PROC_PARENT],
342 				    IMSG_PARENT_MAILBOX_OPEN, 0, 0, -1, &lookup,
343 				    sizeof(struct batch));
344 			}
345 			break;
346 		}
347 		default:
348 			log_warnx("mda_dispatch_runner: got imsg %d",
349 			    imsg.hdr.type);
350 			fatalx("mda_dispatch_runner: unexpected imsg");
351 		}
352 		imsg_free(&imsg);
353 	}
354 	imsg_event_add(ibuf);
355 }
356 
357 
358 void
359 mda_shutdown(void)
360 {
361 	log_info("mail delivery agent exiting");
362 	_exit(0);
363 }
364 
365 void
366 mda_setup_events(struct smtpd *env)
367 {
368 	struct timeval	 tv;
369 
370 	evtimer_set(&env->sc_ev, mda_timeout, env);
371 	tv.tv_sec = 3;
372 	tv.tv_usec = 0;
373 	evtimer_add(&env->sc_ev, &tv);
374 }
375 
376 void
377 mda_disable_events(struct smtpd *env)
378 {
379 	evtimer_del(&env->sc_ev);
380 }
381 
382 void
383 mda_timeout(int fd, short event, void *p)
384 {
385 	struct smtpd		*env = p;
386 	struct timeval		 tv;
387 
388 	tv.tv_sec = 3;
389 	tv.tv_usec = 0;
390 	evtimer_add(&env->sc_ev, &tv);
391 }
392 
393 pid_t
394 mda(struct smtpd *env)
395 {
396 	pid_t		 pid;
397 	struct passwd	*pw;
398 
399 	struct event	 ev_sigint;
400 	struct event	 ev_sigterm;
401 
402 	struct peer peers[] = {
403 		{ PROC_PARENT,	mda_dispatch_parent },
404 		{ PROC_QUEUE,	mda_dispatch_queue },
405 		{ PROC_RUNNER,	mda_dispatch_runner }
406 	};
407 
408 	switch (pid = fork()) {
409 	case -1:
410 		fatal("mda: cannot fork");
411 	case 0:
412 		break;
413 	default:
414 		return (pid);
415 	}
416 
417 	purge_config(env, PURGE_EVERYTHING);
418 
419 	pw = env->sc_pw;
420 
421 #ifndef DEBUG
422 	if (chroot(pw->pw_dir) == -1)
423 		fatal("mda: chroot");
424 	if (chdir("/") == -1)
425 		fatal("mda: chdir(\"/\")");
426 #else
427 #warning disabling privilege revocation and chroot in DEBUG MODE
428 #endif
429 
430 	setproctitle("mail delivery agent");
431 	smtpd_process = PROC_MDA;
432 
433 #ifndef DEBUG
434 	if (setgroups(1, &pw->pw_gid) ||
435 	    setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) ||
436 	    setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid))
437 		fatal("mda: cannot drop privileges");
438 #endif
439 
440 	SPLAY_INIT(&env->batch_queue);
441 
442 	event_init();
443 
444 	signal_set(&ev_sigint, SIGINT, mda_sig_handler, env);
445 	signal_set(&ev_sigterm, SIGTERM, mda_sig_handler, env);
446 	signal_add(&ev_sigint, NULL);
447 	signal_add(&ev_sigterm, NULL);
448 	signal(SIGPIPE, SIG_IGN);
449 	signal(SIGHUP, SIG_IGN);
450 
451 	config_pipes(env, peers, 3);
452 	config_peers(env, peers, 3);
453 
454 	mda_setup_events(env);
455 	event_dispatch();
456 	mda_shutdown();
457 
458 	return (0);
459 }
460 
461 void
462 mda_remove_message(struct smtpd *env, struct batch *batchp, struct message *messagep)
463 {
464 	imsg_compose(env->sc_ibufs[PROC_QUEUE], IMSG_QUEUE_MESSAGE_UPDATE, 0, 0,
465 	    -1, messagep, sizeof (struct message));
466 
467 	if ((batchp->message.status & S_MESSAGE_TEMPFAILURE) == 0 &&
468 	    (batchp->message.status & S_MESSAGE_PERMFAILURE) == 0) {
469 		log_info("%s: to=<%s@%s>, delay=%d, stat=Sent",
470 		    messagep->message_uid,
471 		    messagep->recipient.user,
472 		    messagep->recipient.domain,
473 		    time(NULL) - messagep->creation);
474 	}
475 
476 	SPLAY_REMOVE(sessiontree, &env->sc_sessions, batchp->sessionp);
477 	free(batchp->sessionp);
478 
479 	queue_remove_batch_message(env, batchp, messagep);
480 }
481