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