diff options
Diffstat (limited to 'usr.sbin/smtpd/lka.c')
-rw-r--r-- | usr.sbin/smtpd/lka.c | 1013 |
1 files changed, 1013 insertions, 0 deletions
diff --git a/usr.sbin/smtpd/lka.c b/usr.sbin/smtpd/lka.c new file mode 100644 index 00000000000..f33b4cf5eb1 --- /dev/null +++ b/usr.sbin/smtpd/lka.c @@ -0,0 +1,1013 @@ +/* + * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> + * Copyright (c) 2008 Gilles Chehade <gilles@openbsd.org> + * + * 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 <sys/types.h> +#include <sys/queue.h> +#include <sys/tree.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <db.h> +#include <err.h> +#include <event.h> +#include <fcntl.h> +#include <pwd.h> +#include <netdb.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <util.h> + +#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_setup_events(struct smtpd *); +void lka_disable_events(struct smtpd *); +int lka_verify_mail(struct smtpd *, struct path *); +int lka_verify_rcpt(struct smtpd *, struct path *, struct sockaddr_storage *); +int lka_resolve_mail(struct smtpd *, struct rule *, struct path *); +int lka_resolve_rcpt(struct smtpd *, struct rule *, struct path *); +int lka_forward_file(struct passwd *); +size_t getmxbyname(char *, char ***); +int 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 imsgbuf *, struct message *, struct alias *); +int lka_parse_include(char *); +int forwards_get(struct aliaseslist *, 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 *); + +void +lka_sig_handler(int sig, short event, void *p) +{ + switch (sig) { + case SIGINT: + case SIGTERM: + lka_shutdown(); + break; + default: + fatalx("lka_sig_handler: unexpected signal"); + } +} + +void +lka_dispatch_parent(int sig, short event, void *p) +{ + struct smtpd *env = p; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + ibuf = env->sc_ibufs[PROC_PARENT]; + switch (event) { + case 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(&ibuf->ev); + event_loopexit(NULL); + return; + } + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("parent_dispatch_lka: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + default: + log_debug("parent_dispatch_lka: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(ibuf); +} + +void +lka_dispatch_mfa(int sig, short event, void *p) +{ + struct smtpd *env = p; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + ibuf = env->sc_ibufs[PROC_MFA]; + switch (event) { + case 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(&ibuf->ev); + event_loopexit(NULL); + return; + } + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("lka_dispatch_mfa: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_LKA_LOOKUP_MAIL: { + struct submit_status *ss; + + ss = imsg.data; + 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(ibuf, IMSG_MFA_LOOKUP_MAIL, 0, 0, -1, + ss, sizeof(*ss)); + + break; + } + case IMSG_LKA_LOOKUP_RCPT: { + struct submit_status *ss; + + ss = imsg.data; + ss->code = 530; + + if (lka_verify_rcpt(env, &ss->u.path, &ss->ss)) + ss->code = 250; + + imsg_compose(ibuf, IMSG_MFA_LOOKUP_RCPT, 0, 0, -1, + ss, sizeof(*ss)); + + break; + } + default: + log_debug("lka_dispatch_mfa: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(ibuf); +} + +void +lka_dispatch_smtp(int sig, short event, void *p) +{ + struct smtpd *env = p; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + ibuf = env->sc_ibufs[PROC_SMTP]; + switch (event) { + case 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(&ibuf->ev); + event_loopexit(NULL); + return; + } + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("lka_dispatch_mfa: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + case IMSG_LKA_HOSTNAME_LOOKUP: { + + struct sockaddr *sa = NULL; + socklen_t salen; + char addr[NI_MAXHOST]; + struct addrinfo hints, *res; + int error; + struct session *s = imsg.data; + + if (s->s_ss.ss_family == PF_INET) { + struct sockaddr_in *ssin = (struct sockaddr_in *)&s->s_ss; + sa = (struct sockaddr *)ssin; + } + if (s->s_ss.ss_family == PF_INET6) { + struct sockaddr_in6 *ssin6 = (struct sockaddr_in6 *)&s->s_ss; + sa = (struct sockaddr *)ssin6; + } + + error = getnameinfo(sa, sa->sa_len, addr, sizeof(addr), + NULL, 0, NI_NAMEREQD); + if (error == 0) { + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; + hints.ai_flags = AI_NUMERICHOST; + if (getaddrinfo(addr, "0", &hints, &res) == 0) { + freeaddrinfo(res); + strlcpy(s->s_hostname, "<bogus>", MAXHOSTNAMELEN); + imsg_compose(ibuf, IMSG_SMTP_HOSTNAME_ANSWER, 0, 0, -1, + s, sizeof(struct session)); + break; + } + } else { + error = getnameinfo(sa, salen, addr, sizeof(addr), + NULL, 0, NI_NUMERICHOST); + } + strlcpy(s->s_hostname, addr, MAXHOSTNAMELEN); + imsg_compose(ibuf, IMSG_SMTP_HOSTNAME_ANSWER, 0, 0, -1, + s, sizeof(struct session)); + break; + } + default: + log_debug("lka_dispatch_mfa: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(ibuf); +} + +void +lka_dispatch_queue(int sig, short event, void *p) +{ + struct smtpd *env = p; + struct imsgbuf *ibuf; + struct imsg imsg; + ssize_t n; + + ibuf = env->sc_ibufs[PROC_QUEUE]; + switch (event) { + case 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(&ibuf->ev); + event_loopexit(NULL); + return; + } + break; + case EV_WRITE: + if (msgbuf_write(&ibuf->w) == -1) + fatal("msgbuf_write"); + imsg_event_add(ibuf); + return; + default: + fatalx("unknown event"); + } + + for (;;) { + if ((n = imsg_get(ibuf, &imsg)) == -1) + fatal("lka_dispatch_queue: imsg_read error"); + if (n == 0) + break; + + switch (imsg.hdr.type) { + + case IMSG_LKA_ALIAS_LOOKUP: { + struct message *messagep; + struct alias *alias; + struct alias *remalias; + struct path *path; + struct aliaseslist aliases; + u_int8_t done = 0; + size_t nbiterations = 5; + int ret; + + messagep = imsg.data; + path = &messagep->recipient; + + if (path->flags & F_EXPANDED) + break; + + TAILQ_INIT(&aliases); + + if (path->flags & F_ALIAS) { + ret = aliases_get(env, &aliases, path->user); + } + + if (path->flags & F_VIRTUAL) { + ret = aliases_virtual_get(env, &aliases, path); + } + + if (! ret) { + /* + * Aliases could not be retrieved, this happens + * if the aliases database is regenerated while + * the message is being processed. It is not an + * error necessarily so just ignore this and it + * will be handled by the queue process. + */ + imsg_compose(ibuf, IMSG_QUEUE_REMOVE_SUBMISSION, 0, 0, -1, messagep, + sizeof(struct message)); + break; + } + + /* First pass, figure out if some of the usernames that + * are in the list are actually aliases and expand them + * if they are. The resolution will be tried five times + * at most with an early exit if list did not change in + * a pass. + */ + while (!done && nbiterations--) { + done = 1; + remalias = NULL; + TAILQ_FOREACH(alias, &aliases, entry) { + if (remalias) { + TAILQ_REMOVE(&aliases, remalias, entry); + free(remalias); + remalias = NULL; + } + + if (alias->type == ALIAS_ADDRESS) { + if (aliases_virtual_get(env, &aliases, &alias->u.path)) { + done = 0; + remalias = alias; + } + } + + else if (alias->type == ALIAS_USERNAME) { + if (aliases_get(env, &aliases, alias->u.username)) { + done = 0; + remalias = alias; + } + } + } + if (remalias) { + TAILQ_REMOVE(&aliases, remalias, entry); + free(remalias); + remalias = NULL; + } + } + + /* Second pass, the list no longer contains aliases and + * the message can be sent back to queue process with a + * modified path. + */ + TAILQ_FOREACH(alias, &aliases, entry) { + struct message message = *messagep; + lka_resolve_alias(env, ibuf, &message, alias); + imsg_compose(ibuf, IMSG_LKA_ALIAS_RESULT, 0, 0, -1, + &message, sizeof(struct message)); + } + + imsg_compose(ibuf, IMSG_QUEUE_REMOVE_SUBMISSION, 0, 0, -1, + messagep, sizeof(struct message)); + + while ((alias = TAILQ_FIRST(&aliases))) { + TAILQ_REMOVE(&aliases, alias, entry); + free(alias); + } + break; + } + + case IMSG_LKA_FORWARD_LOOKUP: { + struct message *messagep; + struct aliaseslist aliases; + struct alias *alias; + + messagep = imsg.data; + + /* this is the tenth time the message has been forwarded + * internally, break out of the loop. + */ + if (messagep->recipient.forwardcnt == 10) { + imsg_compose(ibuf, IMSG_QUEUE_REMOVE_SUBMISSION, 0, 0, -1, messagep, + sizeof(struct message)); + break; + } + messagep->recipient.forwardcnt++; + + TAILQ_INIT(&aliases); + if (! forwards_get(&aliases, messagep->recipient.pw_name)) { + messagep->recipient.flags |= F_NOFORWARD; + imsg_compose(ibuf, IMSG_LKA_FORWARD_LOOKUP, 0, 0, -1, messagep, sizeof(struct message)); + imsg_compose(ibuf, IMSG_QUEUE_REMOVE_SUBMISSION, 0, 0, -1, messagep, + sizeof(struct message)); + break; + } + + TAILQ_FOREACH(alias, &aliases, entry) { + struct message message = *messagep; + lka_resolve_alias(env, ibuf, &message, alias); + if (strcmp(messagep->recipient.pw_name, alias->u.username) == 0) { + + message.recipient.flags |= F_FORWARDED; + } + imsg_compose(ibuf, IMSG_LKA_FORWARD_LOOKUP, 0, 0, -1, &message, sizeof(struct message)); + } + + imsg_compose(ibuf, IMSG_QUEUE_REMOVE_SUBMISSION, 0, 0, -1, messagep, sizeof(struct message)); + + while ((alias = TAILQ_FIRST(&aliases))) { + TAILQ_REMOVE(&aliases, alias, entry); + free(alias); + } + + break; + } + + case IMSG_LKA_MX_LOOKUP: { + struct batch *batchp; + struct addrinfo hints, *res, *resp; + char **mx = NULL; + char *lmx[1]; + size_t len, i, j; + int error; + u_int16_t port = 25; + + batchp = imsg.data; + + if (! IS_RELAY(batchp->rule.r_action)) + err(1, "lka_dispatch_queue: inconsistent internal state"); + + if (batchp->rule.r_action == A_RELAY) { + log_debug("attempting to resolve %s", batchp->hostname); + len = getmxbyname(batchp->hostname, &mx); + if (len == 0) { + lmx[0] = batchp->hostname; + mx = lmx; + len = 1; + } + } + else if (batchp->rule.r_action == A_RELAYVIA) { + lmx[0] = batchp->rule.r_value.host.hostname; + port = batchp->rule.r_value.host.port; + log_debug("attempting to resolve %s:%d (forced)", lmx[0], port); + mx = lmx; + len = 1; + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_protocol = IPPROTO_TCP; + for (i = j = 0; i < len; ++i) { + error = getaddrinfo(mx[i], NULL, &hints, &res); + if (error) + continue; + + log_debug("resolving MX: %s", mx[i]); + + for (resp = res; resp != NULL; resp = resp->ai_next) { + if (resp->ai_family == PF_INET) { + struct sockaddr_in *ssin; + + batchp->ss[j] = *(struct sockaddr_storage *)resp->ai_addr; + ssin = (struct sockaddr_in *)&batchp->ss[j]; + ssin->sin_port = htons(port); + ++j; + } + if (resp->ai_family == PF_INET6) { + struct sockaddr_in6 *ssin6; + batchp->ss[j] = *(struct sockaddr_storage *)resp->ai_addr; + ssin6 = (struct sockaddr_in6 *)&batchp->ss[j]; + ssin6->sin6_port = htons(port); + ++j; + } + } + + freeaddrinfo(res); + } + + batchp->ss_cnt = j; + batchp->h_errno = 0; + if (j == 0) + batchp->h_errno = error; + imsg_compose(ibuf, IMSG_LKA_MX_LOOKUP, 0, 0, -1, batchp, sizeof(*batchp)); + + if (mx != lmx) + free(mx); + + break; + } + default: + log_debug("lka_dispatch_queue: unexpected imsg %d", + imsg.hdr.type); + break; + } + imsg_free(&imsg); + } + imsg_event_add(ibuf); +} + +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 peer peers[] = { + { PROC_PARENT, lka_dispatch_parent }, + { PROC_MFA, lka_dispatch_mfa }, + { PROC_QUEUE, lka_dispatch_queue }, + { PROC_SMTP, lka_dispatch_smtp }, + }; + + switch (pid = fork()) { + case -1: + fatal("lka: cannot fork"); + case 0: + break; + default: + return (pid); + } + +// purge_config(env, PURGE_EVERYTHING); + + pw = env->sc_pw; + + setproctitle("lookup agent"); + smtpd_process = PROC_LKA; + +#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(); + + signal_set(&ev_sigint, SIGINT, lka_sig_handler, env); + signal_set(&ev_sigterm, SIGTERM, lka_sig_handler, env); + signal_add(&ev_sigint, NULL); + signal_add(&ev_sigterm, NULL); + signal(SIGPIPE, SIG_IGN); + signal(SIGHUP, SIG_IGN); + + config_peers(env, peers, 4); + + lka_setup_events(env); + event_dispatch(); + lka_shutdown(); + + return (0); +} + +int +lka_verify_mail(struct smtpd *env, struct path *path) +{ + struct rule *r; + struct cond *cond; + struct map *map; + struct mapel *me; + + TAILQ_FOREACH(r, env->sc_rules, r_entry) { + TAILQ_FOREACH(cond, &r->r_conditions, c_entry) { + if (cond->c_type == C_ALL) { + path->rule = *r; + if (r->r_action == A_MBOX || + r->r_action == A_MAILDIR) { + return lka_resolve_mail(env, r, path); + } + return 1; + } + + if (cond->c_type == C_DOM) { + cond->c_match = map_find(env, cond->c_map); + if (cond->c_match == NULL) + fatal("lka failed to lookup map."); + + map = cond->c_match; + TAILQ_FOREACH(me, &map->m_contents, me_entry) { + if (strcasecmp(me->me_key.med_string, + path->domain) == 0) { + path->rule = *r; + if (r->r_action == A_MBOX || + r->r_action == A_MAILDIR || + r->r_action == A_EXT) { + return lka_resolve_mail(env, r, path); + } + return 1; + } + } + } + } + } + path->rule.r_action = A_RELAY; + return 1; +} + +int +lka_verify_rcpt(struct smtpd *env, struct path *path, struct sockaddr_storage *ss) +{ + struct rule *r; + struct cond *cond; + struct map *map; + struct mapel *me; + + TAILQ_FOREACH(r, env->sc_rules, r_entry) { + + TAILQ_FOREACH(cond, &r->r_conditions, c_entry) { + + if (cond->c_type == C_ALL) { + path->rule = *r; + + if (! lka_check_source(env, r->r_sources, ss)) + return 0; + + if (r->r_action == A_MBOX || + r->r_action == A_MAILDIR) { + return lka_resolve_rcpt(env, r, path); + } + return 1; + } + + if (cond->c_type == C_DOM) { + + cond->c_match = map_find(env, cond->c_map); + if (cond->c_match == NULL) + fatal("lka failed to lookup map."); + + map = cond->c_match; + TAILQ_FOREACH(me, &map->m_contents, me_entry) { + if (strcasecmp(me->me_key.med_string, + path->domain) == 0) { + path->rule = *r; + if (! lka_check_source(env, r->r_sources, ss)) + return 0; + + if (IS_MAILBOX(r->r_action) || + IS_EXT(r->r_action)) { + return lka_resolve_rcpt(env, r, path); + } + return 1; + } + } + } + } + } + return 0; +} + +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, MAXLOGNAME); + + for (p = &username[0]; *p != '\0' && *p != '+'; ++p) + *p = tolower((int)*p); + *p = '\0'; + + if (aliases_virtual_exist(env, path)) + path->flags |= F_VIRTUAL; + else if (aliases_exist(env, username)) + path->flags |= F_ALIAS; + else { + pw = getpwnam(username); + if (pw == NULL) + return 0; + (void)strlcpy(path->pw_name, pw->pw_name, MAXLOGNAME); + if (lka_expand(path->rule.r_value.path, MAXPATHLEN, path) >= + MAXPATHLEN) + return 0; + } + + return 1; +} + +int +lka_resolve_rcpt(struct smtpd *env, struct rule *rule, struct path *path) +{ + char username[MAXLOGNAME]; + struct passwd *pw; + char *p; + + (void)strlcpy(username, path->user, MAXLOGNAME); + + for (p = &username[0]; *p != '\0' && *p != '+'; ++p) + *p = tolower((int)*p); + *p = '\0'; + + if ((path->flags & F_EXPANDED) == 0 && aliases_virtual_exist(env, path)) + path->flags |= F_VIRTUAL; + else if ((path->flags & F_EXPANDED) == 0 && aliases_exist(env, username)) + path->flags |= F_ALIAS; + else { + pw = getpwnam(path->pw_name); + if (pw == NULL) + pw = getpwnam(username); + if (pw == NULL) + return 0; + (void)strlcpy(path->pw_name, pw->pw_name, MAXLOGNAME); + if (lka_expand(path->rule.r_value.path, MAXPATHLEN, path) >= + MAXPATHLEN) { + return 0; + } + } + + return 1; +} + +int +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); + ++p; + continue; + } + + if (*(p + 1) != '/') { + char username[MAXLOGNAME]; + char *delim; + + ret = strlcpy(username, p + 1, MAXLOGNAME); + delim = strchr(username, '/'); + if (delim == NULL && ret >= MAXLOGNAME) { + 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 imsgbuf *ibuf, struct message *messagep, struct alias *alias) +{ + struct path *rpath = &messagep->recipient; + + rpath->flags &= ~F_ALIAS; + rpath->flags |= F_EXPANDED; + + switch (alias->type) { + case ALIAS_USERNAME: + if (strlcpy(rpath->pw_name, alias->u.username, + sizeof(rpath->pw_name)) >= sizeof(rpath->pw_name)) + return 0; + lka_verify_rcpt(env, rpath, NULL); + break; + + case ALIAS_FILENAME: + rpath->rule.r_action = A_FILENAME; + strlcpy(rpath->u.filename, alias->u.filename, MAXPATHLEN); + break; + + case ALIAS_FILTER: + rpath->rule.r_action = A_EXT; + strlcpy(rpath->rule.r_value.command, alias->u.filter + 2, MAXPATHLEN); + rpath->rule.r_value.command[strlen(rpath->rule.r_value.command) - 1] = '\0'; + break; + + case ALIAS_ADDRESS: + *rpath = alias->u.path; + lka_verify_rcpt(env, rpath, NULL); + if (IS_MAILBOX(rpath->rule.r_action) || + IS_EXT(rpath->rule.r_action)) + messagep->type = T_MDA_MESSAGE; + else + messagep->type = T_MTA_MESSAGE; + + break; + default: + /* ALIAS_INCLUDE cannot happen here, make gcc shut up */ + break; + } + return 1; +} + +int +lka_check_source(struct smtpd *env, struct map *map, struct sockaddr_storage *ss) +{ + struct mapel *me; + + if (ss == NULL) { + /* This happens when caller is part of an internal + * lookup (ie: alias resolved to a remote address) + */ + return 1; + } + + TAILQ_FOREACH(me, &map->m_contents, me_entry) { + + if (ss->ss_family != me->me_key.med_addr.ss.ss_family) + continue; + + if (ss->ss_len == me->me_key.med_addr.ss.ss_len) + continue; + + if (lka_match_mask(ss, &me->me_key.med_addr)) + return 1; + + } + return 0; +} + +int +lka_match_mask(struct sockaddr_storage *ss, struct netaddr *ssmask) +{ + if (ss->ss_family == AF_INET) { + struct sockaddr_in *ssin = (struct sockaddr_in *)ss; + struct sockaddr_in *ssinmask = (struct sockaddr_in *)&ssmask->ss; + + if ((ssin->sin_addr.s_addr & ssinmask->sin_addr.s_addr) == + ssinmask->sin_addr.s_addr) + return (1); + return (0); + } + + if (ss->ss_family == AF_INET6) { + struct in6_addr *in; + struct in6_addr *inmask; + struct in6_addr mask; + int i; + + bzero(&mask, sizeof(mask)); + for (i = 0; i < (128 - ssmask->masked) / 8; i++) + mask.s6_addr[i] = 0xff; + i = ssmask->masked % 8; + if (i) + mask.s6_addr[ssmask->masked / 8] = 0xff00 >> i; + + in = &((struct sockaddr_in6 *)ss)->sin6_addr; + inmask = &((struct sockaddr_in6 *)&ssmask->ss)->sin6_addr; + + for (i = 0; i < 16; i++) { + if ((in->s6_addr[i] & mask.s6_addr[i]) != + inmask->s6_addr[i]) + return (0); + } + return (1); + } + + return (0); +} |