xref: /netbsd-src/external/ibm-public/postfix/dist/src/global/compat_level.c (revision c48c605c14fd8622b523d1d6a3f0c0bad133ea89)
1 /*	$NetBSD: compat_level.c,v 1.3 2023/12/23 20:30:43 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 
compat_relop_eval(const char * left_str,int relop,const char * rite_str)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 
compat_level_relop_register(void)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 
lookup(const char * name,int unused_mode,void * context)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 
test_expand(void)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 
test_convert(void)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 
usage(char ** argv)422 static NORETURN usage(char **argv)
423 {
424     msg_fatal("usage: %s option\n-c (convert)\n-c (expand)", argv[0]);
425 }
426 
main(int argc,char ** argv)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