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