root/usr/src/lib/gss_mechs/mech_krb5/profile/prof_parse.c
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */


#include "prof_int.h"

#include <stdio.h>
#include <string.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <errno.h>
#include <ctype.h>

#define SECTION_SEP_CHAR '/'

#define STATE_INIT_COMMENT      1
#define STATE_STD_LINE          2
#define STATE_GET_OBRACE        3

struct parse_state {
        int     state;
        int     group_level;
        struct profile_node *root_section;
        struct profile_node *current_section;
};

static char *skip_over_blanks(char *cp)
{
        while (*cp && isspace((int) (*cp)))
                cp++;
        return cp;
}

static void strip_line(char *line)
{
        char *p = line + strlen(line);
        while (p > line && (p[-1] == '\n' || p[-1] == '\r'))
            *p-- = 0;
}

static void parse_quoted_string(char *str)
{
        char *to, *from;

        to = from = str;

        for (to = from = str; *from && *from != '"'; to++, from++) {
                if (*from == '\\') {
                        from++;
                        switch (*from) {
                        case 'n':
                                *to = '\n';
                                break;
                        case 't':
                                *to = '\t';
                                break;
                        case 'b':
                                *to = '\b';
                                break;
                        default:
                                *to = *from;
                        }
                        continue;
                }
                *to = *from;
        }
        *to = '\0';
}


static errcode_t parse_init_state(struct parse_state *state)
{
        state->state = STATE_INIT_COMMENT;
        state->group_level = 0;

        return profile_create_node("(root)", 0, &state->root_section);
}

static errcode_t parse_std_line(char *line, struct parse_state *state)
{
        char    *cp, ch, *tag, *value;
        char    *p;
        errcode_t retval;
        struct profile_node     *node;
        int do_subsection = 0;
        void *iter = 0;

        if (*line == 0)
                return 0;
        cp = skip_over_blanks(line);
        if (cp[0] == ';' || cp[0] == '#')
                return 0;
        strip_line(cp);
        ch = *cp;
        if (ch == 0)
                return 0;
        if (ch == '[') {
                if (state->group_level > 0)
                        return PROF_SECTION_NOTOP;
                cp++;
                p = strchr(cp, ']');
                if (p == NULL)
                        return PROF_SECTION_SYNTAX;
                *p = '\0';
                retval = profile_find_node_subsection(state->root_section,
                                                 cp, &iter, 0,
                                                 &state->current_section);
                if (retval == PROF_NO_SECTION) {
                        retval = profile_add_node(state->root_section,
                                                  cp, 0,
                                                  &state->current_section);
                        if (retval)
                                return retval;
                } else if (retval)
                        return retval;

                /*
                 * Finish off the rest of the line.
                 */
                cp = p+1;
                if (*cp == '*') {
                        profile_make_node_final(state->current_section);
                        cp++;
                }
                /*
                 * A space after ']' should not be fatal
                 */
                cp = skip_over_blanks(cp);
                if (*cp)
                        return PROF_SECTION_SYNTAX;
                return 0;
        }
        if (ch == '}') {
                if (state->group_level == 0)
                        return PROF_EXTRA_CBRACE;
                if (*(cp+1) == '*')
                        profile_make_node_final(state->current_section);
                retval = profile_get_node_parent(state->current_section,
                                                 &state->current_section);
                if (retval)
                        return retval;
                state->group_level--;
                return 0;
        }
        /*
         * Parse the relations
         */
        tag = cp;
        cp = strchr(cp, '=');
        if (!cp)
                return PROF_RELATION_SYNTAX;
        if (cp == tag)
            return PROF_RELATION_SYNTAX;
        *cp = '\0';
        p = tag;
        /* Look for whitespace on left-hand side.  */
        while (p < cp && !isspace((int)*p))
            p++;
        if (p < cp) {
            /* Found some sort of whitespace.  */
            *p++ = 0;
            /* If we have more non-whitespace, it's an error.  */
            while (p < cp) {
                if (!isspace((int)*p))
                    return PROF_RELATION_SYNTAX;
                p++;
            }
        }
        cp = skip_over_blanks(cp+1);
        value = cp;
        if (value[0] == '"') {
                value++;
                parse_quoted_string(value);
        } else if (value[0] == 0) {
                do_subsection++;
                state->state = STATE_GET_OBRACE;
        } else if (value[0] == '{' && *(skip_over_blanks(value+1)) == 0)
                do_subsection++;
        else {
                cp = value + strlen(value) - 1;
                while ((cp > value) && isspace((int) (*cp)))
                        *cp-- = 0;
        }
        if (do_subsection) {
                p = strchr(tag, '*');
                if (p)
                        *p = '\0';
                retval = profile_add_node(state->current_section,
                                          tag, 0, &state->current_section);
                if (retval)
                        return retval;
                if (p)
                        profile_make_node_final(state->current_section);
                state->group_level++;
                return 0;
        }
        p = strchr(tag, '*');
        if (p)
                *p = '\0';
        profile_add_node(state->current_section, tag, value, &node);
        if (p)
                profile_make_node_final(node);
        return 0;
}

