summaryrefslogtreecommitdiffstats
path: root/src/global/compat_level.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/global/compat_level.c')
-rw-r--r--src/global/compat_level.c461
1 files changed, 461 insertions, 0 deletions
diff --git a/src/global/compat_level.c b/src/global/compat_level.c
new file mode 100644
index 0000000..af1cbbf
--- /dev/null
+++ b/src/global/compat_level.c
@@ -0,0 +1,461 @@
+/*++
+/* NAME
+/* compat_level 3
+/* SUMMARY
+/* compatibility_level support
+/* SYNOPSIS
+/* #include <compat_level.h>
+/*
+/* void compat_level_relop_register(void)
+/*
+/* long compat_level_from_string(
+/* const char *str,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/*
+/* long compat_level_from_numbers(
+/* long major,
+/* long minor,
+/* long patch,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/*
+/* const char *compat_level_to_string(
+/* long compat_level,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/* AUXULIARY FUNCTIONS
+/* long compat_level_from_major_minor(
+/* long major,
+/* long minor,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/*
+/* long compat_level_from_major(
+/* long major,
+/* void PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
+/* DESCRIPTION
+/* This module supports compatibility level syntax with
+/* "major.minor.patch" but will also accept the shorter forms
+/* "major.minor" and "major" (missing members default to zero).
+/* Compatibility levels with multiple numbers cannot be compared
+/* as strings or as floating-point numbers (for example, "3.10"
+/* would be smaller than "3.9").
+/*
+/* The major number can range from [0..2047] inclusive (11
+/* bits) or more, while the minor and patch numbers can range
+/* from [0..1023] inclusive (10 bits).
+/*
+/* compat_level_from_string() converts a compatibility level
+/* from string form to numerical form for easy comparison.
+/* Valid input results in a non-negative result. In case of
+/* error, compat_level_from_string() reports the problem with
+/* the provided function, and returns -1 if that function does
+/* not terminate execution.
+/*
+/* compat_level_from_numbers() creates an internal-form
+/* compatibility level from distinct numbers. Valid input
+/* results in a non-negative result. In case of error,
+/* compat_level_from_numbers() reports the problem with the
+/* provided function, and returns -1 if that function does not
+/* terminate execution.
+/*
+/* The functions compat_level_from_major_minor() and
+/* compat_level_from_major() are helpers that default the missing
+/* information to zeroes.
+/*
+/* compat_level_to_string() converts a compatibility level
+/* from numerical form to canonical string form. Valid input
+/* results in a non-null result. In case of error,
+/* compat_level_to_string() reports the problem with the
+/* provided function, and returns a null pointer if that
+/* function does not terminate execution.
+/*
+/* compat_level_relop_register() registers a mac_expand() callback
+/* that registers operators such as <=level, >level, that compare
+/* compatibility levels. This function should be called before
+/* loading parameter settings from main.cf.
+/* DIAGNOSTICS
+/* info, .., panic: bad compatibility_level syntax.
+/* BUGS
+/* The patch and minor fields range from 0..1023 (10 bits) while
+/* the major field ranges from 0..COMPAT_MAJOR_SHIFT47 or more
+/* (11 bits or more).
+/*
+/* This would be a great use case for functions returning
+/* StatusOr<compat_level_t> or StatusOr<string>, but is it a bit
+/* late for a port to C++.
+/* LICENSE
+/* .ad
+/* .fi
+/* The Secure Mailer license must be distributed with this software.
+/* AUTHOR(S)
+/* Wietse Venema
+/* Google, Inc.
+/* 111 8th Avenue
+/* New York, NY 10011, USA
+/*--*/
+
+ /*
+ * System library.
+ */
+#include <sys_defs.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+
+ /*
+ * Utility library.
+ */
+#include <mac_expand.h>
+#include <msg.h>
+#include <sane_strtol.h>
+
+ /*
+ * For easy comparison we convert a three-number compatibility level into
+ * just one number, using different bit ranges for the major version, minor
+ * version, and patch level.
+ *
+ * We use long integers because standard C guarantees that long has at last 32
+ * bits instead of int which may have only 16 bits (though it is unlikely
+ * that Postfix would run on such systems). That gives us 11 or more bits
+ * for the major version, and 10 bits for minor the version and patchlevel.
+ *
+ * Below are all the encoding details in one place. This is easier to verify
+ * than wading through code.
+ */
+#define COMPAT_MAJOR_SHIFT \
+ (COMPAT_MINOR_SHIFT + COMPAT_MINOR_WIDTH)
+
+#define COMPAT_MINOR_SHIFT COMPAT_PATCH_WIDTH
+#define COMPAT_MINOR_BITS 0x3ff
+#define COMPAT_MINOR_WIDTH 10
+
+#define COMPAT_PATCH_BITS 0x3ff
+#define COMPAT_PATCH_WIDTH 10
+
+#define GOOD_MAJOR(m) ((m) >= 0 && (m) <= (LONG_MAX >> COMPAT_MAJOR_SHIFT))
+#define GOOD_MINOR(m) ((m) >= 0 && (m) <= COMPAT_MINOR_BITS)
+#define GOOD_PATCH(p) ((p) >= 0 && (p) <= COMPAT_PATCH_BITS)
+
+#define ENCODE_MAJOR(m) ((m) << COMPAT_MAJOR_SHIFT)
+#define ENCODE_MINOR(m) ((m) << COMPAT_MINOR_SHIFT)
+#define ENCODE_PATCH(p) (p)
+
+#define DECODE_MAJOR(l) ((l) >> COMPAT_MAJOR_SHIFT)
+#define DECODE_MINOR(l) (((l) >> COMPAT_MINOR_SHIFT) & COMPAT_MINOR_BITS)
+#define DECODE_PATCH(l) ((l) & COMPAT_PATCH_BITS)
+
+ /*
+ * Global library.
+ */
+#include <compat_level.h>
+
+/* compat_level_from_string - convert major[.minor] to comparable type */
+
+long compat_level_from_string(const char *str,
+ void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
+{
+ long major, minor, patch, res = 0;
+ const char *start;
+ char *remainder;
+
+ start = str;
+ major = sane_strtol(start, &remainder, 10);
+ if (start < remainder && (*remainder == 0 || *remainder == '.')
+ && errno != ERANGE && GOOD_MAJOR(major)) {
+ res = ENCODE_MAJOR(major);
+ if (*remainder == 0)
+ return res;
+ start = remainder + 1;
+ minor = sane_strtol(start, &remainder, 10);
+ if (start < remainder && (*remainder == 0 || *remainder == '.')
+ && errno != ERANGE && GOOD_MINOR(minor)) {
+ res |= ENCODE_MINOR(minor);
+ if (*remainder == 0)
+ return (res);
+ start = remainder + 1;
+ patch = sane_strtol(start, &remainder, 10);
+ if (start < remainder && *remainder == 0 && errno != ERANGE
+ && GOOD_PATCH(patch)) {
+ return (res | ENCODE_PATCH(patch));
+ }
+ }
+ }
+ msg_fn("malformed compatibility level syntax: \"%s\"", str);
+ return (-1);
+}
+
+/* compat_level_from_numbers - internal form from numbers */
+
+long compat_level_from_numbers(long major, long minor, long patch,
+ void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
+{
+ const char myname[] = "compat_level_from_numbers";
+
+ /*
+ * Sanity checks.
+ */
+ if (!GOOD_MAJOR(major)) {
+ msg_fn("%s: bad major version: %ld", myname, major);
+ return (-1);
+ }
+ if (!GOOD_MINOR(minor)) {
+ msg_fn("%s: bad minor version: %ld", myname, minor);
+ return (-1);
+ }
+ if (!GOOD_PATCH(patch)) {
+ msg_fn("%s: bad patch level: %ld", myname, patch);
+ return (-1);
+ }
+
+ /*
+ * Conversion.
+ */
+ return (ENCODE_MAJOR(major) | ENCODE_MINOR(minor) | ENCODE_PATCH(patch));
+}
+
+/* compat_level_to_string - pretty-print a compatibility level */
+
+const char *compat_level_to_string(long compat_level,
+ void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
+{
+ const char myname[] = "compat_level_to_string";
+ static VSTRING *buf;
+ long major;
+ long minor;
+ long patch;
+
+ /*
+ * Sanity check.
+ */
+ if (compat_level < 0) {
+ msg_fn("%s: bad compatibility level: %ld", myname, compat_level);
+ return (0);
+ }
+
+ /*
+ * Compatibility levels 0..2 have no minor or patch level.
+ */
+ if (buf == 0)
+ buf = vstring_alloc(10);
+ major = DECODE_MAJOR(compat_level);
+ if (!GOOD_MAJOR(major)) {
+ msg_fn("%s: bad compatibility major level: %ld", myname, compat_level);
+ return (0);
+ }
+ vstring_sprintf(buf, "%ld", major);
+ if (major > 2) {
+
+ /*
+ * Expect that major.minor will be common.
+ */
+ minor = DECODE_MINOR(compat_level);
+ vstring_sprintf_append(buf, ".%ld", minor);
+
+ /*
+ * Expect that major.minor.patch will be rare.
+ */
+ patch = DECODE_PATCH(compat_level);
+ if (patch)
+ vstring_sprintf_append(buf, ".%ld", patch);
+ }
+ return (vstring_str(buf));
+}
+
+/* compat_relop_eval - mac_expand callback */
+
+static MAC_EXP_OP_RES compat_relop_eval(const char *left_str, int relop,
+ const char *rite_str)
+{
+ const char myname[] = "compat_relop_eval";
+ long left_val, rite_val, delta;
+
+ /*
+ * Negative result means error.
+ */
+ if ((left_val = compat_level_from_string(left_str, msg_warn)) < 0
+ || (rite_val = compat_level_from_string(rite_str, msg_warn)) < 0)
+ return (MAC_EXP_OP_RES_ERROR);
+
+ /*
+ * Valid result. The difference between non-negative numbers will no
+ * overflow.
+ */
+ delta = left_val - rite_val;
+
+ switch (relop) {
+ case MAC_EXP_OP_TOK_EQ:
+ return (mac_exp_op_res_bool[delta == 0]);
+ case MAC_EXP_OP_TOK_NE:
+ return (mac_exp_op_res_bool[delta != 0]);
+ case MAC_EXP_OP_TOK_LT:
+ return (mac_exp_op_res_bool[delta < 0]);
+ case MAC_EXP_OP_TOK_LE:
+ return (mac_exp_op_res_bool[delta <= 0]);
+ case MAC_EXP_OP_TOK_GE:
+ return (mac_exp_op_res_bool[delta >= 0]);
+ case MAC_EXP_OP_TOK_GT:
+ return (mac_exp_op_res_bool[delta > 0]);
+ default:
+ msg_panic("%s: unknown operator: %d",
+ myname, relop);
+ }
+}
+
+/* compat_level_register - register comparison operators */
+
+void compat_level_relop_register(void)
+{
+ int compat_level_relops[] = {
+ MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE,
+ MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE,
+ MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE,
+ 0,
+ };
+ static int register_done;
+
+ if (register_done++ == 0)
+ mac_expand_add_relop(compat_level_relops, "level", compat_relop_eval);
+}
+
+#ifdef TEST
+#include <unistd.h>
+
+#include <htable.h>
+#include <mymalloc.h>
+#include <stringops.h>
+#include <vstring.h>
+#include <vstream.h>
+#include <vstring_vstream.h>
+
+static const char *lookup(const char *name, int unused_mode, void *context)
+{
+ HTABLE *table = (HTABLE *) context;
+
+ return (htable_find(table, name));
+}
+
+static void test_expand(void)
+{
+ VSTRING *buf = vstring_alloc(100);
+ VSTRING *result = vstring_alloc(100);
+ char *cp;
+ char *name;
+ char *value;
+ HTABLE *table;
+ int stat;
+
+ /*
+ * Add relops that compare string lengths instead of content.
+ */
+ compat_level_relop_register();
+
+ /*
+ * Loop over the inputs.
+ */
+ while (!vstream_feof(VSTREAM_IN)) {
+
+ table = htable_create(0);
+
+ /*
+ * Read a block of definitions, terminated with an empty line.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ cp = vstring_str(buf);
+ name = mystrtok(&cp, CHARS_SPACE "=");
+ value = mystrtok(&cp, CHARS_SPACE "=");
+ htable_enter(table, name, value ? mystrdup(value) : 0);
+ }
+
+ /*
+ * Read a block of patterns, terminated with an empty line or EOF.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ vstream_printf("<< %s\n", vstring_str(buf));
+ vstream_fflush(VSTREAM_OUT);
+ if (VSTRING_LEN(buf) == 0)
+ break;
+ VSTRING_RESET(result);
+ stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
+ (char *) 0, lookup, (void *) table);
+ vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
+ vstream_fflush(VSTREAM_OUT);
+ }
+ htable_free(table, myfree);
+ vstream_printf("\n");
+ }
+
+ /*
+ * Clean up.
+ */
+ vstring_free(buf);
+ vstring_free(result);
+}
+
+static void test_convert(void)
+{
+ VSTRING *buf = vstring_alloc(100);
+ long compat_level;
+ const char *as_string;
+
+ /*
+ * Read compatibility level.
+ */
+ while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
+ if ((compat_level = compat_level_from_string(vstring_str(buf),
+ msg_warn)) < 0)
+ continue;
+ msg_info("%s -> 0x%lx", vstring_str(buf), compat_level);
+ errno = ERANGE;
+ if ((as_string = compat_level_to_string(compat_level,
+ msg_warn)) == 0)
+ continue;
+ msg_info("0x%lx->%s", compat_level, as_string);
+ }
+ vstring_free(buf);
+}
+
+static NORETURN usage(char **argv)
+{
+ msg_fatal("usage: %s option\n-c (convert)\n-c (expand)", argv[0]);
+}
+
+int main(int argc, char **argv)
+{
+ int ch;
+ int mode = 0;
+
+#define MODE_EXPAND (1<<0)
+#define MODE_CONVERT (1<<1)
+
+ while ((ch = GETOPT(argc, argv, "cx")) > 0) {
+ switch (ch) {
+ case 'c':
+ mode |= MODE_CONVERT;
+ break;
+ case 'v':
+ msg_verbose++;
+ break;
+ case 'x':
+ mode |= MODE_EXPAND;
+ break;
+ default:
+ usage(argv);
+ }
+ }
+ switch (mode) {
+ case MODE_CONVERT:
+ test_convert();
+ break;
+ case MODE_EXPAND:
+ test_expand();
+ break;
+ default:
+ usage(argv);
+ }
+ exit(0);
+}
+
+#endif