=================================================================== RCS file: /cvs/cvs/blind/parse.y,v retrieving revision 1.5 retrieving revision 1.18 diff -u -p -r1.5 -r1.18 --- blind/parse.y 2022/03/20 22:00:51 1.5 +++ blind/parse.y 2022/04/10 21:20:24 1.18 @@ -16,53 +16,60 @@ %{ #include -#include -#include +#include #include -#include - +#include + #include "blind.h" +#include "config.h" #include "log.h" -TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); -static struct file { - TAILQ_ENTRY(file) entry; - FILE *stream; - char *name; - size_t ungetpos; - size_t ungetsize; - u_char *ungetbuf; - int eof_reached; - int lineno; - int errors; -} *file, *top; +#define EXPAND_ON 1 +#define EXPAND_OFF 2 +int lookup(char *); +int igetc(void); +int lgetc(int); +int findeol(void); +void lungetc(int); +int kw_cmp(const void *, const void *); int yyparse(void); int yylex(void); int yyerror(const char *, ...) - __attribute__((__format__ (printf, 1, 2))) - __attribute__((__nonnull__ (1))); + __attribute__((__format__ (printf, 1, 2))) + __attribute__((__nonnull__ (1))); +char *symget(const char *); + int config_load(struct blind *); -struct file *config_push(const char *); -int config_pop(void); -int config_perm(int, const char *); +int config_close(void); -struct blind *env = NULL; -static int errors = 0; +static int expanding; +static int errors = 0; +struct blind *env = NULL; +static struct file *f = NULL; +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; + typedef struct { union { - int64_t number; - char *string; + int64_t number; + char *string; } v; int lineno; } YYSTYPE; %} -%token EXPIRE +%token ACTION ARROW DISABLE ENABLE EXPIRE ERROR SET %token NUMBER %token STRING @@ -70,126 +77,401 @@ typedef struct { grammar : | grammar '\n' - | grammar expire '\n' - | grammar error '\n' { file->errors++; } - ; + | grammar set '\n' + | grammar error '\n' { errors++; } + ; -expire : EXPIRE NUMBER { - log_debug("found EXPIRE"); - env->bl_ttl = $2; +set : SET EXPIRE NUMBER { + env->bl_ttl = $3; } +| SET ACTION ENABLE { + env->bl_opt |= BL_OPT_ACTION; + } +| SET ACTION DISABLE { + env->bl_opt |= !BL_OPT_ACTION; + } ; %% +struct keywords { + const char *k_name; + int k_val; +}; + int yyerror(const char *fmt, ...) { + va_list ap; + char *msg; + + errors++; + va_start(ap, fmt); + if (vasprintf(&msg, fmt, ap) == -1) + log_fatal("yyerror vasprintf"); + va_end(ap); + log_info("%s:%d: %s", f->name, yylval.lineno, msg); + free(msg); return (0); } int -yylex(void) +kw_cmp(const void *k, const void *e) { - return (0); + return (strcmp(k, ((const struct keywords *)e)->k_name)); } int -config_load(struct blind *temp) +lookup(char *s) { - env = temp; - errors = 0; + /* this has to be sorted always */ + static const struct keywords keywords[] = { + { "action", ACTION }, + { "disable", DISABLE }, + { "enable", ENABLE }, + { "expire", EXPIRE }, + { "set", SET }, + }; + const struct keywords *p; - if ((file = config_push(env->bl_conf)) == NULL) { - // config_purge(PURGE_ALL); - return (-1); - } - top = file; + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); - yyparse(); + if (p) + return (p->k_val); + else + return (STRING); +} - errors = file->errors; - config_pop(); +char * +symget(const char *nam) +{ + struct sym *sym; - // setup + TAILQ_FOREACH(sym, &symhead, entry) { + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + } + return (NULL); +} - return (0); +int +igetc(void) +{ + int c; + + while (1) { + if (f->unpos > 0) + c = f->unbuf[--f->unpos]; + else + c = getc(f->stream); + + if (c == EXPAND_ON) + expanding = 1; + else if (c == EXPAND_OFF) + expanding = 0; + else + break; + } + return (c); } -struct file * -config_push(const char *name) +int +lgetc(int quotec) { - struct file *nfile; + int c, next; - if ((nfile = calloc(1, sizeof(struct file))) == NULL) { - log_debug("cannot allocate memory"); - return (NULL); - } - if ((nfile->name = strdup(name)) == NULL) { - log_debug("cannot duplicate name"); - free(nfile); - return (NULL); - } - if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { - log_debug("cannot open config file"); - free(nfile->name); - free(nfile); - return (NULL); - } - if (config_perm(fileno(nfile->stream), nfile->name)) { - fclose(nfile->stream); - free(nfile->name); - free(nfile); - return (NULL); - } - nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; - nfile->ungetsize = 16; - nfile->ungetbuf = malloc(nfile->ungetsize); - if (nfile->ungetbuf == NULL) { - log_debug("cannot allocate buffer"); - fclose(nfile->stream); - free(nfile->name); - free(nfile); - return (NULL); - } - TAILQ_INSERT_TAIL(&files, nfile, entry); - return (nfile); + if (quotec) { + if ((c = igetc()) == EOF) { + yyerror("reached end of file while parsing " + "quoted string"); + if (config_close() == EOF) + return (EOF); + return (quotec); + } + return (c); + } + + while ((c = igetc()) == '\\') { + next = igetc(); + if (next != '\n') { + c = next; + break; + } + yylval.lineno = f->lineno; + f->lineno++; + } + + if (c == EOF) { + /* + * Fake EOL when hit EOF for the first time. This gets line + * count right if last line in included file is syntactically + * invalid and has no newline. + */ + if (f->eof == 0) { + f->eof = 1; + return ('\n'); + } + while (c == EOF) { + if (config_close() == EOF) + return (EOF); + c = igetc(); + } + } + return (c); } +void +lungetc(int c) +{ + if (c == EOF) + return; + + if (f->unpos >= f->unsize) { + void *p = reallocarray(f->unbuf, f->unsize, 2); + if (p == NULL) + log_fatal("cannot reallocate memory"); + f->unbuf = p; + f->unsize *= 2; + } + f->unbuf[f->unpos++] = c; +} + int -config_pop(void) +findeol(void) { - struct file *prv; + int c; - if ((prv = TAILQ_PREV(file, files, entry)) != NULL) - prv->errors += file->errors; + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(0); + if (c == '\n') { + f->lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} - TAILQ_REMOVE(&files, file, entry); - fclose(file->stream); - free(file->name); - free(file->ungetbuf); - free(file); - file = prv; - - return (file ? 0 : EOF); +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int quotec, next, c; + int token; + +top: + p = buf; + while ((c = lgetc(0)) == ' ' || c == '\t') + ; /* nothing */ + + yylval.lineno = f->lineno; + if (c == '#') + while ((c = lgetc(0)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && !expanding) { + while (1) { + if ((c = lgetc(0)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro '%s' not defined", buf); + return (findeol()); + } + p = val + strlen(val) - 1; + lungetc(EXPAND_OFF); + while (p >= val) { + lungetc((unsigned char)*p); + p--; + } + lungetc(EXPAND_ON); + goto top; + } + + switch (c) { + case '\'': + case '"': + quotec = c; + while (1) { + if ((c = lgetc(quotec)) == EOF) + return (0); + if (c == '\n') { + f->lineno++; + continue; + } else if (c == '\\') { + if ((next = lgetc(quotec)) == EOF) + return (0); + if (next == quotec || next == ' ' || + next == '\t') + c = next; + else if (next == '\n') { + f->lineno++; + continue; + } else + lungetc(next); + } else if (c == quotec) { + *p = '\0'; + break; + } else if (c == '\0') { + yyerror("syntax error"); + return (findeol()); + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + log_fatal("cannot duplicate buffer"); + return (STRING); + } + +#define allowed_to_end_number(x) \ + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') + + if (c == '-' || isdigit(c)) { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && isdigit(c)); + lungetc(c); + if (p == buf + 1 && buf[0] == '-') + goto nodigits; + if (c == EOF || allowed_to_end_number(c)) { + const char *errstr = NULL; + + *p = '\0'; + yylval.v.number = strtonum(buf, LLONG_MIN, + LLONG_MAX, &errstr); + if (errstr) { + yyerror("\"%s\" invalid number: %s", + buf, errstr); + return (findeol()); + } + return (NUMBER); + } else { +nodigits: + while (p > buf + 1) + lungetc((unsigned char)*--p); + c = (unsigned char)*--p; + if (c == '-') + return (c); + } + } + + if (c == '=') { + if ((c = lgetc(0)) != EOF && c == '>') + return (ARROW); + lungetc(c); + c = '='; + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_') { + do { + *p++ = c; + if ((size_t)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + log_fatal("cannot duplicate buffer"); + return (token); + } + if (c == '\n') { + yylval.lineno = f->lineno; + f->lineno++; + } + if (c == EOF) + return (0); + return (c); } int -config_perm(int fd, const char *name) +config_load(struct blind *temp) { - struct stat st; + env = temp; + errors = 0; - if (fstat(fd, &st)) { - log_debug("cannot stat config file"); - return (-1); + if ((f = calloc(1, sizeof(struct file))) == NULL) { + log_debug("cannot allocate memory"); + return (-1); + } + if ((f->name = strdup(env->bl_conf)) == NULL) { + log_debug("cannot duplicate name"); + free(f); + return (-1); } - if (st.st_uid != 0 && st.st_uid != getuid()) { - log_debug("not root or current user owned"); - return (-1); - } - if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { - log_debug("insecure config file"); - return (-1); - } - return (0); + if ((f->stream = fopen(f->name, "r")) == NULL) { + log_debug("cannot open config file"); + free(f->name); + free(f); + return (-1); + } + if (config_perm(fileno(f->stream), f->name)) { + fclose(f->stream); + free(f->name); + free(f); + return (-1); + } + f->lineno = 1; + f->unsize = 16; + if ((f->unbuf = malloc(f->unsize)) == NULL) { + log_debug("cannot allocate buffer"); + fclose(f->stream); + free(f->name); + free(f); + return (-1); + } + + yyparse(); + // setup + + if (errors) + return (-1); + + return (0); +} + +int +config_close(void) +{ + fclose(f->stream); + free(f->name); + free(f->unbuf); + free(f); + + return (EOF); }