static errcode_t parse_line(char *line, struct parse_state *state)
{
        char    *cp;

        switch (state->state) {
        case STATE_INIT_COMMENT:
                if (line[0] != '[')
                        return 0;
                state->state = STATE_STD_LINE;
                /*FALLTHRU*/
        case STATE_STD_LINE:
                return parse_std_line(line, state);
        case STATE_GET_OBRACE:
                cp = skip_over_blanks(line);
                if (*cp != '{')
                        return PROF_MISSING_OBRACE;
                state->state = STATE_STD_LINE;
                /*FALLTHRU*/
        }
        return 0;
}

errcode_t profile_parse_file(FILE *f, struct profile_node **root)
{
#define BUF_SIZE        2048
        char *bptr;
        errcode_t retval;
        struct parse_state state;

        bptr = malloc (BUF_SIZE);
        if (!bptr)
                return ENOMEM;

        retval = parse_init_state(&state);
        if (retval) {
                free (bptr);
                return retval;
        }
        while (!feof(f)) {
                if (fgets(bptr, BUF_SIZE, f) == NULL)
                        break;
#ifndef PROFILE_SUPPORTS_FOREIGN_NEWLINES
                retval = parse_line(bptr, &state);
                if (retval) {
                        /* Solaris Kerberos: check if an unconfigured file */
                        if (strstr(bptr, "___"))
                                retval = PROF_NO_PROFILE;
                        free (bptr);
                        return retval;
                }
#else
                {
                    char *p, *end;

                    if (strlen(bptr) >= BUF_SIZE - 1) {
                        /* The string may have foreign newlines and
                           gotten chopped off on a non-newline
                           boundary.  Seek backwards to the last known
                           newline.  */
                        long offset;
                        char *c = bptr + strlen (bptr);
                        for (offset = 0; offset > -BUF_SIZE; offset--) {
                            if (*c == '\r' || *c == '\n') {
                                *c = '\0';
                                fseek (f, offset, SEEK_CUR);
                                break;
                            }
                            c--;
                        }
                    }

                    /* First change all newlines to \n */
                    for (p = bptr; *p != '\0'; p++) {
                        if (*p == '\r')
                            *p = '\n';
                    }
                    /* Then parse all lines */
                    p = bptr;
                    end = bptr + strlen (bptr);
                    while (p < end) {
                        char* newline;
                        char* newp;

                        newline = strchr (p, '\n');
                        if (newline != NULL)
                            *newline = '\0';

                        /* parse_line modifies contents of p */
                        newp = p + strlen (p) + 1;
                        retval = parse_line (p, &state);
                        if (retval) {
                            free (bptr);
                            return retval;
                        }

                        p = newp;
                    }
                }
#endif
        }
        *root = state.root_section;

        free (bptr);
        return 0;
}

/*
 * Return TRUE if the string begins or ends with whitespace
 */
static int need_double_quotes(char *str)
{
        if (!str)
                return 0;
        if (str[0] == '\0')
                return 1;
        if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1))))
                return 1;
        if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b'))
                return 1;
        return 0;
}

/*
 * Output a string with double quotes, doing appropriate backquoting
 * of characters as necessary.
 */
