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