/*++ /* NAME /* compat_level 3 /* SUMMARY /* compatibility_level support /* SYNOPSIS /* #include /* /* 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 or StatusOr, 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 #include #include #include #include /* * Utility library. */ #include #include #include /* * 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_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; /* * 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. */ long 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 #include #include #include #include #include #include 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