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 }