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