/* $OpenBSD: table_static.c,v 1.33 2021/06/14 17:58:16 eric Exp $ */ /* * Copyright (c) 2013 Eric Faurot * Copyright (c) 2012 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 "smtpd.h" #include "log.h" struct table_static_priv { int type; struct dict dict; void *iter; }; /* static backend */ static int table_static_config(struct table *); static int table_static_add(struct table *, const char *, const char *); static void table_static_dump(struct table *); static int table_static_update(struct table *); static int table_static_open(struct table *); static int table_static_lookup(struct table *, enum table_service, const char *, char **); static int table_static_fetch(struct table *, enum table_service, char **); static void table_static_close(struct table *); struct table_backend table_backend_static = { "static", K_ALIAS|K_CREDENTIALS|K_DOMAIN|K_NETADDR|K_USERINFO| K_SOURCE|K_MAILADDR|K_ADDRNAME|K_MAILADDRMAP|K_RELAYHOST| K_STRING|K_REGEX, table_static_config, table_static_add, table_static_dump, table_static_open, table_static_update, table_static_close, table_static_lookup, table_static_fetch }; static struct keycmp { enum table_service service; int (*func)(const char *, const char *); } keycmp[] = { { K_DOMAIN, table_domain_match }, { K_NETADDR, table_netaddr_match }, { K_MAILADDR, table_mailaddr_match }, { K_REGEX, table_regex_match }, }; static void table_static_priv_free(struct table_static_priv *priv) { void *p; while (dict_poproot(&priv->dict, (void **)&p)) if (p != priv) free(p); free(priv); } static int table_static_priv_add(struct table_static_priv *priv, const char *key, const char *val) { char lkey[1024]; void *old, *new = NULL; if (!lowercase(lkey, key, sizeof lkey)) { errno = ENAMETOOLONG; return (-1); } if (val) { new = strdup(val); if (new == NULL) return (-1); } /* use priv if value is null, so we can detect duplicate entries */ old = dict_set(&priv->dict, lkey, new ? new : priv); if (old) { if (old != priv) free(old); return (1); } return (0); } static int table_static_priv_load(struct table_static_priv *priv, const char *path) { FILE *fp; char *buf = NULL, *p; int lineno = 0; size_t sz = 0; ssize_t flen; char *keyp; char *valp; int ret = 0; if ((fp = fopen(path, "r")) == NULL) { log_warn("%s: fopen", path); return 0; } while ((flen = getline(&buf, &sz, fp)) != -1) { lineno++; if (buf[flen - 1] == '\n') buf[--flen] = '\0'; keyp = buf; while (isspace((unsigned char)*keyp)) { ++keyp; --flen; } if (*keyp == '\0') continue; while (isspace((unsigned char)keyp[flen - 1])) keyp[--flen] = '\0'; if (*keyp == '#') { if (priv->type == T_NONE) { keyp++; while (isspace((unsigned char)*keyp)) ++keyp; if (!strcmp(keyp, "@list")) priv->type = T_LIST; } continue; } if (priv->type == T_NONE) { for (p = keyp; *p; p++) { if (*p == ' ' || *p == '\t' || *p == ':') { priv->type = T_HASH; break; } } if (priv->type == T_NONE) priv->type = T_LIST; } if (priv->type == T_LIST) { table_static_priv_add(priv, keyp, NULL); continue; } /* T_HASH */ valp = keyp; strsep(&valp, " \t:"); if (valp) { while (*valp) { if (!isspace((unsigned char)*valp) && !(*valp == ':' && isspace((unsigned char)*(valp + 1)))) break; ++valp; } if (*valp == '\0') valp = NULL; } if (valp == NULL) { log_warnx("%s: invalid map entry line %d", path, lineno); goto end; } table_static_priv_add(priv, keyp, valp); } if (ferror(fp)) { log_warn("%s: getline", path); goto end; } /* Accept empty alias files; treat them as hashes */ if (priv->type == T_NONE) priv->type = T_HASH; ret = 1; end: free(buf); fclose(fp); return ret; } static int table_static_config(struct table *t) { struct table_static_priv *priv, *old; /* already up, and no config file? ok */ if (t->t_handle && *t->t_config == '\0') return 1; /* new config */ priv = calloc(1, sizeof(*priv)); if (priv == NULL) return 0; priv->type = t->t_type; dict_init(&priv->dict); if (*t->t_config) { /* load the config file */ if (table_static_priv_load(priv, t->t_config) == 0) { table_static_priv_free(priv); return 0; } } if ((old = t->t_handle)) table_static_priv_free(old); t->t_handle = priv; t->t_type = priv->type; return 1; } static int table_static_add(struct table *table, const char *key, const char *val) { struct table_static_priv *priv = table->t_handle; int r; /* cannot add to a table read from a file */ if (*table->t_config) return 0; if (table->t_type == T_NONE) table->t_type = val ? T_HASH : T_LIST; else if (table->t_type == T_LIST && val) return 0; else if (table->t_type == T_HASH && val == NULL) return 0; if (priv == NULL) { if (table_static_config(table) == 0) return 0; priv = table->t_handle; } r = table_static_priv_add(priv, key, val); if (r == -1) return 0; return 1; } static void table_static_dump(struct table *table) { struct table_static_priv *priv = table->t_handle; const char *key; char *value; void *iter; iter = NULL; while (dict_iter(&priv->dict, &iter, &key, (void**)&value)) { if (value && (void*)value != (void*)priv) log_debug(" \"%s\" -> \"%s\"", key, value); else log_debug(" \"%s\"", key); } } static int table_static_update(struct table *table) { if (table_static_config(table) == 1) { log_info("info: Table \"%s\" successfully updated", table->t_name); return 1; } log_info("info: Failed to update table \"%s\"", table->t_name); return 0; } static int table_static_open(struct table *table) { if (table->t_handle == NULL) return table_static_config(table); return 1; } static void table_static_close(struct table *table) { struct table_static_priv *priv = table->t_handle; if (priv) table_static_priv_free(priv); table->t_handle = NULL; } static int table_static_lookup(struct table *table, enum table_service service, const char *key, char **dst) { struct table_static_priv *priv = table->t_handle; char *line; int ret; int (*match)(const char *, const char *) = NULL; size_t i; void *iter; const char *k; char *v; for (i = 0; i < nitems(keycmp); ++i) if (keycmp[i].service == service) match = keycmp[i].func; line = NULL; iter = NULL; ret = 0; while (dict_iter(&priv->dict, &iter, &k, (void **)&v)) { if (match) { if (match(key, k)) { line = v; ret = 1; } } else { if (strcmp(key, k) == 0) { line = v; ret = 1; } } if (ret) break; } if (dst == NULL) return ret ? 1 : 0; if (ret == 0) return 0; *dst = strdup(line); if (*dst == NULL) return -1; return 1; } static int table_static_fetch(struct table *t, enum table_service service, char **dst) { struct table_static_priv *priv = t->t_handle; const char *k; if (!dict_iter(&priv->dict, &priv->iter, &k, (void **)NULL)) { priv->iter = NULL; if (!dict_iter(&priv->dict, &priv->iter, &k, (void **)NULL)) return 0; } *dst = strdup(k); if (*dst == NULL) return -1; return 1; }