static void output_quoted_string(char *str, void (*cb)(const char *,void *),
                                 void *data)
{
        char    ch;
        char buf[2];

        cb("\"", data);
        if (!str) {
                cb("\"", data);
                return;
        }
        buf[1] = 0;
        while ((ch = *str++)) {
                switch (ch) {
                case '\\':
                        cb("\\\\", data);
                        break;
                case '\n':
                        cb("\\n", data);
                        break;
                case '\t':
                        cb("\\t", data);
                        break;
                case '\b':
                        cb("\\b", data);
                        break;
                default:
                        /* This would be a lot faster if we scanned
                           forward for the next "interesting"
                           character.  */
                        buf[0] = ch;
                        cb(buf, data);
                        break;
                }
        }
        cb("\"", data);
}



#if defined(_WIN32)
#define EOL "\r\n"
#endif

#ifndef EOL
#define EOL "\n"
#endif

/* Errors should be returned, not ignored!  */
static void dump_profile(struct profile_node *root, int level,
                         void (*cb)(const char *, void *), void *data)
{
        int i;
        struct profile_node *p;
        void *iter;
        long retval;
        char *name, *value;

        iter = 0;
        do {
                retval = profile_find_node_relation(root, 0, &iter,
                                                    &name, &value);
                if (retval)
                        break;
                for (i=0; i < level; i++)
                        cb("\t", data);
                if (need_double_quotes(value)) {
                        cb(name, data);
                        cb(" = ", data);
                        output_quoted_string(value, cb, data);
                        cb(EOL, data);
                } else {
                        cb(name, data);
                        cb(" = ", data);
                        cb(value, data);
                        cb(EOL, data);
                }
        } while (iter != 0);

        iter = 0;
        do {
                retval = profile_find_node_subsection(root, 0, &iter,
                                                      &name, &p);
                if (retval)
                        break;
                if (level == 0) { /* [xxx] */
                        cb("[", data);
                        cb(name, data);
                        cb("]", data);
                        cb(profile_is_node_final(p) ? "*" : "", data);
                        cb(EOL, data);
                        dump_profile(p, level+1, cb, data);
                        cb(EOL, data);
                } else {        /* xxx = { ... } */
                        for (i=0; i < level; i++)
                                cb("\t", data);
                        cb(name, data);
                        cb(" = {", data);
                        cb(EOL, data);
                        dump_profile(p, level+1, cb, data);
                        for (i=0; i < level; i++)
                                cb("\t", data);
                        cb("}", data);
                        cb(profile_is_node_final(p) ? "*" : "", data);
                        cb(EOL, data);
                }
        } while (iter != 0);
}

static void dump_profile_to_file_cb(const char *str, void *data)
{
        fputs(str, data);
}

errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile)
{
        dump_profile(root, 0, dump_profile_to_file_cb, dstfile);
        return 0;
}

struct prof_buf {
        char *base;
        size_t cur, max;
        int err;
};

static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len)
{
        if (b->err)
                return;
        if (b->max - b->cur < len) {
                size_t newsize;
                char *newptr;

                newsize = b->max + (b->max >> 1) + len + 1024;
                newptr = realloc(b->base, newsize);
                if (newptr == NULL) {
                        b->err = 1;
                        return;
                }
                b->base = newptr;
                b->max = newsize;
        }
        memcpy(b->base + b->cur, d, len);
        b->cur += len;          /* ignore overflow */
}

static void dump_profile_to_buffer_cb(const char *str, void *data)
{
        add_data_to_buffer((struct prof_buf *)data, str, strlen(str));
}

errcode_t profile_write_tree_to_buffer(struct profile_node *root,
                                       char **buf)
{
        struct prof_buf prof_buf = { 0, 0, 0, 0 };

        dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf);
        if (prof_buf.err) {
                *buf = NULL;
                return ENOMEM;
        }
        add_data_to_buffer(&prof_buf, "", 1); /* append nul */
        if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) {
                char *newptr = realloc(prof_buf.base, prof_buf.cur);
                if (newptr)
                        prof_buf.base = newptr;
        }
        *buf = prof_buf.base;
        return 0;
}