Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: compat_level.c,v 1.4 2025/02/25 19:15:45 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	compat_level 3
      6 /* SUMMARY
      7 /*	compatibility_level support
      8 /* SYNOPSIS
      9 /*	#include <compat_level.h>
     10 /*
     11 /*	void compat_level_relop_register(void)
     12 /*
     13 /*	long	compat_level_from_string(
     14 /*	const char *str,
     15 /*	void	PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
     16 /*
     17 /*	long	compat_level_from_numbers(
     18 /*	long	major,
     19 /*	long	minor,
     20 /*	long	patch,
     21 /*	void	PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
     22 /*
     23 /*	const char *compat_level_to_string(
     24 /*	long	compat_level,
     25 /*	void	PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
     26 /* AUXULIARY FUNCTIONS
     27 /*	long	compat_level_from_major_minor(
     28 /*	long	major,
     29 /*	long	minor,
     30 /*	void	PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
     31 /*
     32 /*	long	compat_level_from_major(
     33 /*	long	major,
     34 /*	void	PRINTFLIKE(1, 2) (*msg_fn)(const char *,...))
     35 /* DESCRIPTION
     36 /*	This module supports compatibility level syntax with
     37 /*	"major.minor.patch" but will also accept the shorter forms
     38 /*	"major.minor" and "major" (missing members default to zero).
     39 /*	Compatibility levels with multiple numbers cannot be compared
     40 /*	as strings or as floating-point numbers (for example, "3.10"
     41 /*	would be smaller than "3.9").
     42 /*
     43 /*	The major number can range from [0..2047] inclusive (11
     44 /*	bits) or more, while the minor and patch numbers can range
     45 /*	from [0..1023] inclusive (10 bits).
     46 /*
     47 /*	compat_level_from_string() converts a compatibility level
     48 /*	from string form to numerical form for easy comparison.
     49 /*	Valid input results in a non-negative result. In case of
     50 /*	error, compat_level_from_string() reports the problem with
     51 /*	the provided function, and returns -1 if that function does
     52 /*	not terminate execution.
     53 /*
     54 /*	compat_level_from_numbers() creates an internal-form
     55 /*	compatibility level from distinct numbers. Valid input
     56 /*	results in a non-negative result. In case of error,
     57 /*	compat_level_from_numbers() reports the problem with the
     58 /*	provided function, and returns -1 if that function does not
     59 /*	terminate execution.
     60 /*
     61 /*	The functions compat_level_from_major_minor() and
     62 /*	compat_level_from_major() are helpers that default the missing
     63 /*	information to zeroes.
     64 /*
     65 /*	compat_level_to_string() converts a compatibility level
     66 /*	from numerical form to canonical string form. Valid input
     67 /*	results in a non-null result. In case of error,
     68 /*	compat_level_to_string() reports the problem with the
     69 /*	provided function, and returns a null pointer if that
     70 /*	function does not terminate execution.
     71 /*
     72 /*	compat_level_relop_register() registers a mac_expand() callback
     73 /*	that registers operators such as <=level, >level, that compare
     74 /*	compatibility levels. This function should be called before
     75 /*	loading parameter settings from main.cf.
     76 /* DIAGNOSTICS
     77 /*	info, .., panic: bad compatibility_level syntax.
     78 /* BUGS
     79 /*	The patch and minor fields range from 0..1023 (10 bits) while
     80 /*	the major field ranges from 0..COMPAT_MAJOR_SHIFT47 or more
     81 /*	(11 bits or more).
     82 /*
     83 /*	This would be a great use case for functions returning
     84 /*	StatusOr<compat_level_t> or StatusOr<string>, but is it a bit
     85 /*	late for a port to C++.
     86 /* LICENSE
     87 /* .ad
     88 /* .fi
     89 /*	The Secure Mailer license must be distributed with this software.
     90 /* AUTHOR(S)
     91 /*	Wietse Venema
     92 /*	Google, Inc.
     93 /*	111 8th Avenue
     94 /*	New York, NY 10011, USA
     95 /*--*/
     96 
     97  /*
     98   * System library.
     99   */
    100 #include <sys_defs.h>
    101 #include <stdio.h>
    102 #include <stdlib.h>
    103 #include <errno.h>
    104 #include <limits.h>
    105 
    106  /*
    107   * Utility library.
    108   */
    109 #include <mac_expand.h>
    110 #include <msg.h>
    111 #include <sane_strtol.h>
    112 
    113  /*
    114   * For easy comparison we convert a three-number compatibility level into
    115   * just one number, using different bit ranges for the major version, minor
    116   * version, and patch level.
    117   *
    118   * We use long integers because standard C guarantees that long has at last 32
    119   * bits instead of int which may have only 16 bits (though it is unlikely
    120   * that Postfix would run on such systems). That gives us 11 or more bits
    121   * for the major version, and 10 bits for minor the version and patchlevel.
    122   *
    123   * Below are all the encoding details in one place. This is easier to verify
    124   * than wading through code.
    125   */
    126 #define COMPAT_MAJOR_SHIFT \
    127 	(COMPAT_MINOR_SHIFT + COMPAT_MINOR_WIDTH)
    128 
    129 #define COMPAT_MINOR_SHIFT	COMPAT_PATCH_WIDTH
    130 #define COMPAT_MINOR_BITS	0x3ff
    131 #define COMPAT_MINOR_WIDTH	10
    132 
    133 #define COMPAT_PATCH_BITS	0x3ff
    134 #define COMPAT_PATCH_WIDTH	10
    135 
    136 #define GOOD_MAJOR(m)	((m) >= 0 && (m) <= (LONG_MAX >> COMPAT_MAJOR_SHIFT))
    137 #define GOOD_MINOR(m)	((m) >= 0 && (m) <= COMPAT_MINOR_BITS)
    138 #define GOOD_PATCH(p)	((p) >= 0 && (p) <= COMPAT_PATCH_BITS)
    139 
    140 #define ENCODE_MAJOR(m)	((m) << COMPAT_MAJOR_SHIFT)
    141 #define ENCODE_MINOR(m)	((m) << COMPAT_MINOR_SHIFT)
    142 #define ENCODE_PATCH(p)	(p)
    143 
    144 #define DECODE_MAJOR(l)	((l) >> COMPAT_MAJOR_SHIFT)
    145 #define DECODE_MINOR(l)	(((l) >> COMPAT_MINOR_SHIFT) & COMPAT_MINOR_BITS)
    146 #define DECODE_PATCH(l)	((l) & COMPAT_PATCH_BITS)
    147 
    148  /*
    149   * Global library.
    150   */
    151 #include <compat_level.h>
    152 
    153 /* compat_level_from_string - convert major[.minor] to comparable type */
    154 
    155 long    compat_level_from_string(const char *str,
    156 		         void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
    157 {
    158     long    major, minor, patch, res = 0;
    159     const char *start;
    160     char   *remainder;
    161 
    162     start = str;
    163     major = sane_strtol(start, &remainder, 10);
    164     if (start < remainder && (*remainder == 0 || *remainder == '.')
    165 	&& errno != ERANGE && GOOD_MAJOR(major)) {
    166 	res = ENCODE_MAJOR(major);
    167 	if (*remainder == 0)
    168 	    return res;
    169 	start = remainder + 1;
    170 	minor = sane_strtol(start, &remainder, 10);
    171 	if (start < remainder && (*remainder == 0 || *remainder == '.')
    172 	    && errno != ERANGE && GOOD_MINOR(minor)) {
    173 	    res |= ENCODE_MINOR(minor);
    174 	    if (*remainder == 0)
    175 		return (res);
    176 	    start = remainder + 1;
    177 	    patch = sane_strtol(start, &remainder, 10);
    178 	    if (start < remainder && *remainder == 0 && errno != ERANGE
    179 		&& GOOD_PATCH(patch)) {
    180 		return (res | ENCODE_PATCH(patch));
    181 	    }
    182 	}
    183     }
    184     msg_fn("malformed compatibility level syntax: \"%s\"", str);
    185     return (-1);
    186 }
    187 
    188 /* compat_level_from_numbers - internal form from numbers */
    189 
    190 long    compat_level_from_numbers(long major, long minor, long patch,
    191 		         void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
    192 {
    193     const char myname[] = "compat_level_from_numbers";
    194 
    195     /*
    196      * Sanity checks.
    197      */
    198     if (!GOOD_MAJOR(major)) {
    199 	msg_fn("%s: bad major version: %ld", myname, major);
    200 	return (-1);
    201     }
    202     if (!GOOD_MINOR(minor)) {
    203 	msg_fn("%s: bad minor version: %ld", myname, minor);
    204 	return (-1);
    205     }
    206     if (!GOOD_PATCH(patch)) {
    207 	msg_fn("%s: bad patch level: %ld", myname, patch);
    208 	return (-1);
    209     }
    210 
    211     /*
    212      * Conversion.
    213      */
    214     return (ENCODE_MAJOR(major) | ENCODE_MINOR(minor) | ENCODE_PATCH(patch));
    215 }
    216 
    217 /* compat_level_to_string - pretty-print a compatibility level */
    218 
    219 const char *compat_level_to_string(long compat_level,
    220 		         void PRINTFLIKE(1, 2) (*msg_fn) (const char *,...))
    221 {
    222     const char myname[] = "compat_level_to_string";
    223     static VSTRING *buf;
    224     long    major;
    225     long    minor;
    226     long    patch;
    227 
    228     /*
    229      * Sanity check.
    230      */
    231     if (compat_level < 0) {
    232 	msg_fn("%s: bad compatibility level: %ld", myname, compat_level);
    233 	return (0);
    234     }
    235 
    236     /*
    237      * Compatibility levels 0..2 have no minor or patch level.
    238      */
    239     if (buf == 0)
    240 	buf = vstring_alloc(10);
    241     major = DECODE_MAJOR(compat_level);
    242     if (!GOOD_MAJOR(major)) {
    243 	msg_fn("%s: bad compatibility major level: %ld", myname, compat_level);
    244 	return (0);
    245     }
    246     vstring_sprintf(buf, "%ld", major);
    247     if (major > 2) {
    248 
    249 	/*
    250 	 * Expect that major.minor will be common.
    251 	 */
    252 	minor = DECODE_MINOR(compat_level);
    253 	vstring_sprintf_append(buf, ".%ld", minor);
    254 
    255 	/*
    256 	 * Expect that major.minor.patch will be rare.
    257 	 */
    258 	patch = DECODE_PATCH(compat_level);
    259 	if (patch)
    260 	    vstring_sprintf_append(buf, ".%ld", patch);
    261     }
    262     return (vstring_str(buf));
    263 }
    264 
    265 /* compat_relop_eval - mac_expand callback */
    266 
    267 static MAC_EXP_OP_RES compat_relop_eval(const char *left_str, int relop,
    268 					        const char *rite_str)
    269 {
    270     const char myname[] = "compat_relop_eval";
    271     long    left_val, rite_val, delta;
    272 
    273     /*
    274      * Negative result means error.
    275      */
    276     if ((left_val = compat_level_from_string(left_str, msg_warn)) < 0
    277 	|| (rite_val = compat_level_from_string(rite_str, msg_warn)) < 0)
    278 	return (MAC_EXP_OP_RES_ERROR);
    279 
    280     /*
    281      * Valid result. The difference between non-negative numbers will no
    282      * overflow.
    283      */
    284     delta = left_val - rite_val;
    285 
    286     switch (relop) {
    287     case MAC_EXP_OP_TOK_EQ:
    288 	return (mac_exp_op_res_bool[delta == 0]);
    289     case MAC_EXP_OP_TOK_NE:
    290 	return (mac_exp_op_res_bool[delta != 0]);
    291     case MAC_EXP_OP_TOK_LT:
    292 	return (mac_exp_op_res_bool[delta < 0]);
    293     case MAC_EXP_OP_TOK_LE:
    294 	return (mac_exp_op_res_bool[delta <= 0]);
    295     case MAC_EXP_OP_TOK_GE:
    296 	return (mac_exp_op_res_bool[delta >= 0]);
    297     case MAC_EXP_OP_TOK_GT:
    298 	return (mac_exp_op_res_bool[delta > 0]);
    299     default:
    300 	msg_panic("%s: unknown operator: %d",
    301 		  myname, relop);
    302     }
    303 }
    304 
    305 /* compat_level_register - register comparison operators */
    306 
    307 void    compat_level_relop_register(void)
    308 {
    309     int     compat_level_relops[] = {
    310 	MAC_EXP_OP_TOK_EQ, MAC_EXP_OP_TOK_NE,
    311 	MAC_EXP_OP_TOK_GT, MAC_EXP_OP_TOK_GE,
    312 	MAC_EXP_OP_TOK_LT, MAC_EXP_OP_TOK_LE,
    313 	0,
    314     };
    315     static int register_done;
    316 
    317     if (register_done++ == 0)
    318 	mac_expand_add_relop(compat_level_relops, "level", compat_relop_eval);
    319 }
    320 
    321 #ifdef TEST
    322 #include <unistd.h>
    323 
    324 #include <htable.h>
    325 #include <mymalloc.h>
    326 #include <stringops.h>
    327 #include <vstring.h>
    328 #include <vstream.h>
    329 #include <vstring_vstream.h>
    330 
    331 static const char *lookup(const char *name, int unused_mode, void *context)
    332 {
    333     HTABLE *table = (HTABLE *) context;
    334 
    335     return (htable_find(table, name));
    336 }
    337 
    338 static void test_expand(void)
    339 {
    340     VSTRING *buf = vstring_alloc(100);
    341     VSTRING *result = vstring_alloc(100);
    342     char   *cp;
    343     char   *name;
    344     char   *value;
    345     HTABLE *table;
    346     int     stat;
    347 
    348     /*
    349      * Add relops that compare string lengths instead of content.
    350      */
    351     compat_level_relop_register();
    352 
    353     /*
    354      * Loop over the inputs.
    355      */
    356     while (!vstream_feof(VSTREAM_IN)) {
    357 
    358 	table = htable_create(0);
    359 
    360 	/*
    361 	 * Read a block of definitions, terminated with an empty line.
    362 	 */
    363 	while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
    364 	    vstream_printf("<< %s\n", vstring_str(buf));
    365 	    vstream_fflush(VSTREAM_OUT);
    366 	    if (VSTRING_LEN(buf) == 0)
    367 		break;
    368 	    cp = vstring_str(buf);
    369 	    name = mystrtok(&cp, CHARS_SPACE "=");
    370 	    value = mystrtok(&cp, CHARS_SPACE "=");
    371 	    htable_enter(table, name, value ? mystrdup(value) : 0);
    372 	}
    373 
    374 	/*
    375 	 * Read a block of patterns, terminated with an empty line or EOF.
    376 	 */
    377 	while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
    378 	    vstream_printf("<< %s\n", vstring_str(buf));
    379 	    vstream_fflush(VSTREAM_OUT);
    380 	    if (VSTRING_LEN(buf) == 0)
    381 		break;
    382 	    VSTRING_RESET(result);
    383 	    stat = mac_expand(result, vstring_str(buf), MAC_EXP_FLAG_NONE,
    384 			      (char *) 0, lookup, (void *) table);
    385 	    vstream_printf("stat=%d result=%s\n", stat, vstring_str(result));
    386 	    vstream_fflush(VSTREAM_OUT);
    387 	}
    388 	htable_free(table, myfree);
    389 	vstream_printf("\n");
    390     }
    391 
    392     /*
    393      * Clean up.
    394      */
    395     vstring_free(buf);
    396     vstring_free(result);
    397 }
    398 
    399 static void test_convert(void)
    400 {
    401     VSTRING *buf = vstring_alloc(100);
    402     long    compat_level;
    403     const char *as_string;
    404 
    405     /*
    406      * Read compatibility level.
    407      */
    408     while (vstring_get_nonl(buf, VSTREAM_IN) != VSTREAM_EOF) {
    409 	if ((compat_level = compat_level_from_string(vstring_str(buf),
    410 						     msg_warn)) < 0)
    411 	    continue;
    412 	msg_info("%s -> 0x%lx", vstring_str(buf), compat_level);
    413 	errno = ERANGE;
    414 	if ((as_string = compat_level_to_string(compat_level,
    415 						msg_warn)) == 0)
    416 	    continue;
    417 	msg_info("0x%lx->%s", compat_level, as_string);
    418     }
    419     vstring_free(buf);
    420 }
    421 
    422 static NORETURN usage(char **argv)
    423 {
    424     msg_fatal("usage: %s option\n-c (convert)\n-c (expand)", argv[0]);
    425 }
    426 
    427 int     main(int argc, char **argv)
    428 {
    429     int     ch;
    430     int     mode = 0;
    431 
    432 #define MODE_EXPAND	(1<<0)
    433 #define MODE_CONVERT	(1<<1)
    434 
    435     while ((ch = GETOPT(argc, argv, "cx")) > 0) {
    436 	switch (ch) {
    437 	case 'c':
    438 	    mode |= MODE_CONVERT;
    439 	    break;
    440 	case 'v':
    441 	    msg_verbose++;
    442 	    break;
    443 	case 'x':
    444 	    mode |= MODE_EXPAND;
    445 	    break;
    446 	default:
    447 	    usage(argv);
    448 	}
    449     }
    450     switch (mode) {
    451     case MODE_CONVERT:
    452 	test_convert();
    453 	break;
    454     case MODE_EXPAND:
    455 	test_expand();
    456 	break;
    457     default:
    458 	usage(argv);
    459     }
    460     exit(0);
    461 }
    462 
    463 #endif
    464