1 /* $OpenBSD: mda.c,v 1.67 2012/01/13 14:01:57 eric Exp $ */ 2 3 /* 4 * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.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/param.h> 25 #include <sys/socket.h> 26 27 #include <err.h> 28 #include <event.h> 29 #include <imsg.h> 30 #include <inttypes.h> 31 #include <pwd.h> 32 #include <signal.h> 33 #include <stdio.h> 34 #include <stdlib.h> 35 #include <string.h> 36 #include <time.h> 37 #include <unistd.h> 38 #include <vis.h> 39 40 #include "smtpd.h" 41 #include "log.h" 42 43 static void mda_imsg(struct imsgev *, struct imsg *); 44 static void mda_shutdown(void); 45 static void mda_sig_handler(int, short, void *); 46 static void mda_store(struct mda_session *); 47 static void mda_store_event(int, short, void *); 48 static struct mda_session *mda_lookup(u_int32_t); 49 50 u_int32_t mda_id; 51 52 static void 53 mda_imsg(struct imsgev *iev, struct imsg *imsg) 54 { 55 char output[128], *error, *parent_error; 56 struct deliver deliver; 57 struct mda_session *s; 58 struct delivery_mda *d_mda; 59 struct mailaddr *maddr; 60 struct envelope *ep; 61 u_int16_t msg; 62 63 log_imsg(PROC_MDA, iev->proc, imsg); 64 65 if (iev->proc == PROC_QUEUE) { 66 switch (imsg->hdr.type) { 67 case IMSG_MDA_SESS_NEW: 68 /* make new session based on provided args */ 69 s = calloc(1, sizeof *s); 70 if (s == NULL) 71 fatal(NULL); 72 msgbuf_init(&s->w); 73 s->msg = *(struct envelope *)imsg->data; 74 s->id = mda_id++; 75 s->datafp = fdopen(imsg->fd, "r"); 76 if (s->datafp == NULL) 77 fatalx("mda: fdopen"); 78 LIST_INSERT_HEAD(&env->mda_sessions, s, entry); 79 80 /* request parent to fork a helper process */ 81 ep = &s->msg; 82 d_mda = &s->msg.agent.mda; 83 switch (d_mda->method) { 84 case A_MDA: 85 deliver.mode = A_MDA; 86 strlcpy(deliver.user, d_mda->as_user, 87 sizeof (deliver.user)); 88 strlcpy(deliver.to, d_mda->to.buffer, 89 sizeof deliver.to); 90 break; 91 92 case A_MBOX: 93 deliver.mode = A_MBOX; 94 strlcpy(deliver.user, "root", 95 sizeof (deliver.user)); 96 strlcpy(deliver.to, d_mda->to.user, 97 sizeof (deliver.to)); 98 snprintf(deliver.from, sizeof(deliver.from), 99 "%s@%s", ep->sender.user, 100 ep->sender.domain); 101 break; 102 103 case A_MAILDIR: 104 deliver.mode = A_MAILDIR; 105 strlcpy(deliver.user, d_mda->as_user, 106 sizeof deliver.user); 107 strlcpy(deliver.to, d_mda->to.buffer, 108 sizeof deliver.to); 109 break; 110 111 case A_FILENAME: 112 deliver.mode = A_FILENAME; 113 strlcpy(deliver.user, d_mda->as_user, 114 sizeof deliver.user); 115 strlcpy(deliver.to, d_mda->to.buffer, 116 sizeof deliver.to); 117 break; 118 119 default: 120 log_debug("mda: unknown rule action: %d", d_mda->method); 121 fatalx("mda: unknown rule action"); 122 } 123 124 imsg_compose_event(env->sc_ievs[PROC_PARENT], 125 IMSG_PARENT_FORK_MDA, s->id, 0, -1, &deliver, 126 sizeof deliver); 127 return; 128 } 129 } 130 131 if (iev->proc == PROC_PARENT) { 132 switch (imsg->hdr.type) { 133 case IMSG_PARENT_FORK_MDA: 134 s = mda_lookup(imsg->hdr.peerid); 135 136 if (imsg->fd < 0) 137 fatalx("mda: fd pass fail"); 138 s->w.fd = imsg->fd; 139 140 mda_store(s); 141 return; 142 143 case IMSG_MDA_DONE: 144 s = mda_lookup(imsg->hdr.peerid); 145 146 /* 147 * Grab last line of mda stdout/stderr if available. 148 */ 149 output[0] = '\0'; 150 if (imsg->fd != -1) { 151 char *ln, *buf; 152 FILE *fp; 153 size_t len; 154 155 buf = NULL; 156 if (lseek(imsg->fd, 0, SEEK_SET) < 0) 157 fatalx("lseek"); 158 fp = fdopen(imsg->fd, "r"); 159 if (fp == NULL) 160 fatal("mda: fdopen"); 161 while ((ln = fgetln(fp, &len))) { 162 if (ln[len - 1] == '\n') 163 ln[len - 1] = '\0'; 164 else { 165 buf = malloc(len + 1); 166 if (buf == NULL) 167 fatal(NULL); 168 memcpy(buf, ln, len); 169 buf[len] = '\0'; 170 ln = buf; 171 } 172 strlcpy(output, "\"", sizeof output); 173 strnvis(output + 1, ln, 174 sizeof(output) - 2, 175 VIS_SAFE | VIS_CSTYLE); 176 strlcat(output, "\"", sizeof output); 177 log_debug("mda_out: %s", output); 178 } 179 free(buf); 180 fclose(fp); 181 } 182 183 /* 184 * Choose between parent's description of error and 185 * child's output, the latter having preference over 186 * the former. 187 */ 188 error = NULL; 189 parent_error = imsg->data; 190 if (strcmp(parent_error, "exited okay") == 0) { 191 if (!feof(s->datafp) || s->w.queued) 192 error = "mda exited prematurely"; 193 } else { 194 if (output[0]) 195 error = output; 196 else 197 error = parent_error; 198 } 199 200 /* update queue entry */ 201 msg = IMSG_QUEUE_DELIVERY_OK; 202 if (error) { 203 msg = IMSG_QUEUE_DELIVERY_TEMPFAIL; 204 envelope_set_errormsg(&s->msg, "%s", error); 205 } 206 imsg_compose_event(env->sc_ievs[PROC_QUEUE], msg, 207 0, 0, -1, &s->msg, sizeof s->msg); 208 209 /* 210 * XXX: which struct path gets used for logging depends 211 * on whether lka did aliases or .forward processing; 212 * lka may need to be changed to present data in more 213 * unified way. 214 */ 215 if (s->msg.rule.r_action == A_MAILDIR || 216 s->msg.rule.r_action == A_MBOX) 217 maddr = &s->msg.dest; 218 else 219 maddr = &s->msg.rcpt; 220 221 /* log status */ 222 if (error && asprintf(&error, "Error (%s)", error) < 0) 223 fatal("mda: asprintf"); 224 log_info("%016" PRIx64 ": to=<%s@%s>, delay=%" PRId64 ", stat=%s", 225 s->msg.id, maddr->user, maddr->domain, 226 (int64_t) (time(NULL) - s->msg.creation), 227 error ? error : "Sent"); 228 free(error); 229 230 /* destroy session */ 231 LIST_REMOVE(s, entry); 232 if (s->w.fd != -1) 233 close(s->w.fd); 234 if (s->datafp) 235 fclose(s->datafp); 236 msgbuf_clear(&s->w); 237 event_del(&s->ev); 238 free(s); 239 240 /* update queue's session count */ 241 imsg_compose_event(env->sc_ievs[PROC_QUEUE], 242 IMSG_MDA_SESS_NEW, 0, 0, -1, NULL, 0); 243 return; 244 245 case IMSG_CTL_VERBOSE: 246 log_verbose(*(int *)imsg->data); 247 return; 248 } 249 } 250 251 errx(1, "mda_imsg: unexpected %s imsg", imsg_to_str(imsg->hdr.type)); 252 } 253 254 static void 255 mda_sig_handler(int sig, short event, void *p) 256 { 257 switch (sig) { 258 case SIGINT: 259 case SIGTERM: 260 mda_shutdown(); 261 break; 262 default: 263 fatalx("mda_sig_handler: unexpected signal"); 264 } 265 } 266 267 static void 268 mda_shutdown(void) 269 { 270 log_info("mail delivery agent exiting"); 271 _exit(0); 272 } 273 274 pid_t 275 mda(void) 276 { 277 pid_t pid; 278 struct passwd *pw; 279 280 struct event ev_sigint; 281 struct event ev_sigterm; 282 283 struct peer peers[] = { 284 { PROC_PARENT, imsg_dispatch }, 285 { PROC_QUEUE, imsg_dispatch } 286 }; 287 288 switch (pid = fork()) { 289 case -1: 290 fatal("mda: cannot fork"); 291 case 0: 292 break; 293 default: 294 return (pid); 295 } 296 297 purge_config(PURGE_EVERYTHING); 298 299 pw = env->sc_pw; 300 301 if (chroot(pw->pw_dir) == -1) 302 fatal("mda: chroot"); 303 if (chdir("/") == -1) 304 fatal("mda: chdir(\"/\")"); 305 306 smtpd_process = PROC_MDA; 307 setproctitle("%s", env->sc_title[smtpd_process]); 308 309 if (setgroups(1, &pw->pw_gid) || 310 setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || 311 setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) 312 fatal("mda: cannot drop privileges"); 313 314 LIST_INIT(&env->mda_sessions); 315 316 imsg_callback = mda_imsg; 317 event_init(); 318 319 signal_set(&ev_sigint, SIGINT, mda_sig_handler, NULL); 320 signal_set(&ev_sigterm, SIGTERM, mda_sig_handler, NULL); 321 signal_add(&ev_sigint, NULL); 322 signal_add(&ev_sigterm, NULL); 323 signal(SIGPIPE, SIG_IGN); 324 signal(SIGHUP, SIG_IGN); 325 326 config_pipes(peers, nitems(peers)); 327 config_peers(peers, nitems(peers)); 328 329 if (event_dispatch() < 0) 330 fatal("event_dispatch"); 331 mda_shutdown(); 332 333 return (0); 334 } 335 336 static void 337 mda_store(struct mda_session *s) 338 { 339 char *p; 340 struct ibuf *buf; 341 int len; 342 343 if (s->msg.sender.user[0] && s->msg.sender.domain[0]) 344 /* XXX: remove user provided Return-Path, if any */ 345 len = asprintf(&p, "Return-Path: %s@%s\nDelivered-To: %s@%s\n", 346 s->msg.sender.user, s->msg.sender.domain, 347 s->msg.rcpt.user, 348 s->msg.rcpt.domain); 349 else 350 len = asprintf(&p, "Delivered-To: %s@%s\n", 351 s->msg.rcpt.user, 352 s->msg.rcpt.domain); 353 354 if (len == -1) 355 fatal("mda_store: asprintf"); 356 357 session_socket_blockmode(s->w.fd, BM_NONBLOCK); 358 if ((buf = ibuf_open(len)) == NULL) 359 fatal(NULL); 360 if (ibuf_add(buf, p, len) < 0) 361 fatal(NULL); 362 ibuf_close(&s->w, buf); 363 event_set(&s->ev, s->w.fd, EV_WRITE, mda_store_event, s); 364 event_add(&s->ev, NULL); 365 free(p); 366 } 367 368 static void 369 mda_store_event(int fd, short event, void *p) 370 { 371 char tmp[16384]; 372 struct mda_session *s = p; 373 struct ibuf *buf; 374 size_t len; 375 376 if (s->w.queued == 0) { 377 if ((buf = ibuf_dynamic(0, sizeof tmp)) == NULL) 378 fatal(NULL); 379 len = fread(tmp, 1, sizeof tmp, s->datafp); 380 if (ferror(s->datafp)) 381 fatal("mda_store_event: fread failed"); 382 if (feof(s->datafp) && len == 0) { 383 close(s->w.fd); 384 s->w.fd = -1; 385 return; 386 } 387 if (ibuf_add(buf, tmp, len) < 0) 388 fatal(NULL); 389 ibuf_close(&s->w, buf); 390 } 391 392 if (ibuf_write(&s->w) < 0) { 393 close(s->w.fd); 394 s->w.fd = -1; 395 return; 396 } 397 398 event_set(&s->ev, fd, EV_WRITE, mda_store_event, s); 399 event_add(&s->ev, NULL); 400 } 401 402 static struct mda_session * 403 mda_lookup(u_int32_t id) 404 { 405 struct mda_session *s; 406 407 LIST_FOREACH(s, &env->mda_sessions, entry) 408 if (s->id == id) 409 break; 410 411 if (s == NULL) 412 fatalx("mda: bogus session id"); 413 414 return s; 415 } 416