Path: blob/main/crypto/krb5/src/util/profile/prof_parse.c
34889 views
/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */1#include "prof_int.h"23#include <sys/types.h>4#include <stdio.h>5#include <string.h>6#ifdef HAVE_STDLIB_H7#include <stdlib.h>8#endif9#include <errno.h>10#include <ctype.h>11#ifndef _WIN3212#include <dirent.h>13#endif1415#define SECTION_SEP_CHAR '/'1617#define STATE_INIT_COMMENT 118#define STATE_STD_LINE 219#define STATE_GET_OBRACE 32021struct parse_state {22int state;23int group_level;24int discard; /* group_level of a final-flagged section */25struct profile_node *root_section;26struct profile_node *current_section;27};2829static errcode_t parse_file(FILE *f, struct parse_state *state,30char **ret_modspec);3132static char *skip_over_blanks(char *cp)33{34while (*cp && isspace((int) (*cp)))35cp++;36return cp;37}3839static void strip_line(char *line)40{41char *p = line + strlen(line);42while (p > line && (p[-1] == '\n' || p[-1] == '\r'))43*--p = 0;44}4546static void parse_quoted_string(char *str)47{48char *to, *from;4950for (to = from = str; *from && *from != '"'; to++, from++) {51if (*from == '\\' && *(from + 1) != '\0') {52from++;53switch (*from) {54case 'n':55*to = '\n';56break;57case 't':58*to = '\t';59break;60case 'b':61*to = '\b';62break;63default:64*to = *from;65}66continue;67}68*to = *from;69}70*to = '\0';71}727374static errcode_t parse_std_line(char *line, struct parse_state *state)75{76char *cp, ch, *tag, *value;77char *p;78errcode_t retval;79struct profile_node *node;80int do_subsection = 0;8182if (*line == 0)83return 0;84cp = skip_over_blanks(line);85if (cp[0] == ';' || cp[0] == '#')86return 0;87strip_line(cp);88ch = *cp;89if (ch == 0)90return 0;91if (ch == '[') {92if (state->group_level > 1)93return PROF_SECTION_NOTOP;94cp++;95p = strchr(cp, ']');96if (p == NULL)97return PROF_SECTION_SYNTAX;98*p = '\0';99retval = profile_add_node(state->root_section, cp, NULL, 0,100&state->current_section);101if (retval)102return retval;103state->group_level = 1;104/* If we previously saw this section name with the final flag,105* discard values until the next top-level section. */106state->discard = profile_is_node_final(state->current_section) ?1071 : 0;108109/*110* Finish off the rest of the line.111*/112cp = p+1;113if (*cp == '*') {114profile_make_node_final(state->current_section);115cp++;116}117/*118* A space after ']' should not be fatal119*/120cp = skip_over_blanks(cp);121if (*cp)122return PROF_SECTION_SYNTAX;123return 0;124}125if (ch == '}') {126if (state->group_level < 2)127return PROF_EXTRA_CBRACE;128if (*(cp+1) == '*')129profile_make_node_final(state->current_section);130state->group_level--;131/* Check if we are done discarding values from a subsection. */132if (state->group_level < state->discard)133state->discard = 0;134/* Ascend to the current node's parent, unless the subsection we ended135* was discarded (in which case we never descended). */136if (!state->discard) {137retval = profile_get_node_parent(state->current_section,138&state->current_section);139if (retval)140return retval;141}142return 0;143}144/*145* Parse the relations146*/147tag = cp;148cp = strchr(cp, '=');149if (!cp)150return PROF_RELATION_SYNTAX;151if (cp == tag)152return PROF_RELATION_SYNTAX;153*cp = '\0';154p = tag;155/* Look for whitespace on left-hand side. */156while (p < cp && !isspace((int)*p))157p++;158if (p < cp) {159/* Found some sort of whitespace. */160*p++ = 0;161/* If we have more non-whitespace, it's an error. */162while (p < cp) {163if (!isspace((int)*p))164return PROF_RELATION_SYNTAX;165p++;166}167}168cp = skip_over_blanks(cp+1);169value = cp;170if (value[0] == '"') {171value++;172parse_quoted_string(value);173} else if (value[0] == 0) {174do_subsection++;175state->state = STATE_GET_OBRACE;176} else if (value[0] == '{' && *(skip_over_blanks(value+1)) == 0)177do_subsection++;178else {179cp = value + strlen(value) - 1;180while ((cp > value) && isspace((int) (*cp)))181*cp-- = 0;182}183if (do_subsection) {184p = strchr(tag, '*');185if (p)186*p = '\0';187state->group_level++;188if (!state->discard) {189retval = profile_add_node(state->current_section, tag, NULL, 0,190&state->current_section);191if (retval)192return retval;193/* If we previously saw this subsection with the final flag,194* discard values until the subsection is done. */195if (profile_is_node_final(state->current_section))196state->discard = state->group_level;197if (p)198profile_make_node_final(state->current_section);199}200return 0;201}202p = strchr(tag, '*');203if (p)204*p = '\0';205if (!state->discard) {206profile_add_node(state->current_section, tag, value, 1, &node);207if (p && node)208profile_make_node_final(node);209}210return 0;211}212213/* Open and parse an included profile file. */214static errcode_t parse_include_file(const char *filename,215struct profile_node *root_section)216{217FILE *fp;218errcode_t retval = 0;219struct parse_state state;220221/* Create a new state so that fragments are syntactically independent but222* share a root section. */223state.state = STATE_INIT_COMMENT;224state.group_level = state.discard = 0;225state.root_section = root_section;226state.current_section = NULL;227228fp = fopen(filename, "r");229if (fp == NULL)230return PROF_FAIL_INCLUDE_FILE;231retval = parse_file(fp, &state, NULL);232fclose(fp);233return retval;234}235236/* Return non-zero if filename contains only alphanumeric characters, dashes,237* and underscores, or if the filename ends in ".conf" and is not a dotfile. */238static int valid_name(const char *filename)239{240const char *p;241size_t len = strlen(filename);242243/* Ignore dotfiles, which might be editor or filesystem artifacts. */244if (*filename == '.')245return 0;246247if (len >= 5 && !strcmp(filename + len - 5, ".conf"))248return 1;249250for (p = filename; *p != '\0'; p++) {251if (!isalnum((unsigned char)*p) && *p != '-' && *p != '_')252return 0;253}254return 1;255}256257/*258* Include files within dirname. Only files with names ending in ".conf", or259* consisting entirely of alphanumeric characters, dashes, and underscores are260* included. This restriction avoids including editor backup files, .rpmsave261* files, and the like. Files are processed in alphanumeric order.262*/263static errcode_t parse_include_dir(const char *dirname,264struct profile_node *root_section)265{266errcode_t retval = 0;267char **fnames, *pathname;268int i;269270if (k5_dir_filenames(dirname, &fnames) != 0)271return PROF_FAIL_INCLUDE_DIR;272273for (i = 0; fnames != NULL && fnames[i] != NULL; i++) {274if (!valid_name(fnames[i]))275continue;276if (asprintf(&pathname, "%s/%s", dirname, fnames[i]) < 0) {277retval = ENOMEM;278break;279}280retval = parse_include_file(pathname, root_section);281free(pathname);282if (retval)283break;284}285k5_free_filenames(fnames);286return retval;287}288289static errcode_t parse_line(char *line, struct parse_state *state,290char **ret_modspec)291{292char *cp;293294#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION295if (strncmp(line, "include", 7) == 0 && isspace(line[7])) {296cp = skip_over_blanks(line + 7);297strip_line(cp);298return parse_include_file(cp, state->root_section);299}300if (strncmp(line, "includedir", 10) == 0 && isspace(line[10])) {301cp = skip_over_blanks(line + 10);302strip_line(cp);303return parse_include_dir(cp, state->root_section);304}305#endif306switch (state->state) {307case STATE_INIT_COMMENT:308if (strncmp(line, "module", 6) == 0 && isspace(line[6])) {309/*310* If we are expecting a module declaration, fill in *ret_modspec311* and return PROF_MODULE, which will cause parsing to abort and312* the module to be loaded instead. If we aren't expecting a313* module declaration, return PROF_MODULE without filling in314* *ret_modspec, which will be treated as an ordinary error.315*/316if (ret_modspec) {317cp = skip_over_blanks(line + 6);318strip_line(cp);319*ret_modspec = strdup(cp);320if (!*ret_modspec)321return ENOMEM;322}323return PROF_MODULE;324}325if (line[0] != '[')326return 0;327state->state = STATE_STD_LINE;328case STATE_STD_LINE:329return parse_std_line(line, state);330case STATE_GET_OBRACE:331cp = skip_over_blanks(line);332if (*cp != '{')333return PROF_MISSING_OBRACE;334state->state = STATE_STD_LINE;335}336return 0;337}338339static errcode_t parse_file(FILE *f, struct parse_state *state,340char **ret_modspec)341{342#define BUF_SIZE 2048343char *bptr;344errcode_t retval;345346bptr = malloc (BUF_SIZE);347if (!bptr)348return ENOMEM;349350while (!feof(f)) {351if (fgets(bptr, BUF_SIZE, f) == NULL)352break;353#ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES354retval = parse_line(bptr, state, ret_modspec);355if (retval) {356free (bptr);357return retval;358}359#else360{361char *p, *end;362363if (strlen(bptr) >= BUF_SIZE - 1) {364/* The string may have foreign newlines and365gotten chopped off on a non-newline366boundary. Seek backwards to the last known367newline. */368long offset;369char *c = bptr + strlen (bptr);370for (offset = 0; offset > -BUF_SIZE; offset--) {371if (*c == '\r' || *c == '\n') {372*c = '\0';373fseek (f, offset, SEEK_CUR);374break;375}376c--;377}378}379380/* First change all newlines to \n */381for (p = bptr; *p != '\0'; p++) {382if (*p == '\r')383*p = '\n';384}385/* Then parse all lines */386p = bptr;387end = bptr + strlen (bptr);388while (p < end) {389char* newline;390char* newp;391392newline = strchr (p, '\n');393if (newline != NULL)394*newline = '\0';395396/* parse_line modifies contents of p */397newp = p + strlen (p) + 1;398retval = parse_line (p, state, ret_modspec);399if (retval) {400free (bptr);401return retval;402}403404p = newp;405}406}407#endif408}409410free (bptr);411return 0;412}413414errcode_t profile_parse_file(FILE *f, struct profile_node **root,415char **ret_modspec)416{417struct parse_state state;418errcode_t retval;419420*root = NULL;421422/* Initialize parsing state with a new root node. */423state.state = STATE_INIT_COMMENT;424state.group_level = state.discard = 0;425state.current_section = NULL;426retval = profile_create_node("(root)", 0, &state.root_section);427if (retval)428return retval;429430retval = parse_file(f, &state, ret_modspec);431if (retval) {432profile_free_node(state.root_section);433return retval;434}435*root = state.root_section;436return 0;437}438439errcode_t profile_process_directory(const char *dirname,440struct profile_node **root)441{442errcode_t retval;443struct profile_node *node;444445*root = NULL;446retval = profile_create_node("(root)", 0, &node);447if (retval)448return retval;449retval = parse_include_dir(dirname, node);450if (retval) {451profile_free_node(node);452return retval;453}454*root = node;455return 0;456}457458/*459* Return TRUE if the string begins or ends with whitespace460*/461static int need_double_quotes(char *str)462{463if (!str)464return 0;465if (str[0] == '\0')466return 1;467if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1))))468return 1;469if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b'))470return 1;471return 0;472}473474/*475* Output a string with double quotes, doing appropriate backquoting476* of characters as necessary.477*/478static void output_quoted_string(char *str, void (*cb)(const char *,void *),479void *data)480{481char ch;482char buf[2];483484cb("\"", data);485if (!str) {486cb("\"", data);487return;488}489buf[1] = 0;490while ((ch = *str++)) {491switch (ch) {492case '\\':493cb("\\\\", data);494break;495case '\n':496cb("\\n", data);497break;498case '\t':499cb("\\t", data);500break;501case '\b':502cb("\\b", data);503break;504default:505/* This would be a lot faster if we scanned506forward for the next "interesting"507character. */508buf[0] = ch;509cb(buf, data);510break;511}512}513cb("\"", data);514}515516517518#if defined(_WIN32)519#define EOL "\r\n"520#endif521522#ifndef EOL523#define EOL "\n"524#endif525526/* Errors should be returned, not ignored! */527static void dump_profile(struct profile_node *root, int level,528void (*cb)(const char *, void *), void *data)529{530int i, final;531struct profile_node *p;532void *iter;533long retval;534char *name, *value;535536iter = 0;537do {538retval = profile_find_node_relation(root, 0, &iter,539&name, &value, &final);540if (retval)541break;542for (i=0; i < level; i++)543cb("\t", data);544cb(name, data);545cb(final ? "*" : "", data);546cb(" = ", data);547if (need_double_quotes(value))548output_quoted_string(value, cb, data);549else550cb(value, data);551cb(EOL, data);552} while (iter != 0);553554iter = 0;555do {556retval = profile_find_node_subsection(root, 0, &iter,557&name, &p);558if (retval)559break;560if (level == 0) { /* [xxx] */561cb("[", data);562cb(name, data);563cb("]", data);564cb(profile_is_node_final(p) ? "*" : "", data);565cb(EOL, data);566dump_profile(p, level+1, cb, data);567cb(EOL, data);568} else { /* xxx = { ... } */569for (i=0; i < level; i++)570cb("\t", data);571cb(name, data);572cb(" = {", data);573cb(EOL, data);574dump_profile(p, level+1, cb, data);575for (i=0; i < level; i++)576cb("\t", data);577cb("}", data);578cb(profile_is_node_final(p) ? "*" : "", data);579cb(EOL, data);580}581} while (iter != 0);582}583584static void dump_profile_to_file_cb(const char *str, void *data)585{586fputs(str, data);587}588589errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile)590{591dump_profile(root, 0, dump_profile_to_file_cb, dstfile);592return 0;593}594595struct prof_buf {596char *base;597size_t cur, max;598int err;599};600601static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len)602{603if (b->err)604return;605if (b->max - b->cur < len) {606size_t newsize;607char *newptr;608609newsize = b->max + (b->max >> 1) + len + 1024;610newptr = realloc(b->base, newsize);611if (newptr == NULL) {612b->err = 1;613return;614}615b->base = newptr;616b->max = newsize;617}618memcpy(b->base + b->cur, d, len);619b->cur += len; /* ignore overflow */620}621622static void dump_profile_to_buffer_cb(const char *str, void *data)623{624add_data_to_buffer((struct prof_buf *)data, str, strlen(str));625}626627errcode_t profile_write_tree_to_buffer(struct profile_node *root,628char **buf)629{630struct prof_buf prof_buf = { 0, 0, 0, 0 };631632dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf);633if (prof_buf.err) {634*buf = NULL;635return ENOMEM;636}637add_data_to_buffer(&prof_buf, "", 1); /* append nul */638if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) {639char *newptr = realloc(prof_buf.base, prof_buf.cur);640if (newptr)641prof_buf.base = newptr;642}643*buf = prof_buf.base;644return 0;645}646647648