/* $OpenBSD: expand.c,v 1.25 2013/12/26 17:25:32 eric Exp $ */ /* * Copyright (c) 2009 Gilles Chehade * Copyright (c) 2012 Eric Faurot * * 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 "smtpd.h" #include "log.h" static const char *expandnode_info(struct expandnode *); struct expandnode * expand_lookup(struct expand *expand, struct expandnode *key) { return RB_FIND(expandtree, &expand->tree, key); } int expand_to_text(struct expand *expand, char *buf, size_t sz) { struct expandnode *xn; buf[0] = '\0'; RB_FOREACH(xn, expandtree, &expand->tree) { if (buf[0]) strlcat(buf, ", ", sz); strlcat(buf, expandnode_to_text(xn), sz); } return 1; } void expand_insert(struct expand *expand, struct expandnode *node) { struct expandnode *xn; node->parent = expand->parent; log_trace(TRACE_EXPAND, "expand: %p: expand_insert() called for %s", expand, expandnode_info(node)); if (node->type == EXPAND_USERNAME && expand->parent && expand->parent->type == EXPAND_USERNAME && !strcmp(expand->parent->u.user, node->u.user)) { log_trace(TRACE_EXPAND, "expand: %p: setting sameuser = 1", expand); node->sameuser = 1; } if (expand_lookup(expand, node)) { log_trace(TRACE_EXPAND, "expand: %p: node found, discarding", expand); return; } xn = xmemdup(node, sizeof *xn, "expand_insert"); xn->rule = expand->rule; xn->parent = expand->parent; xn->alias = expand->alias; if (xn->parent) xn->depth = xn->parent->depth + 1; else xn->depth = 0; RB_INSERT(expandtree, &expand->tree, xn); if (expand->queue) TAILQ_INSERT_TAIL(expand->queue, xn, tq_entry); expand->nb_nodes++; log_trace(TRACE_EXPAND, "expand: %p: inserted node %p", expand, xn); } void expand_clear(struct expand *expand) { struct expandnode *xn; log_trace(TRACE_EXPAND, "expand: %p: clearing expand tree", expand); if (expand->queue) while ((xn = TAILQ_FIRST(expand->queue))) TAILQ_REMOVE(expand->queue, xn, tq_entry); while ((xn = RB_ROOT(&expand->tree)) != NULL) { RB_REMOVE(expandtree, &expand->tree, xn); free(xn); } } void expand_free(struct expand *expand) { expand_clear(expand); log_trace(TRACE_EXPAND, "expand: %p: freeing expand tree", expand); free(expand); } int expand_cmp(struct expandnode *e1, struct expandnode *e2) { struct expandnode *p1, *p2; int r; if (e1->type < e2->type) return -1; if (e1->type > e2->type) return 1; if (e1->sameuser < e2->sameuser) return -1; if (e1->sameuser > e2->sameuser) return 1; if (e1->mapping < e2->mapping) return -1; if (e1->mapping > e2->mapping) return 1; if (e1->userbase < e2->userbase) return -1; if (e1->userbase > e2->userbase) return 1; r = memcmp(&e1->u, &e2->u, sizeof(e1->u)); if (r) return (r); if (e1->parent == e2->parent) return (0); if (e1->parent == NULL) return (-1); if (e2->parent == NULL) return (1); /* * The same node can be expanded in for different dest context. * Wen need to distinguish between those. */ for(p1 = e1->parent; p1->type != EXPAND_ADDRESS; p1 = p1->parent) ; for(p2 = e2->parent; p2->type != EXPAND_ADDRESS; p2 = p2->parent) ; if (p1 < p2) return (-1); if (p1 > p2) return (1); if (e1->type != EXPAND_FILENAME && e1->type != EXPAND_FILTER) return (0); /* * For external delivery, we need to distinguish between users. * If we can't find a username, we assume it is _smtpd. */ for(p1 = e1->parent; p1 && p1->type != EXPAND_USERNAME; p1 = p1->parent) ; for(p2 = e2->parent; p2 && p2->type != EXPAND_USERNAME; p2 = p2->parent) ; if (p1 < p2) return (-1); if (p1 > p2) return (1); return (0); } static int expand_line_split(char **line, char **ret) { static char buffer[SMTPD_MAXLINESIZE]; int esc, i, dq, sq; char *s; memset(buffer, 0, sizeof buffer); esc = dq = sq = i = 0; for (s = *line; (*s) && (i < (int)sizeof(buffer)); ++s) { if (esc) { buffer[i++] = *s; esc = 0; continue; } if (*s == '\\') { esc = 1; continue; } if (*s == ',' && !dq && !sq) { *ret = buffer; *line = s+1; return (1); } buffer[i++] = *s; esc = 0; if (*s == '"' && !sq) dq ^= 1; if (*s == '\'' && !dq) sq ^= 1; } if (esc || dq || sq || i == sizeof(buffer)) return (-1); *ret = buffer; *line = s; return (i ? 1 : 0); } int expand_line(struct expand *expand, const char *s, int do_includes) { struct expandnode xn; char buffer[SMTPD_MAXLINESIZE]; char *p, *subrcpt; int ret; memset(buffer, 0, sizeof buffer); if (strlcpy(buffer, s, sizeof buffer) >= sizeof buffer) return 0; p = buffer; while ((ret = expand_line_split(&p, &subrcpt)) > 0) { subrcpt = strip(subrcpt); if (subrcpt[0] == '\0') continue; if (! text_to_expandnode(&xn, subrcpt)) return 0; if (! do_includes) if (xn.type == EXPAND_INCLUDE) continue; expand_insert(expand, &xn); } if (ret >= 0) return 1; /* expand_line_split() returned < 0 */ return 0; } static const char * expandnode_info(struct expandnode *e) { static char buffer[1024]; const char *type = NULL; const char *value = NULL; char tmp[64]; switch (e->type) { case EXPAND_FILTER: type = "filter"; break; case EXPAND_FILENAME: type = "filename"; break; case EXPAND_INCLUDE: type = "include"; break; case EXPAND_USERNAME: type = "username"; break; case EXPAND_ADDRESS: type = "address"; break; case EXPAND_ERROR: type = "error"; break; case EXPAND_INVALID: default: return NULL; } if ((value = expandnode_to_text(e)) == NULL) return NULL; strlcpy(buffer, type, sizeof buffer); strlcat(buffer, ":", sizeof buffer); if (strlcat(buffer, value, sizeof buffer) >= sizeof buffer) return NULL; snprintf(tmp, sizeof(tmp), "[parent=%p", e->parent); if (strlcat(buffer, tmp, sizeof buffer) >= sizeof buffer) return NULL; if (e->mapping) { strlcat(buffer, ", mapping=", sizeof buffer); strlcat(buffer, e->mapping->t_name, sizeof buffer); } if (e->userbase) { strlcat(buffer, ", userbase=", sizeof buffer); strlcat(buffer, e->userbase->t_name, sizeof buffer); } if (strlcat(buffer, "]", sizeof buffer) >= sizeof buffer) return NULL; return buffer; } RB_GENERATE(expandtree, expandnode, entry, expand_cmp);