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