2020-03-29 09:16:53 +00:00
|
|
|
/* UnrealIRCd configuration preprocessor
|
|
|
|
* (C) Copyright 2019 Bram Matthys ("Syzop") and the UnrealIRCd team
|
|
|
|
* License: GPLv2
|
|
|
|
*
|
|
|
|
* Technically this isn't a 100% true preprocessor, but to the end user
|
|
|
|
* it will certainly look like it, hence the name.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "unrealircd.h"
|
|
|
|
|
|
|
|
extern ConfigFile *conf;
|
|
|
|
|
|
|
|
NameValueList *config_defines = NULL; /**< List of @defines, only valid during configuration reading */
|
|
|
|
|
|
|
|
static inline int ValidVarCharacter(char x)
|
|
|
|
{
|
|
|
|
if (isupper(x) || isdigit(x) || strchr("_", x))
|
|
|
|
return 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-01-15 05:16:34 +00:00
|
|
|
PreprocessorItem evaluate_preprocessor_if(char *statement, const char *filename, int linenumber, ConditionalConfig **cc_out)
|
2020-03-29 09:16:53 +00:00
|
|
|
{
|
|
|
|
char *p=statement, *name;
|
|
|
|
int negative = 0;
|
|
|
|
ConditionalConfig *cc;
|
|
|
|
|
|
|
|
/* Currently we support only 4 things:
|
|
|
|
* $XYZ == "something"
|
|
|
|
* $XYZ != "something"
|
|
|
|
* module-loaded("something")
|
|
|
|
* !module-loaded("something")
|
|
|
|
* defined($XYZ)
|
|
|
|
* !defined($XYZ)
|
|
|
|
* We do not support && or || or anything else at this time.
|
|
|
|
*/
|
|
|
|
skip_whitespace(&p);
|
|
|
|
if (*p == '@')
|
|
|
|
p++;
|
|
|
|
if (*p == '!')
|
|
|
|
{
|
|
|
|
negative = 1;
|
|
|
|
p++;
|
|
|
|
skip_whitespace(&p);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now comes the keyword or a variable name */
|
|
|
|
if (!strncmp(p, "module-loaded", 13))
|
|
|
|
{
|
|
|
|
p += 13;
|
|
|
|
skip_whitespace(&p);
|
|
|
|
if (*p != '(')
|
|
|
|
{
|
|
|
|
config_error("%s:%i: expected '(' for module-loaded(...",
|
|
|
|
filename, linenumber);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
skip_whitespace(&p);
|
|
|
|
if (*p == '"')
|
|
|
|
p++;
|
|
|
|
name = p;
|
|
|
|
read_until(&p, ")\"");
|
|
|
|
if (!*p)
|
|
|
|
{
|
|
|
|
config_error("%s:%i: invalid if statement (termination error): %s",
|
|
|
|
filename, linenumber, statement);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
*p = '\0';
|
|
|
|
cc = safe_alloc(sizeof(ConditionalConfig));
|
|
|
|
cc->condition = IF_MODULE;
|
|
|
|
cc->negative = negative;
|
|
|
|
safe_strdup(cc->name, name);
|
|
|
|
*cc_out = cc;
|
|
|
|
return PREPROCESSOR_IF;
|
|
|
|
} else
|
|
|
|
if (!strncmp(p, "defined", 7))
|
|
|
|
{
|
|
|
|
p += 7;
|
|
|
|
skip_whitespace(&p);
|
|
|
|
if (*p != '(')
|
|
|
|
{
|
|
|
|
config_error("%s:%i: expected '(' for defined(...",
|
|
|
|
filename, linenumber);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
skip_whitespace(&p);
|
|
|
|
if (*p == '"')
|
|
|
|
p++;
|
|
|
|
name = p;
|
|
|
|
read_until(&p, ")\"");
|
|
|
|
if (!*p)
|
|
|
|
{
|
|
|
|
config_error("%s:%i: invalid if statement (termination error): %s",
|
|
|
|
filename, linenumber, statement);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
*p = '\0';
|
|
|
|
cc = safe_alloc(sizeof(ConditionalConfig));
|
|
|
|
cc->condition = IF_DEFINED;
|
|
|
|
cc->negative = negative;
|
|
|
|
safe_strdup(cc->name, name);
|
|
|
|
*cc_out = cc;
|
|
|
|
return PREPROCESSOR_IF;
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
char *name_terminate, *name2;
|
|
|
|
// Should be one of:
|
|
|
|
// $XYZ == "something"
|
|
|
|
// $XYZ != "something"
|
|
|
|
// Anything else is an error.
|
|
|
|
if (*p != '$')
|
|
|
|
{
|
|
|
|
config_error("%s:%i: invalid @if statement. Either an unknown function, or did you mean $VARNAME?: %s",
|
|
|
|
filename, linenumber, statement);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
/* variable name starts now */
|
|
|
|
name = p;
|
|
|
|
read_until(&p, " \t=!");
|
|
|
|
if (!*p)
|
|
|
|
{
|
|
|
|
config_error("%s:%i: invalid if statement (termination error): %s",
|
|
|
|
filename, linenumber, statement);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
name_terminate = p;
|
|
|
|
skip_whitespace(&p);
|
|
|
|
if (!strncmp(p, "==", 2))
|
|
|
|
{
|
|
|
|
negative = 0;
|
|
|
|
} else
|
|
|
|
if (!strncmp(p, "!=", 2))
|
|
|
|
{
|
|
|
|
negative = 1;
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
*name_terminate = '\0';
|
|
|
|
config_error("%s:%i: @if: expected == or != after '%s'",
|
|
|
|
filename, linenumber, name);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
p += 2;
|
|
|
|
*name_terminate = '\0';
|
|
|
|
skip_whitespace(&p);
|
|
|
|
if (*p != '"')
|
|
|
|
{
|
|
|
|
config_error("%s:%i: @if: expected double quotes, missing \" perhaps?",
|
|
|
|
filename, linenumber);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
name2 = p;
|
|
|
|
read_until(&p, "\"");
|
|
|
|
if (!*p)
|
|
|
|
{
|
|
|
|
config_error("%s:%i: invalid @if statement, missing \" at end perhaps?",
|
|
|
|
filename, linenumber);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
*p = '\0';
|
|
|
|
cc = safe_alloc(sizeof(ConditionalConfig));
|
|
|
|
cc->condition = IF_VALUE;
|
|
|
|
cc->negative = negative;
|
|
|
|
safe_strdup(cc->name, name);
|
|
|
|
safe_strdup(cc->opt, name2);
|
|
|
|
*cc_out = cc;
|
|
|
|
return PREPROCESSOR_IF;
|
|
|
|
}
|
|
|
|
|
|
|
|
config_error("%s:%i: Error while evaluating '@if' statement '%s'",
|
|
|
|
filename, linenumber, statement);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
|
2022-01-15 05:16:34 +00:00
|
|
|
PreprocessorItem evaluate_preprocessor_define(char *statement, const char *filename, int linenumber)
|
2020-03-29 09:16:53 +00:00
|
|
|
{
|
|
|
|
char *p = statement;
|
|
|
|
char *name, *name_terminator;
|
|
|
|
char *value;
|
|
|
|
|
|
|
|
skip_whitespace(&p);
|
|
|
|
name = p;
|
|
|
|
read_until(&p, " \t");
|
|
|
|
if (!*p)
|
|
|
|
{
|
|
|
|
config_error("%s:%i: invalid @define statement",
|
|
|
|
filename, linenumber);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
name_terminator = p;
|
|
|
|
skip_whitespace(&p);
|
|
|
|
if (*p != '"')
|
|
|
|
{
|
|
|
|
config_error("%s:%i: @define: expected double quotes, missing \" perhaps?",
|
|
|
|
filename, linenumber);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
p++;
|
|
|
|
value = p;
|
|
|
|
read_until(&p, "\"");
|
|
|
|
if (!*p)
|
|
|
|
{
|
|
|
|
config_error("%s:%i: invalid @define statement, missing \" at end perhaps?",
|
|
|
|
filename, linenumber);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
*p = '\0';
|
|
|
|
*name_terminator = '\0';
|
|
|
|
|
|
|
|
if (*name != '$')
|
|
|
|
{
|
|
|
|
config_error("%s:%i: the defined variable should start with a dollar sign ($), "
|
|
|
|
"so: @define $something \"123\" and not @define something \"123\"",
|
|
|
|
filename, linenumber);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
/* Skip dollar sign */
|
|
|
|
name++;
|
|
|
|
for (p = name; *p; p++)
|
|
|
|
{
|
|
|
|
if (!ValidVarCharacter(*p))
|
|
|
|
{
|
|
|
|
config_error("%s:%i: A $VARIABLE name may only contain UPPERcase characters, "
|
|
|
|
"digits, and the _ character. Illegal character: '%c'",
|
|
|
|
filename, linenumber, *p);
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (strlen(value) > 512)
|
|
|
|
{
|
|
|
|
config_error("%s:%i: Value of defined variable is extremely large (%ld characters)!",
|
|
|
|
filename, linenumber, (long)strlen(value));
|
|
|
|
return PREPROCESSOR_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
NameValueList *d = safe_alloc(sizeof(NameValueList));
|
|
|
|
safe_strdup(d->name, name);
|
|
|
|
safe_strdup(d->value, value);
|
|
|
|
AddListItem(d, config_defines);
|
|
|
|
return PREPROCESSOR_DEFINE;
|
|
|
|
}
|
|
|
|
|
2022-01-15 05:16:34 +00:00
|
|
|
PreprocessorItem parse_preprocessor_item(char *start, char *end, const char *filename, int linenumber, ConditionalConfig **cc)
|
2020-03-29 09:16:53 +00:00
|
|
|
{
|
|
|
|
char buf[512];
|
|
|
|
int max;
|
|
|
|
|
|
|
|
*cc = NULL;
|
|
|
|
|
|
|
|
max = end - start + 1;
|
|
|
|
if (max > sizeof(buf))
|
|
|
|
max = sizeof(buf);
|
|
|
|
strlcpy(buf, start, max);
|
|
|
|
|
|
|
|
if (!strncmp(buf, "@define", 7))
|
|
|
|
return evaluate_preprocessor_define(buf+7, filename, linenumber);
|
|
|
|
else if (!strncmp(buf, "@if ", 4))
|
|
|
|
return evaluate_preprocessor_if(buf+4, filename, linenumber, cc);
|
2022-01-15 05:16:34 +00:00
|
|
|
else if (!strncmp(buf, "@endif", 6))
|
2020-03-29 09:16:53 +00:00
|
|
|
return PREPROCESSOR_ENDIF;
|
|
|
|
|
|
|
|
config_error("%s:%i: Unknown preprocessor directive: %s", filename, linenumber, buf);
|
|
|
|
return PREPROCESSOR_ERROR; /* ??? */
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Free a ConditionalConfig entry.
|
|
|
|
* NOTE: be sure to do a DelListItem() before calling this, if necessary.
|
|
|
|
*/
|
|
|
|
void preprocessor_cc_free_entry(ConditionalConfig *cc)
|
|
|
|
{
|
|
|
|
safe_free(cc->name);
|
|
|
|
safe_free(cc->opt);
|
|
|
|
safe_free(cc);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Free ConditionalConfig entries in a linked list that
|
|
|
|
* are equal or above 'level'. This happens during an @endif.
|
|
|
|
*/
|
|
|
|
void preprocessor_cc_free_level(ConditionalConfig **cc_list, int level)
|
|
|
|
{
|
|
|
|
ConditionalConfig *cc, *cc_next;
|
|
|
|
for (cc = *cc_list; cc; cc = cc_next)
|
|
|
|
{
|
|
|
|
cc_next = cc->next;
|
|
|
|
if (cc->priority >= level)
|
|
|
|
{
|
|
|
|
DelListItem(cc, *cc_list);
|
|
|
|
preprocessor_cc_free_entry(cc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Duplicates a linked list of ConditionalConfig entries */
|
|
|
|
void preprocessor_cc_duplicate_list(ConditionalConfig *r, ConditionalConfig **out)
|
|
|
|
{
|
|
|
|
ConditionalConfig *cc;
|
|
|
|
|
|
|
|
*out = NULL;
|
|
|
|
for (; r; r = r->next)
|
|
|
|
{
|
|
|
|
cc = safe_alloc(sizeof(ConditionalConfig));
|
|
|
|
safe_strdup(cc->name, r->name);
|
|
|
|
safe_strdup(cc->opt, r->opt);
|
|
|
|
cc->priority = r->priority;
|
|
|
|
cc->condition = r->condition;
|
|
|
|
cc->negative = r->negative;
|
|
|
|
AddListItem(cc, *out);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void preprocessor_cc_free_list(ConditionalConfig *cc)
|
|
|
|
{
|
|
|
|
ConditionalConfig *cc_next;
|
|
|
|
|
|
|
|
for (; cc; cc = cc_next)
|
|
|
|
{
|
|
|
|
cc_next = cc->next;
|
|
|
|
safe_free(cc->name);
|
|
|
|
safe_free(cc->opt);
|
|
|
|
safe_free(cc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NameValueList *find_config_define(const char *name)
|
|
|
|
{
|
|
|
|
NameValueList *n;
|
|
|
|
|
|
|
|
for (n = config_defines; n; n = n->next)
|
|
|
|
if (!strcasecmp(n->name, name))
|
|
|
|
return n;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Resolve a preprocessor condition to true (=default) or false */
|
|
|
|
int preprocessor_resolve_if(ConditionalConfig *cc, PreprocessorPhase phase)
|
|
|
|
{
|
|
|
|
int result = 0;
|
|
|
|
|
|
|
|
if (!cc)
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
if (cc->condition == IF_MODULE)
|
|
|
|
{
|
|
|
|
if (phase == PREPROCESSOR_PHASE_INITIAL)
|
|
|
|
return 1; /* we cannot handle @if module-loaded() yet.. */
|
|
|
|
if (is_module_loaded(cc->name))
|
|
|
|
{
|
|
|
|
result = 1;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
if (cc->condition == IF_DEFINED)
|
|
|
|
{
|
|
|
|
NameValueList *d = find_config_define(cc->name);
|
|
|
|
if (d)
|
|
|
|
{
|
|
|
|
result = 1;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
if (cc->condition == IF_VALUE)
|
|
|
|
{
|
|
|
|
NameValueList *d = find_config_define(cc->name);
|
|
|
|
if (d && !strcasecmp(d->value, cc->opt))
|
|
|
|
{
|
|
|
|
result = 1;
|
|
|
|
}
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
config_status("[BUG] unhandled @if type!!");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (cc->negative)
|
|
|
|
result = result ? 0 : 1;
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
void preprocessor_resolve_conditionals_ce(ConfigEntry **ce_list, PreprocessorPhase phase)
|
|
|
|
{
|
2022-01-15 05:16:34 +00:00
|
|
|
ConfigEntry *ce, *next, *ce_prev;
|
2020-03-29 09:16:53 +00:00
|
|
|
ConfigEntry *cep, *cep_next, *cep_prev;
|
|
|
|
|
|
|
|
ce_prev = NULL;
|
2022-01-15 05:16:34 +00:00
|
|
|
for (ce = *ce_list; ce; ce = next)
|
2020-03-29 09:16:53 +00:00
|
|
|
{
|
2022-01-15 05:16:34 +00:00
|
|
|
next = ce->next;
|
2020-03-29 09:16:53 +00:00
|
|
|
/* This is for an @if before a block start */
|
2022-01-15 05:16:34 +00:00
|
|
|
if (!preprocessor_resolve_if(ce->conditional_config, phase))
|
2020-03-29 09:16:53 +00:00
|
|
|
{
|
|
|
|
/* Delete this entry */
|
|
|
|
if (ce == *ce_list)
|
|
|
|
{
|
|
|
|
/* we are head, so new head */
|
2022-01-15 05:16:34 +00:00
|
|
|
*ce_list = ce->next; /* can be NULL now */
|
2020-03-29 09:16:53 +00:00
|
|
|
} else {
|
|
|
|
/* non-head */
|
2022-01-15 05:16:34 +00:00
|
|
|
ce_prev->next = ce->next; /* can be NULL now */
|
2020-03-29 09:16:53 +00:00
|
|
|
}
|
|
|
|
config_entry_free(ce);
|
|
|
|
continue;
|
|
|
|
}
|
2022-01-15 05:16:34 +00:00
|
|
|
preprocessor_resolve_conditionals_ce(&ce->items, phase);
|
2020-03-29 09:16:53 +00:00
|
|
|
ce_prev = ce;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void preprocessor_resolve_conditionals_all(PreprocessorPhase phase)
|
|
|
|
{
|
|
|
|
ConfigFile *cfptr;
|
|
|
|
|
2022-01-15 05:16:34 +00:00
|
|
|
for (cfptr = conf; cfptr; cfptr = cfptr->next)
|
|
|
|
preprocessor_resolve_conditionals_ce(&cfptr->items, phase);
|
2020-03-29 09:16:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** Frees the list of config_defines, so all @defines */
|
|
|
|
void free_config_defines(void)
|
|
|
|
{
|
|
|
|
NameValueList *e, *e_next;
|
|
|
|
for (e = config_defines; e; e = e_next)
|
|
|
|
{
|
|
|
|
e_next = e->next;
|
|
|
|
safe_free(e->name);
|
|
|
|
safe_free(e->value);
|
|
|
|
safe_free(e);
|
|
|
|
}
|
|
|
|
config_defines = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Return value of defined value */
|
|
|
|
char *get_config_define(char *name)
|
|
|
|
{
|
|
|
|
NameValueList *e;
|
|
|
|
|
|
|
|
for (e = config_defines; e; e = e->next)
|
|
|
|
{
|
|
|
|
if (!strcasecmp(e->name, name))
|
|
|
|
return e->value;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void preprocessor_replace_defines(char **item, ConfigEntry *ce)
|
|
|
|
{
|
|
|
|
static char buf[4096];
|
|
|
|
char varname[512];
|
|
|
|
const char *i, *varstart, *varend;
|
|
|
|
char *o;
|
|
|
|
int n = sizeof(buf)-2;
|
|
|
|
int limit;
|
|
|
|
char *value;
|
|
|
|
|
|
|
|
if (!strchr(*item, '$'))
|
|
|
|
return; /* quick return in 99% of the cases */
|
|
|
|
|
|
|
|
o = buf;
|
|
|
|
for (i = *item; *i; i++)
|
|
|
|
{
|
|
|
|
if (*i != '$')
|
|
|
|
{
|
|
|
|
*o++ = *i;
|
|
|
|
if (--n == 0)
|
|
|
|
break;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* $ encountered: */
|
|
|
|
varstart = i;
|
|
|
|
i++;
|
|
|
|
for (; *i && ValidVarCharacter(*i); i++);
|
|
|
|
varend = i;
|
|
|
|
i--;
|
|
|
|
limit = varend - varstart + 1;
|
|
|
|
if (limit > sizeof(varname))
|
|
|
|
limit = sizeof(varname);
|
|
|
|
strlcpy(varname, varstart, limit);
|
|
|
|
if (!strncmp(varstart, "$$", 2))
|
|
|
|
{
|
|
|
|
/* If we get here then we encountered "$$"
|
|
|
|
* and varname is just "$".
|
|
|
|
* This means we are hitting a $ escape sequence,
|
|
|
|
* the $$ is used as a literal $.
|
|
|
|
* Would be very rare if you need it, but.. we support it.
|
|
|
|
*/
|
|
|
|
value = varname; /* bit confusing, but no +1 here */
|
|
|
|
i++; /* skip extra $ in input */
|
|
|
|
} else
|
|
|
|
{
|
|
|
|
value = get_config_define(varname+1);
|
|
|
|
if (!value)
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
/* Complain about $VARS if they are not defined, but don't bother
|
|
|
|
* for cases where it's clearly not a macro, eg. contains illegal
|
|
|
|
* variable characters.
|
|
|
|
*/
|
|
|
|
if ((limit > 2) && ((*varend == '\0') || strchr("\t ,.", *varend)))
|
|
|
|
{
|
|
|
|
config_warn("%s:%d: Variable %s used here but there's no @define for it earlier.",
|
2022-01-15 05:16:34 +00:00
|
|
|
ce->file->filename, ce->line_number, varname);
|
2020-03-29 09:16:53 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
value = varname; /* not found? then use varname, including the '$' */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
limit = strlen(value) + 1;
|
|
|
|
if (limit > n)
|
|
|
|
limit = n;
|
|
|
|
strlcpy(o, value, limit);
|
|
|
|
o += limit - 1;
|
|
|
|
n -= limit;
|
|
|
|
if (n == 0)
|
|
|
|
break; /* no output buffer left */
|
|
|
|
if (*varend == 0)
|
|
|
|
break; /* no input buffer left */
|
|
|
|
}
|
|
|
|
*o = '\0';
|
|
|
|
safe_strdup(*item, buf);
|
|
|
|
}
|