summaryrefslogtreecommitdiffstats
path: root/src/util/dict_cidr.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:46:30 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 01:46:30 +0000
commitb5896ba9f6047e7031e2bdee0622d543e11a6734 (patch)
treefd7b460593a2fee1be579bec5697e6d887ea3421 /src/util/dict_cidr.c
parentInitial commit. (diff)
downloadpostfix-b5896ba9f6047e7031e2bdee0622d543e11a6734.tar.xz
postfix-b5896ba9f6047e7031e2bdee0622d543e11a6734.zip
Adding upstream version 3.4.23.upstream/3.4.23upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/util/dict_cidr.c363
1 files changed, 363 insertions, 0 deletions
diff --git a/src/util/dict_cidr.c b/src/util/dict_cidr.c
new file mode 100644
index 0000000..3376dcd
--- /dev/null
+++ b/src/util/dict_cidr.c
@@ -0,0 +1,363 @@
+/*++
+/* NAME
+/* dict_cidr 3
+/* SUMMARY
+/* Dictionary interface for CIDR data
+/* SYNOPSIS
+/* #include <dict_cidr.h>
+/*
+/* DICT *dict_cidr_open(name, open_flags, dict_flags)
+/* const char *name;
+/* int open_flags;
+/* int dict_flags;
+/* DESCRIPTION
+/* dict_cidr_open() opens the named file and stores
+/* the key/value pairs where the key must be either a
+/* "naked" IP address or a netblock in CIDR notation.
+/* SEE ALSO
+/* dict(3) generic dictionary manager
+/* AUTHOR(S)
+/* Jozsef Kadlecsik
+/* kadlec@blackhole.kfki.hu
+/* KFKI Research Institute for Particle and Nuclear Physics
+/* POB. 49
+/* 1525 Budapest, Hungary
+/*
+/* Wietse Venema
+/* IBM T.J. Watson Research
+/* P.O. Box 704
+/* Yorktown Heights, NY 10598, USA
+/*
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+/* System library. */
+
+#include <sys_defs.h>
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+/* Utility library. */
+
+#include <mymalloc.h>
+#include <msg.h>
+#include <vstream.h>
+#include <vstring.h>
+#include <stringops.h>
+#include <readlline.h>
+#include <dict.h>
+#include <myaddrinfo.h>
+#include <cidr_match.h>
+#include <dict_cidr.h>
+#include <warn_stat.h>
+#include <mvect.h>
+
+/* Application-specific. */
+
+ /*
+ * Each rule in a CIDR table is parsed and stored in a linked list.
+ */
+typedef struct DICT_CIDR_ENTRY {
+ CIDR_MATCH cidr_info; /* must be first */
+ char *value; /* lookup result */
+ int lineno;
+} DICT_CIDR_ENTRY;
+
+typedef struct {
+ DICT dict; /* generic members */
+ DICT_CIDR_ENTRY *head; /* first entry */
+} DICT_CIDR;
+
+/* dict_cidr_lookup - CIDR table lookup */
+
+static const char *dict_cidr_lookup(DICT *dict, const char *key)
+{
+ DICT_CIDR *dict_cidr = (DICT_CIDR *) dict;
+ DICT_CIDR_ENTRY *entry;
+
+ if (msg_verbose)
+ msg_info("dict_cidr_lookup: %s: %s", dict->name, key);
+
+ dict->error = 0;
+
+ if ((entry = (DICT_CIDR_ENTRY *)
+ cidr_match_execute(&(dict_cidr->head->cidr_info), key)) != 0)
+ return (entry->value);
+ return (0);
+}
+
+/* dict_cidr_close - close the CIDR table */
+
+static void dict_cidr_close(DICT *dict)
+{
+ DICT_CIDR *dict_cidr = (DICT_CIDR *) dict;
+ DICT_CIDR_ENTRY *entry;
+ DICT_CIDR_ENTRY *next;
+
+ for (entry = dict_cidr->head; entry; entry = next) {
+ next = (DICT_CIDR_ENTRY *) entry->cidr_info.next;
+ myfree(entry->value);
+ myfree((void *) entry);
+ }
+ dict_free(dict);
+}
+
+/* dict_cidr_parse_rule - parse CIDR table rule into network, mask and value */
+
+static DICT_CIDR_ENTRY *dict_cidr_parse_rule(DICT *dict, char *p, int lineno,
+ int nesting, VSTRING *why)
+{
+ DICT_CIDR_ENTRY *rule;
+ char *pattern;
+ char *value;
+ CIDR_MATCH cidr_info;
+ MAI_HOSTADDR_STR hostaddr;
+ int match = 1;
+
+ /*
+ * IF must be followed by a pattern.
+ */
+ if (strncasecmp(p, "IF", 2) == 0 && !ISALNUM(p[2])) {
+ p += 2;
+ for (;;) {
+ if (*p == '!')
+ match = !match;
+ else if (!ISSPACE(*p))
+ break;
+ p++;
+ }
+ if (*p == 0) {
+ vstring_sprintf(why, "no address pattern");
+ return (0);
+ }
+ trimblanks(p, 0)[0] = 0; /* Trim trailing blanks */
+ if (cidr_match_parse_if(&cidr_info, p, match, why) != 0)
+ return (0);
+ value = "";
+ }
+
+ /*
+ * ENDIF must not be followed by other text.
+ */
+ else if (strncasecmp(p, "ENDIF", 5) == 0 && !ISALNUM(p[5])) {
+ p += 5;
+ while (*p && ISSPACE(*p)) /* Skip whitespace */
+ p++;
+ if (*p != 0) {
+ vstring_sprintf(why, "garbage after ENDIF");
+ return (0);
+ }
+ if (nesting == 0) {
+ vstring_sprintf(why, "ENDIF without IF");
+ return (0);
+ }
+ cidr_match_endif(&cidr_info);
+ value = "";
+ }
+
+ /*
+ * An address pattern.
+ */
+ else {
+
+ /*
+ * Process negation operators.
+ */
+ for (;;) {
+ if (*p == '!')
+ match = !match;
+ else if (!ISSPACE(*p))
+ break;
+ p++;
+ }
+
+ /*
+ * Split the rule into key and value. We already eliminated leading
+ * whitespace, comments, empty lines or lines with whitespace only.
+ * This means a null key can't happen but we will handle this anyway.
+ */
+ pattern = p;
+ while (*p && !ISSPACE(*p)) /* Skip over key */
+ p++;
+ if (*p) /* Terminate key */
+ *p++ = 0;
+ while (*p && ISSPACE(*p)) /* Skip whitespace */
+ p++;
+ value = p;
+ trimblanks(value, 0)[0] = 0; /* Trim trailing blanks */
+ if (*pattern == 0) {
+ vstring_sprintf(why, "no address pattern");
+ return (0);
+ }
+
+ /*
+ * Parse the pattern, destroying it in the process.
+ */
+ if (cidr_match_parse(&cidr_info, pattern, match, why) != 0)
+ return (0);
+
+ if (*value == 0) {
+ vstring_sprintf(why, "no lookup result");
+ return (0);
+ }
+ }
+
+ /*
+ * Optionally replace the value file the contents of a file.
+ */
+ if (dict->flags & DICT_FLAG_SRC_RHS_IS_FILE) {
+ VSTRING *base64_buf;
+ char *err;
+
+ if ((base64_buf = dict_file_to_b64(dict, value)) == 0) {
+ err = dict_file_get_error(dict);
+ vstring_strcpy(why, err);
+ myfree(err);
+ return (0);
+ }
+ value = vstring_str(base64_buf);
+ }
+
+ /*
+ * Bundle up the result.
+ */
+ rule = (DICT_CIDR_ENTRY *) mymalloc(sizeof(DICT_CIDR_ENTRY));
+ rule->cidr_info = cidr_info;
+ rule->value = mystrdup(value);
+ rule->lineno = lineno;
+
+ if (msg_verbose) {
+ if (inet_ntop(cidr_info.addr_family, cidr_info.net_bytes,
+ hostaddr.buf, sizeof(hostaddr.buf)) == 0)
+ msg_fatal("inet_ntop: %m");
+ msg_info("dict_cidr_open: add %s/%d %s",
+ hostaddr.buf, cidr_info.mask_shift, rule->value);
+ }
+ return (rule);
+}
+
+/* dict_cidr_open - parse CIDR table */
+
+DICT *dict_cidr_open(const char *mapname, int open_flags, int dict_flags)
+{
+ const char myname[] = "dict_cidr_open";
+ DICT_CIDR *dict_cidr;
+ VSTREAM *map_fp = 0;
+ struct stat st;
+ VSTRING *line_buffer = 0;
+ VSTRING *why = 0;
+ DICT_CIDR_ENTRY *rule;
+ DICT_CIDR_ENTRY *last_rule = 0;
+ int last_line = 0;
+ int lineno;
+ int nesting = 0;
+ DICT_CIDR_ENTRY **rule_stack = 0;
+ MVECT mvect;
+
+ /*
+ * Let the optimizer worry about eliminating redundant code.
+ */
+#define DICT_CIDR_OPEN_RETURN(d) do { \
+ DICT *__d = (d); \
+ if (map_fp != 0 && vstream_fclose(map_fp)) \
+ msg_fatal("cidr map %s: read error: %m", mapname); \
+ if (line_buffer != 0) \
+ vstring_free(line_buffer); \
+ if (why != 0) \
+ vstring_free(why); \
+ return (__d); \
+ } while (0)
+
+ /*
+ * Sanity checks.
+ */
+ if (open_flags != O_RDONLY)
+ DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname,
+ open_flags, dict_flags,
+ "%s:%s map requires O_RDONLY access mode",
+ DICT_TYPE_CIDR, mapname));
+
+ /*
+ * Open the configuration file.
+ */
+ if ((map_fp = vstream_fopen(mapname, O_RDONLY, 0)) == 0)
+ DICT_CIDR_OPEN_RETURN(dict_surrogate(DICT_TYPE_CIDR, mapname,
+ open_flags, dict_flags,
+ "open %s: %m", mapname));
+ if (fstat(vstream_fileno(map_fp), &st) < 0)
+ msg_fatal("fstat %s: %m", mapname);
+
+ line_buffer = vstring_alloc(100);
+ why = vstring_alloc(100);
+
+ /*
+ * XXX Eliminate unnecessary queries by setting a flag that says "this
+ * map matches network addresses only".
+ */
+ dict_cidr = (DICT_CIDR *) dict_alloc(DICT_TYPE_CIDR, mapname,
+ sizeof(*dict_cidr));
+ dict_cidr->dict.lookup = dict_cidr_lookup;
+ dict_cidr->dict.close = dict_cidr_close;
+ dict_cidr->dict.flags = dict_flags | DICT_FLAG_PATTERN;
+ dict_cidr->head = 0;
+
+ dict_cidr->dict.owner.uid = st.st_uid;
+ dict_cidr->dict.owner.status = (st.st_uid != 0);
+
+ while (readllines(line_buffer, map_fp, &last_line, &lineno)) {
+ rule = dict_cidr_parse_rule(&dict_cidr->dict,
+ vstring_str(line_buffer), lineno,
+ nesting, why);
+ if (rule == 0) {
+ msg_warn("cidr map %s, line %d: %s: skipping this rule",
+ mapname, lineno, vstring_str(why));
+ continue;
+ }
+ if (rule->cidr_info.op == CIDR_MATCH_OP_IF) {
+ if (rule_stack == 0)
+ rule_stack = (DICT_CIDR_ENTRY **) mvect_alloc(&mvect,
+ sizeof(*rule_stack), nesting + 1,
+ (MVECT_FN) 0, (MVECT_FN) 0);
+ else
+ rule_stack =
+ (DICT_CIDR_ENTRY **) mvect_realloc(&mvect, nesting + 1);
+ rule_stack[nesting] = rule;
+ nesting++;
+ } else if (rule->cidr_info.op == CIDR_MATCH_OP_ENDIF) {
+ DICT_CIDR_ENTRY *if_rule;
+
+ if (nesting-- <= 0)
+ /* Already handled in dict_cidr_parse_rule(). */
+ msg_panic("%s: ENDIF without IF", myname);
+ if_rule = rule_stack[nesting];
+ if (if_rule->cidr_info.op != CIDR_MATCH_OP_IF)
+ msg_panic("%s: unexpected rule stack element type %d",
+ myname, if_rule->cidr_info.op);
+ if_rule->cidr_info.block_end = &(rule->cidr_info);
+ }
+ if (last_rule == 0)
+ dict_cidr->head = rule;
+ else
+ last_rule->cidr_info.next = &(rule->cidr_info);
+ last_rule = rule;
+ }
+
+ while (nesting-- > 0)
+ msg_warn("cidr map %s, line %d: IF has no matching ENDIF",
+ mapname, rule_stack[nesting]->lineno);
+
+ if (rule_stack)
+ (void) mvect_free(&mvect);
+
+ dict_file_purge_buffers(&dict_cidr->dict);
+ DICT_CIDR_OPEN_RETURN(DICT_DEBUG (&dict_cidr->dict));
+}