/* $OpenBSD: lka.c,v 1.62 2009/08/08 00:02:22 gilles Exp $ */ /* * Copyright (c) 2008 Pierre-Yves Ritschard * Copyright (c) 2008 Gilles Chehade * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "smtpd.h" __dead void lka_shutdown(void); void lka_sig_handler(int, short, void *); void lka_dispatch_parent(int, short, void *); void lka_dispatch_mfa(int, short, void *); void lka_dispatch_smtp(int, short, void *); void lka_dispatch_queue(int, short, void *); void lka_dispatch_runner(int, short, void *); void lka_dispatch_mta(int, short, void *); void lka_setup_events(struct smtpd *); void lka_disable_events(struct smtpd *); int lka_verify_mail(struct smtpd *, struct path *); int lka_resolve_mail(struct smtpd *, struct rule *, struct path *); int lka_forward_file(struct passwd *); size_t lka_expand(char *, size_t, struct path *); int aliases_exist(struct smtpd *, char *); int aliases_get(struct smtpd *, struct aliaseslist *, char *); int lka_resolve_alias(struct smtpd *, struct path *, struct alias *); int lka_parse_include(char *); int lka_check_source(struct smtpd *, struct map *, struct sockaddr_storage *); int lka_match_mask(struct sockaddr_storage *, struct netaddr *); int aliases_virtual_get(struct smtpd *, struct aliaseslist *, struct path *); int aliases_virtual_exist(struct smtpd *, struct path *); int lka_resolve_path(struct smtpd *, struct path *); void lka_expand_rcpt(struct smtpd *, struct aliaseslist *, struct lkasession *); int lka_expand_rcpt_iteration(struct smtpd *, struct aliaseslist *, struct lkasession *); void lka_rcpt_action(struct smtpd *, struct path *); void lka_clear_aliaseslist(struct aliaseslist *); int lka_encode_credentials(char *, size_t, char *); struct rule *ruleset_match(struct smtpd *, struct path *, struct sockaddr_storage *); void lka_sig_handler(int sig, short event, void *p) { int status; pid_t pid; switch (sig) { case SIGINT: case SIGTERM: lka_shutdown(); break; case SIGCHLD: do { pid = waitpid(-1, &status, WNOHANG); } while (pid > 0 || (pid == -1 && errno == EINTR)); break; default: fatalx("lka_sig_handler: unexpected signal"); } } void lka_dispatch_parent(int sig, short event, void *p) { struct smtpd *env = p; struct imsgev *iev; struct imsgbuf *ibuf; struct imsg imsg; ssize_t n; iev = env->sc_ievs[PROC_PARENT]; ibuf = &iev->ibuf; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1) fatal("imsg_read_error"); if (n == 0) { /* this pipe is dead, so remove the event handler */ event_del(&iev->ev); event_loopexit(NULL); return; } } if (event & EV_WRITE) { if (msgbuf_write(&ibuf->w) == -1) fatal("msgbuf_write"); } for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatalx("lka_dispatch_parent: imsg_get error"); if (n == 0) break; switch (imsg.hdr.type) { case IMSG_CONF_START: if ((env->sc_rules_reload = calloc(1, sizeof(*env->sc_rules))) == NULL) fatal("mfa_dispatch_parent: calloc"); if ((env->sc_maps_reload = calloc(1, sizeof(*env->sc_maps))) == NULL) fatal("mfa_dispatch_parent: calloc"); TAILQ_INIT(env->sc_rules_reload); TAILQ_INIT(env->sc_maps_reload); break; case IMSG_CONF_RULE: { struct rule *rule = imsg.data; IMSG_SIZE_CHECK(rule); rule = calloc(1, sizeof(*rule)); if (rule == NULL) fatal("mfa_dispatch_parent: calloc"); *rule = *(struct rule *)imsg.data; TAILQ_INIT(&rule->r_conditions); TAILQ_INSERT_TAIL(env->sc_rules_reload, rule, r_entry); break; } case IMSG_CONF_CONDITION: { struct rule *r = TAILQ_LAST(env->sc_rules_reload, rulelist); struct cond *cond = imsg.data; IMSG_SIZE_CHECK(cond); cond = calloc(1, sizeof(*cond)); if (cond == NULL) fatal("mfa_dispatch_parent: calloc"); *cond = *(struct cond *)imsg.data; TAILQ_INSERT_TAIL(&r->r_conditions, cond, c_entry); break; } case IMSG_CONF_MAP: { struct map *m = imsg.data; IMSG_SIZE_CHECK(m); m = calloc(1, sizeof(*m)); if (m == NULL) fatal("mfa_dispatch_parent: calloc"); *m = *(struct map *)imsg.data; TAILQ_INIT(&m->m_contents); TAILQ_INSERT_TAIL(env->sc_maps_reload, m, m_entry); break; } case IMSG_CONF_RULE_SOURCE: { struct rule *rule = TAILQ_LAST(env->sc_rules_reload, rulelist); char *sourcemap = imsg.data; void *temp = env->sc_maps; /* map lookup must be done in the reloaded conf */ env->sc_maps = env->sc_maps_reload; rule->r_sources = map_findbyname(env, sourcemap); if (rule->r_sources == NULL) fatalx("maps inconsistency"); env->sc_maps = temp; break; } case IMSG_CONF_MAP_CONTENT: { struct map *m = TAILQ_LAST(env->sc_maps_reload, maplist); struct mapel *mapel = imsg.data; IMSG_SIZE_CHECK(mapel); mapel = calloc(1, sizeof(*mapel)); if (mapel == NULL) fatal("mfa_dispatch_parent: calloc"); *mapel = *(struct mapel *)imsg.data; TAILQ_INSERT_TAIL(&m->m_contents, mapel, me_entry); break; } case IMSG_CONF_END: { /* switch and destroy old ruleset */ if (env->sc_rules) purge_config(env, PURGE_RULES); if (env->sc_maps) purge_config(env, PURGE_MAPS); env->sc_rules = env->sc_rules_reload; env->sc_maps = env->sc_maps_reload; break; } case IMSG_PARENT_FORWARD_OPEN: { int fd; struct forward_req *fwreq = imsg.data; struct lkasession key; struct lkasession *lkasession; IMSG_SIZE_CHECK(fwreq); key.id = fwreq->id; lkasession = SPLAY_FIND(lkatree, &env->lka_sessions, &key); if (lkasession == NULL) fatal("lka_dispatch_parent: lka session is gone"); fd = imsg.fd; --lkasession->pending; if (fd == -1) { if (! fwreq->status) { lkasession->ss.code = 530; lkasession->flags |= F_ERROR; } else { struct alias *alias; struct message message; alias = calloc(1, sizeof(struct alias)); if (alias == NULL) fatal("lka_dispatch_parent: calloc"); alias_parse(alias, fwreq->pw_name); message = lkasession->message; bzero(&message.recipient, sizeof(struct path)); strlcpy(message.recipient.domain, lkasession->path.domain, sizeof(message.recipient.domain)); lka_resolve_alias(env, &message.recipient, alias); lka_rcpt_action(env, &message.recipient); if (lka_expand(message.recipient.rule.r_value.path, sizeof(message.recipient.rule.r_value.path), &message.recipient) >= sizeof(message.recipient.rule.r_value.path)) { log_debug("expansion failed..."); } imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_SUBMIT_ENVELOPE, 0, 0, -1, &message, sizeof(struct message)); if (! lkasession->pending) imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, &message, sizeof(struct message)); break; } } else { if (! forwards_get(fd, &lkasession->aliaseslist)) { lkasession->ss.code = 530; lkasession->flags |= F_ERROR; } close(fd); } lka_expand_rcpt(env, &lkasession->aliaseslist, lkasession); break; } default: log_warnx("lka_dispatch_parent: got imsg %d", imsg.hdr.type); fatalx("lka_dispatch_parent: unexpected imsg"); } imsg_free(&imsg); } imsg_event_add(iev); } void lka_dispatch_mfa(int sig, short event, void *p) { struct smtpd *env = p; struct imsgev *iev; struct imsgbuf *ibuf; struct imsg imsg; ssize_t n; iev = env->sc_ievs[PROC_MFA]; ibuf = &iev->ibuf; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1) fatal("imsg_read_error"); if (n == 0) { /* this pipe is dead, so remove the event handler */ event_del(&iev->ev); event_loopexit(NULL); return; } } if (event & EV_WRITE) { if (msgbuf_write(&ibuf->w) == -1) fatal("msgbuf_write"); } for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatalx("lka_dispatch_mfa: imsg_get error"); if (n == 0) break; switch (imsg.hdr.type) { case IMSG_LKA_MAIL: { struct submit_status *ss = imsg.data; IMSG_SIZE_CHECK(ss); ss->code = 530; if (ss->u.path.user[0] == '\0' && ss->u.path.domain[0] == '\0') ss->code = 250; else if (lka_verify_mail(env, &ss->u.path)) ss->code = 250; imsg_compose_event(iev, IMSG_LKA_MAIL, 0, 0, -1, ss, sizeof(*ss)); break; } case IMSG_LKA_RCPT: { struct submit_status *ss = imsg.data; struct message message; struct lkasession *lkasession; struct forward_req fwreq; int ret; IMSG_SIZE_CHECK(ss); ss->code = 530; if (IS_RELAY(ss->u.path.rule.r_action)) { ss->code = 250; message = ss->msg; message.recipient = ss->u.path; imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_SUBMIT_ENVELOPE, 0, 0, -1, &message, sizeof(struct message)); imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, &message, sizeof(struct message)); break; } if (! lka_resolve_path(env, &ss->u.path)) { imsg_compose_event(iev, IMSG_LKA_RCPT, 0, 0, -1, ss, sizeof(*ss)); break; } ss->code = 250; lkasession = calloc(1, sizeof(struct lkasession)); if (lkasession == NULL) fatal("lka_dispatch_mfa: calloc"); lkasession->id = queue_generate_id(); lkasession->path = ss->u.path; lkasession->message = ss->msg; lkasession->ss = *ss; TAILQ_INIT(&lkasession->aliaseslist); SPLAY_INSERT(lkatree, &env->lka_sessions, lkasession); ret = 0; if (lkasession->path.flags & F_PATH_ACCOUNT) { fwreq.id = lkasession->id; (void)strlcpy(fwreq.pw_name, ss->u.path.pw_name, sizeof(fwreq.pw_name)); imsg_compose_event(env->sc_ievs[PROC_PARENT], IMSG_PARENT_FORWARD_OPEN, 0, 0, -1, &fwreq, sizeof(fwreq)); ++lkasession->pending; break; } else if (lkasession->path.flags & F_PATH_ALIAS) { ret = aliases_get(env, &lkasession->aliaseslist, lkasession->path.user); } else if (lkasession->path.flags & F_PATH_VIRTUAL) { ret = aliases_virtual_get(env, &lkasession->aliaseslist, &lkasession->path); } else fatal("lka_dispatch_mfa: path with illegal flag"); if (ret == 0) { /* No aliases ... */ ss->code = 530; imsg_compose_event(iev, IMSG_LKA_RCPT, 0, 0, -1, ss, sizeof(*ss)); break; } lka_expand_rcpt(env, &lkasession->aliaseslist, lkasession); break; } default: log_warnx("lka_dispatch_mfa: got imsg %d", imsg.hdr.type); fatalx("lka_dispatch_mfa: unexpected imsg"); } imsg_free(&imsg); } imsg_event_add(iev); } void lka_dispatch_mta(int sig, short event, void *p) { struct smtpd *env = p; struct imsgev *iev; struct imsgbuf *ibuf; struct imsg imsg; ssize_t n; iev = env->sc_ievs[PROC_MTA]; ibuf = &iev->ibuf; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1) fatal("imsg_read_error"); if (n == 0) { /* this pipe is dead, so remove the event handler */ event_del(&iev->ev); event_loopexit(NULL); return; } } if (event & EV_WRITE) { if (msgbuf_write(&ibuf->w) == -1) fatal("msgbuf_write"); } for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatalx("lka_dispatch_mta: imsg_get error"); if (n == 0) break; switch (imsg.hdr.type) { case IMSG_LKA_SECRET: { struct secret *query = imsg.data; char *secret = NULL; char *map = "secrets"; IMSG_SIZE_CHECK(query); secret = map_dblookup(env, map, query->host); log_debug("secret for %s %s", query->host, secret ? "found" : "not found"); query->secret[0] = '\0'; if (secret == NULL) { log_warnx("failed to lookup %s in the %s map", query->host, map); } else if (! lka_encode_credentials(query->secret, sizeof(query->secret), secret)) { log_warnx("parse error for %s in the %s map", query->host, map); } imsg_compose_event(iev, IMSG_LKA_SECRET, 0, 0, -1, query, sizeof(*query)); free(secret); break; } case IMSG_DNS_A: case IMSG_DNS_MX: { struct dns *query = imsg.data; IMSG_SIZE_CHECK(query); dns_async(env, iev, imsg.hdr.type, query); break; } default: log_warnx("lka_dispatch_mta: got imsg %d", imsg.hdr.type); fatalx("lka_dispatch_mta: unexpected imsg"); } imsg_free(&imsg); } imsg_event_add(iev); } void lka_dispatch_smtp(int sig, short event, void *p) { struct smtpd *env = p; struct imsgev *iev; struct imsgbuf *ibuf; struct imsg imsg; ssize_t n; iev = env->sc_ievs[PROC_SMTP]; ibuf = &iev->ibuf; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1) fatal("imsg_read_error"); if (n == 0) { /* this pipe is dead, so remove the event handler */ event_del(&iev->ev); event_loopexit(NULL); return; } } if (event & EV_WRITE) { if (msgbuf_write(&ibuf->w) == -1) fatal("msgbuf_write"); } for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatalx("lka_dispatch_smtp: imsg_get error"); if (n == 0) break; switch (imsg.hdr.type) { case IMSG_DNS_PTR: { struct dns *query = imsg.data; IMSG_SIZE_CHECK(query); dns_async(env, iev, IMSG_DNS_PTR, query); break; } default: log_warnx("lka_dispatch_smtp: got imsg %d", imsg.hdr.type); fatalx("lka_dispatch_smtp: unexpected imsg"); } imsg_free(&imsg); } imsg_event_add(iev); } void lka_dispatch_queue(int sig, short event, void *p) { struct smtpd *env = p; struct imsgev *iev; struct imsgbuf *ibuf; struct imsg imsg; ssize_t n; iev = env->sc_ievs[PROC_QUEUE]; ibuf = &iev->ibuf; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1) fatal("imsg_read_error"); if (n == 0) { /* this pipe is dead, so remove the event handler */ event_del(&iev->ev); event_loopexit(NULL); return; } } if (event & EV_WRITE) { if (msgbuf_write(&ibuf->w) == -1) fatal("msgbuf_write"); } for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatalx("lka_dispatch_queue: imsg_get error"); if (n == 0) break; switch (imsg.hdr.type) { default: log_warnx("lka_dispatch_queue: got imsg %d", imsg.hdr.type); fatalx("lka_dispatch_queue: unexpected imsg"); } imsg_free(&imsg); } imsg_event_add(iev); } void lka_dispatch_runner(int sig, short event, void *p) { struct smtpd *env = p; struct imsgev *iev; struct imsgbuf *ibuf; struct imsg imsg; ssize_t n; iev = env->sc_ievs[PROC_RUNNER]; ibuf = &iev->ibuf; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1) fatal("imsg_read_error"); if (n == 0) { /* this pipe is dead, so remove the event handler */ event_del(&iev->ev); event_loopexit(NULL); return; } } if (event & EV_WRITE) { if (msgbuf_write(&ibuf->w) == -1) fatal("msgbuf_write"); } for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatalx("lka_dispatch_runner: imsg_get error"); if (n == 0) break; switch (imsg.hdr.type) { default: log_warnx("lka_dispatch_runner: got imsg %d", imsg.hdr.type); fatalx("lka_dispatch_runner: unexpected imsg"); } imsg_free(&imsg); } imsg_event_add(iev); } void lka_shutdown(void) { log_info("lookup agent exiting"); _exit(0); } void lka_setup_events(struct smtpd *env) { } void lka_disable_events(struct smtpd *env) { } pid_t lka(struct smtpd *env) { pid_t pid; struct passwd *pw; struct event ev_sigint; struct event ev_sigterm; struct event ev_sigchld; struct peer peers[] = { { PROC_PARENT, lka_dispatch_parent }, { PROC_MFA, lka_dispatch_mfa }, { PROC_QUEUE, lka_dispatch_queue }, { PROC_SMTP, lka_dispatch_smtp }, { PROC_RUNNER, lka_dispatch_runner }, { PROC_MTA, lka_dispatch_mta } }; switch (pid = fork()) { case -1: fatal("lka: cannot fork"); case 0: break; default: return (pid); } purge_config(env, PURGE_EVERYTHING); pw = env->sc_pw; smtpd_process = PROC_LKA; setproctitle("%s", env->sc_title[smtpd_process]); #ifndef DEBUG if (setgroups(1, &pw->pw_gid) || setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) || setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("lka: cannot drop privileges"); #endif event_init(); SPLAY_INIT(&env->lka_sessions); signal_set(&ev_sigint, SIGINT, lka_sig_handler, env); signal_set(&ev_sigterm, SIGTERM, lka_sig_handler, env); signal_set(&ev_sigchld, SIGCHLD, lka_sig_handler, env); signal_add(&ev_sigint, NULL); signal_add(&ev_sigterm, NULL); signal_add(&ev_sigchld, NULL); signal(SIGPIPE, SIG_IGN); signal(SIGHUP, SIG_IGN); config_pipes(env, peers, nitems(peers)); config_peers(env, peers, nitems(peers)); lka_setup_events(env); event_dispatch(); lka_shutdown(); return (0); } int lka_verify_mail(struct smtpd *env, struct path *path) { struct rule *r; r = ruleset_match(env, path, NULL); if (r == NULL) { path->rule.r_action = A_RELAY; return 1; } path->rule = *r; if (r->r_action == A_MBOX || r->r_action == A_MAILDIR || r->r_action == A_EXT) { lka_resolve_mail(env, r, path); return 1; } return 1; } int lka_resolve_mail(struct smtpd *env, struct rule *rule, struct path *path) { char username[MAXLOGNAME]; struct passwd *pw; char *p; (void)strlcpy(username, path->user, sizeof(username)); for (p = &username[0]; *p != '\0' && *p != '+'; ++p) *p = tolower((int)*p); *p = '\0'; if (aliases_virtual_exist(env, path)) path->flags |= F_PATH_VIRTUAL; else if (aliases_exist(env, username)) path->flags |= F_PATH_ALIAS; else { pw = getpwnam(username); if (pw == NULL) return 0; (void)strlcpy(path->pw_name, pw->pw_name, sizeof(path->pw_name)); if (lka_expand(path->rule.r_value.path, sizeof(path->rule.r_value.path), path) >= sizeof(path->rule.r_value.path)) return 0; } return 1; } size_t lka_expand(char *buf, size_t len, struct path *path) { char *p, *pbuf; struct rule r; size_t ret; struct passwd *pw; bzero(r.r_value.path, MAXPATHLEN); pbuf = r.r_value.path; ret = 0; for (p = path->rule.r_value.path; *p != '\0'; ++p) { if (p == path->rule.r_value.path && *p == '~') { if (*(p + 1) == '/' || *(p + 1) == '\0') { pw = getpwnam(path->pw_name); if (pw == NULL) continue; ret += strlcat(pbuf, pw->pw_dir, len); if (ret >= len) return ret; pbuf += strlen(pw->pw_dir); continue; } if (*(p + 1) != '/') { char username[MAXLOGNAME]; char *delim; ret = strlcpy(username, p + 1, sizeof(username)); delim = strchr(username, '/'); if (delim == NULL && ret >= sizeof(username)) { continue; } if (delim != NULL) { *delim = '\0'; } pw = getpwnam(username); if (pw == NULL) continue; ret += strlcat(pbuf, pw->pw_dir, len); if (ret >= len) return ret; pbuf += strlen(pw->pw_dir); p += strlen(username); continue; } } if (strncmp(p, "%a", 2) == 0) { ret += strlcat(pbuf, path->user, len); if (ret >= len) return ret; pbuf += strlen(path->user); ++p; continue; } if (strncmp(p, "%u", 2) == 0) { ret += strlcat(pbuf, path->pw_name, len); if (ret >= len) return ret; pbuf += strlen(path->pw_name); ++p; continue; } if (strncmp(p, "%d", 2) == 0) { ret += strlcat(pbuf, path->domain, len); if (ret >= len) return ret; pbuf += strlen(path->domain); ++p; continue; } if (*p == '%' && isdigit((int)*(p+1)) && *(p+2) == 'a') { size_t idx; idx = *(p+1) - '0'; if (idx < strlen(path->user)) *pbuf++ = path->user[idx]; p+=2; ++ret; continue; } if (*p == '%' && isdigit((int)*(p+1)) && *(p+2) == 'u') { size_t idx; idx = *(p+1) - '0'; if (idx < strlen(path->pw_name)) *pbuf++ = path->pw_name[idx]; p+=2; ++ret; continue; } if (*p == '%' && isdigit((int)*(p+1)) && *(p+2) == 'd') { size_t idx; idx = *(p+1) - '0'; if (idx < strlen(path->domain)) *pbuf++ = path->domain[idx]; p+=2; ++ret; continue; } *pbuf++ = *p; ++ret; } memcpy(path->rule.r_value.path, r.r_value.path, ret); return ret; } int lka_resolve_alias(struct smtpd *env, struct path *path, struct alias *alias) { switch (alias->type) { case ALIAS_USERNAME: log_debug("USERNAME: %s", alias->u.username); if (strlcpy(path->pw_name, alias->u.username, sizeof(path->pw_name)) >= sizeof(path->pw_name)) return 0; if (path->user[0] == '\0') { if (strlcpy(path->user, alias->u.username, sizeof(path->user)) >= sizeof(path->user)) return 0; } if (path->domain[0] == '\0') { if (strlcpy(path->domain, env->sc_hostname, sizeof(path->domain)) >= sizeof(path->domain)) return 0; } break; case ALIAS_FILENAME: log_debug("FILENAME: %s", alias->u.filename); path->rule.r_action = A_FILENAME; strlcpy(path->u.filename, alias->u.filename, sizeof(path->u.filename)); break; case ALIAS_FILTER: log_debug("FILTER: %s", alias->u.filter); path->rule.r_action = A_EXT; strlcpy(path->rule.r_value.command, alias->u.filter + 2, sizeof(path->rule.r_value.command)); path->rule.r_value.command[strlen(path->rule.r_value.command) - 1] = '\0'; break; case ALIAS_ADDRESS: log_debug("ADDRESS: %s@%s", alias->u.path.user, alias->u.path.domain); *path = alias->u.path; break; case ALIAS_INCLUDE: fatalx("lka_resolve_alias: unexpected type"); break; } return 1; } void lka_expand_rcpt(struct smtpd *env, struct aliaseslist *aliases, struct lkasession *lkasession) { int ret; struct alias *alias; struct message message; ret = 0; while (! (lkasession->flags & F_ERROR) && ! lkasession->pending && lkasession->iterations < 5) { ++lkasession->iterations; ret = lka_expand_rcpt_iteration(env, &lkasession->aliaseslist, lkasession); if (ret == -1) { lkasession->ss.code = 530; lkasession->flags |= F_ERROR; } if (lkasession->pending || ret <= 0) break; } if (lkasession->pending) return; if (lkasession->flags & F_ERROR) { lka_clear_aliaseslist(&lkasession->aliaseslist); imsg_compose_event(env->sc_ievs[PROC_MFA], IMSG_LKA_RCPT, 0, 0, -1, &lkasession->ss, sizeof(struct submit_status)); } else if (TAILQ_FIRST(&lkasession->aliaseslist) == NULL) { message = lkasession->message; message.recipient = lkasession->path; imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_SUBMIT_ENVELOPE, 0, 0, -1, &message, sizeof(struct message)); imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, &message, sizeof(struct message)); } else { log_debug("a list of aliases is available"); message = lkasession->message; while ((alias = TAILQ_FIRST(&lkasession->aliaseslist)) != NULL) { bzero(&message.recipient, sizeof(struct path)); lka_resolve_alias(env, &message.recipient, alias); lka_rcpt_action(env, &message.recipient); imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_SUBMIT_ENVELOPE, 0, 0, -1, &message, sizeof(struct message)); TAILQ_REMOVE(&lkasession->aliaseslist, alias, entry); free(alias); } imsg_compose_event(env->sc_ievs[PROC_QUEUE], IMSG_QUEUE_COMMIT_ENVELOPES, 0, 0, -1, &message, sizeof(struct message)); } SPLAY_REMOVE(lkatree, &env->lka_sessions, lkasession); free(lkasession); } int lka_expand_rcpt_iteration(struct smtpd *env, struct aliaseslist *aliases, struct lkasession *lkasession) { u_int8_t done = 1; struct alias *rmalias = NULL; struct alias *alias; struct forward_req fwreq; rmalias = NULL; TAILQ_FOREACH(alias, aliases, entry) { if (rmalias) { TAILQ_REMOVE(aliases, rmalias, entry); free(rmalias); rmalias = NULL; } if (alias->type == ALIAS_ADDRESS) { if (aliases_virtual_get(env, aliases, &alias->u.path)) { rmalias = alias; done = 0; } } else if (alias->type == ALIAS_USERNAME) { if (aliases_get(env, aliases, alias->u.username)) { done = 0; rmalias = alias; } else { done = 0; fwreq.id = lkasession->id; (void)strlcpy(fwreq.pw_name, alias->u.username, sizeof(fwreq.pw_name)); imsg_compose_event(env->sc_ievs[PROC_PARENT], IMSG_PARENT_FORWARD_OPEN, 0, 0, -1, &fwreq, sizeof(fwreq)); ++lkasession->pending; rmalias = alias; } } } if (rmalias) { TAILQ_REMOVE(aliases, rmalias, entry); free(rmalias); rmalias = NULL; } if (!done && lkasession->iterations == 5) return -1; if (TAILQ_FIRST(aliases) == NULL) return 0; return 1; } int lka_resolve_path(struct smtpd *env, struct path *path) { char username[MAXLOGNAME]; struct passwd *pw; char *p; (void)strlcpy(username, path->user, sizeof(username)); for (p = &username[0]; *p != '\0' && *p != '+'; ++p) *p = tolower((int)*p); *p = '\0'; if (aliases_virtual_exist(env, path)) path->flags |= F_PATH_VIRTUAL; else if (aliases_exist(env, username)) path->flags |= F_PATH_ALIAS; else { path->flags |= F_PATH_ACCOUNT; pw = getpwnam(username); if (pw == NULL) return 0; (void)strlcpy(path->pw_name, pw->pw_name, sizeof(path->pw_name)); if (lka_expand(path->rule.r_value.path, sizeof(path->rule.r_value.path), path) >= sizeof(path->rule.r_value.path)) return 0; } return 1; } void lka_rcpt_action(struct smtpd *env, struct path *path) { struct rule *r; if (path->domain[0] == '\0') (void)strlcpy(path->domain, env->sc_hostname, sizeof (path->domain)); r = ruleset_match(env, path, NULL); if (r == NULL) { path->rule.r_action = A_RELAY; return; } path->rule = *r; } int lkasession_cmp(struct lkasession *s1, struct lkasession *s2) { /* * do not return u_int64_t's */ if (s1->id < s2->id) return (-1); if (s1->id > s2->id) return (1); return (0); } void lka_clear_aliaseslist(struct aliaseslist *aliaseslist) { struct alias *alias; while ((alias = TAILQ_FIRST(aliaseslist)) != NULL) { TAILQ_REMOVE(aliaseslist, alias, entry); free(alias); } } int lka_encode_credentials(char *dst, size_t size, char *user) { char *pass, *buf; int buflen; if ((pass = strchr(user, ':')) == NULL) return 0; *pass++ = '\0'; if ((buflen = asprintf(&buf, "%c%s%c%s", '\0', user, '\0', pass)) == -1) fatal(NULL); if (__b64_ntop((unsigned char *)buf, buflen, dst, size) == -1) { free(buf); return 0; } free(buf); return 1; } SPLAY_GENERATE(lkatree, lkasession, nodes, lkasession_cmp);