unrealircd- supernets unrealircd source & configuration |
git clone git://git.acid.vegas/unrealircd.git |
Log | Files | Refs | Archive | README | LICENSE |
conf_preprocessor.c (12294B)
1 /* UnrealIRCd configuration preprocessor 2 * (C) Copyright 2019 Bram Matthys ("Syzop") and the UnrealIRCd team 3 * License: GPLv2 or later 4 * 5 * Technically this isn't a 100% true preprocessor, but to the end user 6 * it will certainly look like it, hence the name. 7 */ 8 9 #include "unrealircd.h" 10 11 extern ConfigFile *conf; 12 13 NameValueList *config_defines = NULL; /**< List of @defines, only valid during configuration reading */ 14 15 static inline int ValidVarCharacter(char x) 16 { 17 if (isupper(x) || isdigit(x) || strchr("_", x)) 18 return 1; 19 return 0; 20 } 21 22 PreprocessorItem evaluate_preprocessor_if(char *statement, const char *filename, int linenumber, ConditionalConfig **cc_out) 23 { 24 char *p=statement, *name; 25 int negative = 0; 26 ConditionalConfig *cc; 27 28 /* Currently we support only 4 things: 29 * $XYZ == "something" 30 * $XYZ != "something" 31 * module-loaded("something") 32 * !module-loaded("something") 33 * defined($XYZ) 34 * !defined($XYZ) 35 * We do not support && or || or anything else at this time. 36 */ 37 skip_whitespace(&p); 38 if (*p == '@') 39 p++; 40 if (*p == '!') 41 { 42 negative = 1; 43 p++; 44 skip_whitespace(&p); 45 } 46 47 /* Now comes the keyword or a variable name */ 48 if (!strncmp(p, "module-loaded", 13)) 49 { 50 p += 13; 51 skip_whitespace(&p); 52 if (*p != '(') 53 { 54 config_error("%s:%i: expected '(' for module-loaded(...", 55 filename, linenumber); 56 return PREPROCESSOR_ERROR; 57 } 58 p++; 59 skip_whitespace(&p); 60 if (*p == '"') 61 p++; 62 name = p; 63 read_until(&p, ")\""); 64 if (!*p) 65 { 66 config_error("%s:%i: invalid if statement (termination error): %s", 67 filename, linenumber, statement); 68 return PREPROCESSOR_ERROR; 69 } 70 *p = '\0'; 71 cc = safe_alloc(sizeof(ConditionalConfig)); 72 cc->condition = IF_MODULE; 73 cc->negative = negative; 74 safe_strdup(cc->name, name); 75 *cc_out = cc; 76 return PREPROCESSOR_IF; 77 } else 78 if (!strncmp(p, "defined", 7)) 79 { 80 p += 7; 81 skip_whitespace(&p); 82 if (*p != '(') 83 { 84 config_error("%s:%i: expected '(' for defined(...", 85 filename, linenumber); 86 return PREPROCESSOR_ERROR; 87 } 88 p++; 89 skip_whitespace(&p); 90 if (*p == '"') 91 p++; 92 name = p; 93 read_until(&p, ")\""); 94 if (!*p) 95 { 96 config_error("%s:%i: invalid if statement (termination error): %s", 97 filename, linenumber, statement); 98 return PREPROCESSOR_ERROR; 99 } 100 *p = '\0'; 101 cc = safe_alloc(sizeof(ConditionalConfig)); 102 cc->condition = IF_DEFINED; 103 cc->negative = negative; 104 safe_strdup(cc->name, name); 105 *cc_out = cc; 106 return PREPROCESSOR_IF; 107 } else 108 { 109 char *name_terminate, *name2; 110 // Should be one of: 111 // $XYZ == "something" 112 // $XYZ != "something" 113 // Anything else is an error. 114 if (*p != '$') 115 { 116 config_error("%s:%i: invalid @if statement. Either an unknown function, or did you mean $VARNAME?: %s", 117 filename, linenumber, statement); 118 return PREPROCESSOR_ERROR; 119 } 120 p++; 121 /* variable name starts now */ 122 name = p; 123 read_until(&p, " \t=!"); 124 if (!*p) 125 { 126 config_error("%s:%i: invalid if statement (termination error): %s", 127 filename, linenumber, statement); 128 return PREPROCESSOR_ERROR; 129 } 130 name_terminate = p; 131 skip_whitespace(&p); 132 if (!strncmp(p, "==", 2)) 133 { 134 negative = 0; 135 } else 136 if (!strncmp(p, "!=", 2)) 137 { 138 negative = 1; 139 } else 140 { 141 *name_terminate = '\0'; 142 config_error("%s:%i: @if: expected == or != after '%s'", 143 filename, linenumber, name); 144 return PREPROCESSOR_ERROR; 145 } 146 p += 2; 147 *name_terminate = '\0'; 148 skip_whitespace(&p); 149 if (*p != '"') 150 { 151 config_error("%s:%i: @if: expected double quotes, missing \" perhaps?", 152 filename, linenumber); 153 return PREPROCESSOR_ERROR; 154 } 155 p++; 156 name2 = p; 157 read_until(&p, "\""); 158 if (!*p) 159 { 160 config_error("%s:%i: invalid @if statement, missing \" at end perhaps?", 161 filename, linenumber); 162 return PREPROCESSOR_ERROR; 163 } 164 *p = '\0'; 165 cc = safe_alloc(sizeof(ConditionalConfig)); 166 cc->condition = IF_VALUE; 167 cc->negative = negative; 168 safe_strdup(cc->name, name); 169 safe_strdup(cc->opt, name2); 170 *cc_out = cc; 171 return PREPROCESSOR_IF; 172 } 173 174 config_error("%s:%i: Error while evaluating '@if' statement '%s'", 175 filename, linenumber, statement); 176 return PREPROCESSOR_ERROR; 177 } 178 179 PreprocessorItem evaluate_preprocessor_define(char *statement, const char *filename, int linenumber) 180 { 181 char *p = statement; 182 char *name, *name_terminator; 183 char *value; 184 185 skip_whitespace(&p); 186 name = p; 187 read_until(&p, " \t"); 188 if (!*p) 189 { 190 config_error("%s:%i: invalid @define statement", 191 filename, linenumber); 192 return PREPROCESSOR_ERROR; 193 } 194 name_terminator = p; 195 skip_whitespace(&p); 196 if (*p != '"') 197 { 198 config_error("%s:%i: @define: expected double quotes, missing \" perhaps?", 199 filename, linenumber); 200 return PREPROCESSOR_ERROR; 201 } 202 p++; 203 value = p; 204 read_until(&p, "\""); 205 if (!*p) 206 { 207 config_error("%s:%i: invalid @define statement, missing \" at end perhaps?", 208 filename, linenumber); 209 return PREPROCESSOR_ERROR; 210 } 211 212 *p = '\0'; 213 *name_terminator = '\0'; 214 215 if (*name != '$') 216 { 217 config_error("%s:%i: the defined variable should start with a dollar sign ($), " 218 "so: @define $something \"123\" and not @define something \"123\"", 219 filename, linenumber); 220 return PREPROCESSOR_ERROR; 221 } 222 /* Skip dollar sign */ 223 name++; 224 for (p = name; *p; p++) 225 { 226 if (!ValidVarCharacter(*p)) 227 { 228 config_error("%s:%i: A $VARIABLE name may only contain UPPERcase characters, " 229 "digits, and the _ character. Illegal character: '%c'", 230 filename, linenumber, *p); 231 return PREPROCESSOR_ERROR; 232 } 233 } 234 235 if (strlen(value) > 512) 236 { 237 config_error("%s:%i: Value of defined variable is extremely large (%ld characters)!", 238 filename, linenumber, (long)strlen(value)); 239 return PREPROCESSOR_ERROR; 240 } 241 242 NameValueList *d = safe_alloc(sizeof(NameValueList)); 243 safe_strdup(d->name, name); 244 safe_strdup(d->value, value); 245 AddListItem(d, config_defines); 246 return PREPROCESSOR_DEFINE; 247 } 248 249 PreprocessorItem parse_preprocessor_item(char *start, char *end, const char *filename, int linenumber, ConditionalConfig **cc) 250 { 251 char buf[512]; 252 int max; 253 254 *cc = NULL; 255 256 max = end - start + 1; 257 if (max > sizeof(buf)) 258 max = sizeof(buf); 259 strlcpy(buf, start, max); 260 261 if (!strncmp(buf, "@define", 7)) 262 return evaluate_preprocessor_define(buf+7, filename, linenumber); 263 else if (!strncmp(buf, "@if ", 4)) 264 return evaluate_preprocessor_if(buf+4, filename, linenumber, cc); 265 else if (!strncmp(buf, "@endif", 6)) 266 return PREPROCESSOR_ENDIF; 267 268 config_error("%s:%i: Unknown preprocessor directive: %s", filename, linenumber, buf); 269 return PREPROCESSOR_ERROR; /* ??? */ 270 } 271 272 /** Free a ConditionalConfig entry. 273 * NOTE: be sure to do a DelListItem() before calling this, if necessary. 274 */ 275 void preprocessor_cc_free_entry(ConditionalConfig *cc) 276 { 277 safe_free(cc->name); 278 safe_free(cc->opt); 279 safe_free(cc); 280 } 281 282 /** Free ConditionalConfig entries in a linked list that 283 * are equal or above 'level'. This happens during an @endif. 284 */ 285 void preprocessor_cc_free_level(ConditionalConfig **cc_list, int level) 286 { 287 ConditionalConfig *cc, *cc_next; 288 for (cc = *cc_list; cc; cc = cc_next) 289 { 290 cc_next = cc->next; 291 if (cc->priority >= level) 292 { 293 DelListItem(cc, *cc_list); 294 preprocessor_cc_free_entry(cc); 295 } 296 } 297 } 298 299 /** Duplicates a linked list of ConditionalConfig entries */ 300 void preprocessor_cc_duplicate_list(ConditionalConfig *r, ConditionalConfig **out) 301 { 302 ConditionalConfig *cc; 303 304 *out = NULL; 305 for (; r; r = r->next) 306 { 307 cc = safe_alloc(sizeof(ConditionalConfig)); 308 safe_strdup(cc->name, r->name); 309 safe_strdup(cc->opt, r->opt); 310 cc->priority = r->priority; 311 cc->condition = r->condition; 312 cc->negative = r->negative; 313 AddListItem(cc, *out); 314 } 315 } 316 317 void preprocessor_cc_free_list(ConditionalConfig *cc) 318 { 319 ConditionalConfig *cc_next; 320 321 for (; cc; cc = cc_next) 322 { 323 cc_next = cc->next; 324 safe_free(cc->name); 325 safe_free(cc->opt); 326 safe_free(cc); 327 } 328 } 329 330 NameValueList *find_config_define(const char *name) 331 { 332 NameValueList *n; 333 334 for (n = config_defines; n; n = n->next) 335 if (!strcasecmp(n->name, name)) 336 return n; 337 return NULL; 338 } 339 340 /** Resolve a preprocessor condition to true (=default) or false */ 341 int preprocessor_resolve_if(ConditionalConfig *cc, PreprocessorPhase phase) 342 { 343 int result = 0; 344 345 if (!cc) 346 return 1; 347 348 if (cc->condition == IF_MODULE) 349 { 350 if (phase == PREPROCESSOR_PHASE_INITIAL) 351 { 352 /* We cannot handle @if module-loaded() yet.. */ 353 return 1; 354 } 355 if (phase == PREPROCESSOR_PHASE_SECONDARY) 356 { 357 /* We can only handle blacklisted modules at this point, so: */ 358 if (is_blacklisted_module(cc->name)) 359 return 0; 360 return 1; 361 } 362 if (is_module_loaded(cc->name)) 363 { 364 result = 1; 365 } 366 } else 367 if (cc->condition == IF_DEFINED) 368 { 369 NameValueList *d = find_config_define(cc->name); 370 if (d) 371 { 372 result = 1; 373 } 374 } else 375 if (cc->condition == IF_VALUE) 376 { 377 NameValueList *d = find_config_define(cc->name); 378 if (d && !strcasecmp(d->value, cc->opt)) 379 { 380 result = 1; 381 } 382 } else 383 { 384 config_status("[BUG] unhandled @if type!!"); 385 } 386 387 if (cc->negative) 388 result = result ? 0 : 1; 389 390 return result; 391 } 392 393 void preprocessor_resolve_conditionals_ce(ConfigEntry **ce_list, PreprocessorPhase phase) 394 { 395 ConfigEntry *ce, *next, *ce_prev; 396 ConfigEntry *cep, *cep_next, *cep_prev; 397 398 ce_prev = NULL; 399 for (ce = *ce_list; ce; ce = next) 400 { 401 next = ce->next; 402 /* This is for an @if before a block start */ 403 if (!preprocessor_resolve_if(ce->conditional_config, phase)) 404 { 405 /* Delete this entry */ 406 if (ce == *ce_list) 407 { 408 /* we are head, so new head */ 409 *ce_list = ce->next; /* can be NULL now */ 410 } else { 411 /* non-head */ 412 ce_prev->next = ce->next; /* can be NULL now */ 413 } 414 config_entry_free(ce); 415 continue; 416 } 417 preprocessor_resolve_conditionals_ce(&ce->items, phase); 418 ce_prev = ce; 419 } 420 } 421 422 void preprocessor_resolve_conditionals_all(PreprocessorPhase phase) 423 { 424 ConfigFile *cfptr; 425 426 for (cfptr = conf; cfptr; cfptr = cfptr->next) 427 preprocessor_resolve_conditionals_ce(&cfptr->items, phase); 428 } 429 430 /** Frees the list of config_defines, so all @defines */ 431 void free_config_defines(void) 432 { 433 NameValueList *e, *e_next; 434 for (e = config_defines; e; e = e_next) 435 { 436 e_next = e->next; 437 safe_free(e->name); 438 safe_free(e->value); 439 safe_free(e); 440 } 441 config_defines = NULL; 442 } 443 444 /** Return value of defined value */ 445 char *get_config_define(char *name) 446 { 447 NameValueList *e; 448 449 for (e = config_defines; e; e = e->next) 450 { 451 if (!strcasecmp(e->name, name)) 452 return e->value; 453 } 454 455 return NULL; 456 } 457 458 void preprocessor_replace_defines(char **item, ConfigEntry *ce) 459 { 460 static char buf[4096]; 461 char varname[512]; 462 const char *i, *varstart, *varend; 463 char *o; 464 int n = sizeof(buf)-2; 465 int limit; 466 char *value; 467 468 if (!strchr(*item, '$')) 469 return; /* quick return in 99% of the cases */ 470 471 o = buf; 472 for (i = *item; *i; i++) 473 { 474 if (*i != '$') 475 { 476 *o++ = *i; 477 if (--n == 0) 478 break; 479 continue; 480 } 481 482 /* $ encountered: */ 483 varstart = i; 484 i++; 485 for (; *i && ValidVarCharacter(*i); i++); 486 varend = i; 487 i--; 488 limit = varend - varstart + 1; 489 if (limit > sizeof(varname)) 490 limit = sizeof(varname); 491 strlcpy(varname, varstart, limit); 492 if (!strncmp(varstart, "$$", 2)) 493 { 494 /* If we get here then we encountered "$$" 495 * and varname is just "$". 496 * This means we are hitting a $ escape sequence, 497 * the $$ is used as a literal $. 498 * Would be very rare if you need it, but.. we support it. 499 */ 500 value = varname; /* bit confusing, but no +1 here */ 501 i++; /* skip extra $ in input */ 502 } else 503 { 504 value = get_config_define(varname+1); 505 if (!value) 506 { 507 #if 0 508 /* Complain about $VARS if they are not defined, but don't bother 509 * for cases where it's clearly not a macro, eg. contains illegal 510 * variable characters. 511 */ 512 if ((limit > 2) && ((*varend == '\0') || strchr("\t ,.", *varend))) 513 { 514 config_warn("%s:%d: Variable %s used here but there's no @define for it earlier.", 515 ce->file->filename, ce->line_number, varname); 516 } 517 #endif 518 value = varname; /* not found? then use varname, including the '$' */ 519 } 520 } 521 limit = strlen(value) + 1; 522 if (limit > n) 523 limit = n; 524 strlcpy(o, value, limit); 525 o += limit - 1; 526 n -= limit; 527 if (n == 0) 528 break; /* no output buffer left */ 529 if (*varend == 0) 530 break; /* no input buffer left */ 531 } 532 *o = '\0'; 533 safe_strdup(*item, buf); 534 }