view th_config.c @ 354:c01e42fc9adb

More work on SOCKS proxy support, should work now.
author Matti Hamalainen <ccr@tnsp.org>
date Thu, 23 Jun 2011 08:26:48 +0300
parents 0db02b8d2d11
children e694c02d6982
line wrap: on
line source

/*
 * Very simple configuration handling functions
 * Programmed and designed by Matti 'ccr' Hamalainen
 * (C) Copyright 2004-2010 Tecnic Software productions (TNSP)
 *
 * Please read file 'COPYING' for information on license and distribution.
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdio.h>
#include <stdarg.h>
#include "th_config.h"
#include "th_util.h"
#include "th_string.h"

#define SET_MAX_BUF     (8192)


/* Free a given configuration (the values are not free'd)
 */
void th_cfg_free(cfgitem_t *cfg)
{
    cfgitem_t *curr = cfg;

    while (curr != NULL) {
        cfgitem_t *next = curr->next;
        
        if (curr->type == ITEM_SECTION)
            th_cfg_free((cfgitem_t *) curr->v.data);
        
        th_free(curr->name);
        th_free(curr);
        curr = next;
    }
}


/* Allocate and add new item to configuration
 */
static cfgitem_t *th_cfg_add(cfgitem_t **cfg, const char *name, const int type, void *data)
{
    cfgitem_t *node;

    if (cfg == NULL)
        return NULL;

    /* Allocate new item */
    node = (cfgitem_t *) th_calloc(1, sizeof(cfgitem_t));
    if (node == NULL)
        return NULL;

    /* Set values */
    node->type = type;
    node->v.data = data;
    node->name = th_strdup(name);
    
    /* Insert into linked list */
    if (*cfg != NULL) {
        node->prev = (*cfg)->prev;
        (*cfg)->prev->next = node;
        (*cfg)->prev = node;
    } else {
        *cfg = node;
        node->prev = node;
    }
    node->next = NULL;

    return node;
}


/* Add integer type setting into give configuration
 */
int th_cfg_add_int(cfgitem_t ** cfg, char * name,
              int *itemData, int itemDef)
{
    cfgitem_t *node;

    node = th_cfg_add(cfg, name, ITEM_INT, (void *) itemData);
    if (node == NULL)
        return -1;

    *itemData = itemDef;

    return 0;
}


int th_cfg_add_hexvalue(cfgitem_t ** cfg, char * name,
              int *itemData, int itemDef)
{
    cfgitem_t *node;

    node = th_cfg_add(cfg, name, ITEM_HEX_TRIPLET, (void *) itemData);
    if (node == NULL)
        return -1;

    *itemData = itemDef;

    return 0;
}


/* Add unsigned integer type setting into give configuration
 */
int th_cfg_add_uint(cfgitem_t ** cfg, char * name,
               unsigned int * itemData, unsigned int itemDef)
{
    cfgitem_t *node;

    node = th_cfg_add(cfg, name, ITEM_UINT, (void *) itemData);
    if (node == NULL)
        return -1;

    *itemData = itemDef;

    return 0;
}


/* Add strint type setting into given configuration
 */
int th_cfg_add_string(cfgitem_t ** cfg, char * name,
              char ** itemData, char * itemDef)
{
    cfgitem_t *node;

    node = th_cfg_add(cfg, name, ITEM_STRING, (void *) itemData);
    if (node == NULL)
        return -1;

    *itemData = th_strdup(itemDef);

    return 0;
}


/* Add boolean type setting into given configuration
 */
int th_cfg_add_bool(cfgitem_t ** cfg, char * name,
               BOOL * itemData, BOOL itemDef)
{
    cfgitem_t *node;

    node = th_cfg_add(cfg, name, ITEM_BOOL, (void *) itemData);
    if (node == NULL)
        return -1;

    *itemData = itemDef;

    return 0;
}


/* Add implicit comment
 */
int th_cfg_add_comment(cfgitem_t ** cfg, char * comment)
{
    cfgitem_t *node;

    node = th_cfg_add(cfg, comment, ITEM_COMMENT, NULL);
    if (node == NULL)
        return -1;

    return 0;
}


/* Add new section
 */
int th_cfg_add_section(cfgitem_t ** cfg, char * name, cfgitem_t *data)
{
    cfgitem_t *node;
    
    node = th_cfg_add(cfg, name, ITEM_SECTION, (void *) data);
    if (node == NULL)
        return -1;

    return 0;
}


int th_cfg_add_string_list(cfgitem_t ** cfg, char * name, qlist_t **data)
{
    cfgitem_t *node;
    
    if (data == NULL)
        return -5;
    
    node = th_cfg_add(cfg, name, ITEM_STRING_LIST, (void *) data);
    if (node == NULL)
        return -1;

    return 0;
}


/* Read a given file into configuration structure and variables
 */
enum {
    PM_EOF,
    PM_ERROR,
    PM_NORMAL,
    PM_COMMENT,
    PM_NEXT,
    PM_KEYNAME,
    PM_KEYSET,
    PM_STRING,
    PM_INT,
    PM_BOOL,
    PM_SECTION,
    PM_ARRAY
};

#define VADDCH(ch) if (strPos < SET_MAX_BUF) { tmpStr[strPos++] = ch; }
#define VISEND(ch) (ch == '\r' || ch == '\n' || ch == ';' || th_isspace(c) || ch == '#')

typedef struct {
    FILE *file;
    char *filename;
    size_t line;
} conffile_t;


static void th_cfg_error(conffile_t *f, const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);
    fprintf(stderr, "%s: '%s', line #%d: ", th_prog_name, f->filename, (unsigned int) f->line);
    vfprintf(stderr, fmt, ap);
    va_end(ap);
}


static int th_cfg_read_sect(conffile_t *f, cfgitem_t * cfg, int nesting)
{
    cfgitem_t *item = NULL;
    char tmpStr[SET_MAX_BUF + 1];
    size_t strPos;
    int c, parseMode, prevMode, nextMode, tmpCh;
    BOOL isFound, isStart, isError, validError;

    /* Initialize values */
    tmpCh = 0;
    strPos = 0;
    c = -1;
    isFound = isStart = isError = validError = FALSE;
    nextMode = prevMode = parseMode = PM_NORMAL;

    /* Parse the configuration */
    while (parseMode != PM_EOF && parseMode != PM_ERROR) {
        if (c == -1) {
            /* Get next character */
            switch (c = fgetc(f->file)) {
            case EOF:
                if (parseMode != PM_NORMAL) {
                    th_cfg_error(f,
                    "Unexpected end of file.\n");
                    parseMode = PM_ERROR;
                } else
                    parseMode = PM_EOF;
                break;

            case '\n':
                f->line++;
            }
        }

        switch (parseMode) {
        case PM_COMMENT:
            /* Comment parsing mode */
            if (c == '\n') {
                /* End of line, end of comment */
                parseMode = prevMode;
                prevMode = PM_COMMENT;
            }
            c = -1;
            break;

        case PM_NORMAL:
            /* Normal parsing mode */
            if (c == '#') {
                prevMode = parseMode;
                parseMode = PM_COMMENT;
                c = -1;
            } else if (VISEND(c)) {
                c = -1;
            } else if (c == '}') {
                if (nesting > 0) {
                    /* Check for validation errors */
                    return (validError) ? 1 : 0;
                } else {
                    th_cfg_error(f, "Invalid nesting sequence encountered.\n");
                    parseMode = PM_ERROR;
                }
            } else if (th_isalpha(c)) {
                /* Start of key name found */
                prevMode = parseMode;
                parseMode = PM_KEYNAME;
                strPos = 0;
            } else {
                /* Error! Invalid character found */
                th_cfg_error(f,
                    "Unexpected character '%c'.\n", c);
                parseMode = PM_ERROR;
            }
            break;

        case PM_KEYNAME:
            /* Configuration KEY name parsing mode */
            if (c == '#') {
                /* Start of comment */
                prevMode = parseMode;
                parseMode = PM_COMMENT;
                c = -1;
            } else if (th_iscrlf(c) || th_isspace(c) || c == '=') {
                /* End of key name */
                prevMode = parseMode;
                parseMode = PM_NEXT;
                nextMode = PM_KEYSET;
            } else if (th_isalnum(c) || c == '_') {
                /* Add to key name string */
                VADDCH(c)
                else
                {
                    /* Error! Key name string too long! */
                    th_cfg_error(f,
                        "Config key name too long!");
                    parseMode = PM_ERROR;
                }
                c = -1;
            } else {
                /* Error! Invalid character found */
                tmpStr[strPos] = 0;
                th_cfg_error(f,
                    "Unexpected character '%c' in key name '%s'.\n", c, tmpStr);
                parseMode = PM_ERROR;
            }
            break;

        case PM_KEYSET:
            if (c == '=') {
                /* Find key from configuration */
                tmpStr[strPos] = 0;
                isFound = FALSE;
                item = cfg;
                while (item != NULL && !isFound) {
                    if (item->name != NULL && strcmp(item->name, tmpStr) == 0)
                        isFound = TRUE;
                    else
                        item = item->next;
                }

                /* Check if key was found */
                if (isFound) {
                    /* Okay, set next mode */
                    switch (item->type) {
                    case ITEM_HEX_TRIPLET:
                    case ITEM_STRING:
                        nextMode = PM_STRING;
                        break;

                    case ITEM_STRING_LIST:
                        nextMode = PM_ARRAY;
                        break;

                    case ITEM_INT:
                    case ITEM_UINT:
                        nextMode = PM_INT;
                        break;

                    case ITEM_BOOL:
                        nextMode = PM_BOOL;
                        break;
                    
                    case ITEM_SECTION:
                        nextMode = PM_SECTION;
                        break;
                    }

                    prevMode = parseMode;
                    parseMode = PM_NEXT;
                    isStart = TRUE;
                    strPos = 0;
                } else {
                    /* Error! No configuration key by this name found */
                    th_cfg_error(f,
                        "No such configuration setting ('%s')\n", tmpStr);
                    parseMode = PM_ERROR;
                }

                c = -1;
            } else {
                /* Error! '=' expected! */
                th_cfg_error(f,
                    "Unexpected character '%c', assignation '=' was expected.\n", c);
                parseMode = PM_ERROR;
            }
            break;

        case PM_NEXT:
            /* Search next item parsing mode */
            if (c == '#') {
                /* Start of comment */
                prevMode = parseMode;
                parseMode = PM_COMMENT;
            } else if (th_isspace(c) || th_iscrlf(c)) {
                /* Ignore whitespaces and linechanges */
                c = -1;
            } else {
                /* Next item found */
                prevMode = parseMode;
                parseMode = nextMode;
            }
            break;

        case PM_ARRAY:
            if (isStart) {
                switch (item->type) {
                    case ITEM_STRING_LIST:
                        prevMode = parseMode;
                        parseMode = PM_STRING;
                        break;
                }
            } else if (c == ',') {
                switch (item->type) {
                    case ITEM_STRING_LIST:
                        c = -1;
                        isStart = TRUE;
                        prevMode = parseMode;
                        parseMode = PM_NEXT;
                        nextMode = PM_STRING;
                        break;
                }
            } else {
                prevMode = parseMode;
                parseMode = PM_NORMAL;
            }
            break;

        case PM_SECTION:
            /* Section parsing mode */
            if (c != '{') {
                /* Error! Section start '{' expected! */
                th_cfg_error(f,
                    "Unexpected character '%c', section start '{' was expected.\n", c);
                parseMode = PM_ERROR;
            } else {
                int res = th_cfg_read_sect(f, item->v.section, nesting + 1);
                c = -1;
                if (res > 0)
                    validError = TRUE;
                else if (res < 0)
                    parseMode = PM_ERROR;
                else {
                    prevMode = parseMode;
                    parseMode = PM_NORMAL;
                }
            }
            break;
            
        case PM_STRING:
            /* String parsing mode */
            if (isStart) {
                /* Start of string, get delimiter */
                tmpCh = c;
                isStart = FALSE;
                strPos = 0;
            } else if (c == tmpCh) {
                /* End of string, set the value */
                tmpStr[strPos] = 0;
                
                switch (item->type) {
                    case ITEM_HEX_TRIPLET:
                        *(item->v.val_int) = th_get_hex_triplet(tmpStr);
                        prevMode = parseMode;
                        parseMode = PM_NORMAL;
                        break;
                    case ITEM_STRING:
                        th_pstrcpy(item->v.val_str, tmpStr);
                        prevMode = parseMode;
                        parseMode = PM_NORMAL;
                        break;
                    case ITEM_STRING_LIST:
                        th_llist_append(item->v.list, th_strdup(tmpStr));
                        prevMode = parseMode;
                        parseMode = PM_NEXT;
                        nextMode = PM_ARRAY;
                        break;
                }
                
            } else {
                /* Add character to string */
                VADDCH(c)
                else
                {
                    /* Error! String too long! */
                    th_cfg_error(f,
                        "String too long! Maximum is %d characters.",
                        SET_MAX_BUF);
                    parseMode = PM_ERROR;
                }
            }

            c = -1;
            break;

        case PM_INT:
            /* Integer parsing mode */
            if (isStart && item->type == ITEM_UINT && c == '-') {
                /* Error! Negative values not allowed for unsigned ints */
                th_cfg_error(f,
                        "Negative value specified for %s, unsigned value expected.",
                        item->name);
                parseMode = PM_ERROR;
            } else if (isStart && (c == '-' || c == '+')) {
                VADDCH(c)
                else
                isError = TRUE;
            } else if (th_isdigit(c)) {
                VADDCH(c)
                else
                isError = TRUE;
            } else if (VISEND(c)) {
                /* End of integer parsing mode */
                tmpStr[strPos] = 0;
                switch (item->type) {
                case ITEM_INT:
                    *(item->v.val_int) = atoi(tmpStr);
                    break;

                case ITEM_UINT:
                    *(item->v.val_uint) = atol(tmpStr);
                    break;
                }

                prevMode = parseMode;
                parseMode = PM_NORMAL;
            } else {
                /* Error! Unexpected character. */
                th_cfg_error(f,
                    "Unexpected character '%c' for integer setting '%s'.",
                    c, item->name);
                parseMode = PM_ERROR;
            }

            if (isError) {
                /* Error! String too long! */
                th_cfg_error(f, "String too long! Maximum is %d characters.",
                        SET_MAX_BUF);
                parseMode = PM_ERROR;
            }

            isStart = FALSE;
            c = -1;
            break;

        case PM_BOOL:
            /* Boolean parsing mode */
            if (isStart) {
                tmpCh = c;
                isStart = FALSE;
            } else if (VISEND(c)) {
                BOOL tmpBool = FALSE;
                
                /* End of boolean parsing */
                switch (tmpCh) {
                case 'Y': case 'y':
                case 'T': case 't':
                case '1':
                    tmpBool = TRUE;
                    break;
                
                case 'N': case 'n':
                case 'F': case 'f':
                case '0':
                    tmpBool = FALSE;
                    break;
                
                default:
                    isError = TRUE;
                }
                
                if (isError) {
                    th_cfg_error(f, "Invalid boolean value for '%s'.\n", item->name);
                    parseMode = PM_ERROR;
                } else {
                    *(item->v.val_bool) = tmpBool;

                    prevMode = parseMode;
                    parseMode = PM_NORMAL;
                }
            }
            c = -1;
            break;
        }
    }

    /* Check for validation errors */
    if (validError)
        return 1;

    /* Return result */
    if (parseMode == PM_ERROR)
        return -2;
    else
        return 0;
}


int th_cfg_read(FILE *inFile, char *filename, cfgitem_t * cfg)
{
    conffile_t f;
    
    f.file = inFile;
    f.filename = filename;
    f.line = 1;
    
    return th_cfg_read_sect(&f, cfg, 0);
}


/* Write a configuration into file
 */
static void th_print_indent(conffile_t *f, int nesting)
{
    int i;
    for (i = 0; i < nesting * 2; i++)
        fputc(' ', f->file);
}


static int th_cfg_write_sect(conffile_t *f, cfgitem_t *item, int nesting)
{
    while (item != NULL) {
        if (item->type == ITEM_COMMENT) {
            th_print_indent(f, nesting);
            if (fprintf(f->file, "# %s\n", (item->name != NULL) ? item->name : "" ) < 0)
                return -1;
        } else
        if (item->name != NULL) {
            th_print_indent(f, nesting);
            
            switch (item->type) {
            case ITEM_STRING:
                if (*(item->v.val_str) == NULL) {
                    if (fprintf(f->file, "#%s = \"\"\n", item->name) < 0)
                        return -3;
                } else {
                    if (fprintf(f->file, "%s = \"%s\"\n",
                        item->name, *(item->v.val_str)) < 0)
                        return -3;
                }
                break;

            case ITEM_STRING_LIST:
                if (*(item->v.list) == NULL) {
                    if (fprintf(f->file, "#%s = \"\", \"\"\n", item->name) < 0)
                        return -3;
                } else {
                    qlist_t *node = *(item->v.list);
                    size_t n = th_llist_length(node);
                    if (fprintf(f->file, "%s = ", item->name) < 0)
                        return -3;
                    
                    while (node != NULL) {
                        if (node->data != NULL)
                            fprintf(f->file, "\"%s\"", (char *) node->data);

                        if (--n > 0)
                            fprintf(f->file, ", ");
                        
                        node = node->next;
                    }

                    if (fprintf(f->file, "\n") < 0)
                        return -3;
                }
                break;

            case ITEM_INT:
                if (fprintf(f->file, "%s = %i\n",
                    item->name, *(item->v.val_int)) < 0)
                    return -4;
                break;

            case ITEM_UINT:
                if (fprintf(f->file, "%s = %d\n",
                    item->name, *(item->v.val_uint)) < 0)
                    return -5;
                break;

            case ITEM_BOOL:
                if (fprintf(f->file, "%s = %s\n",
                    item->name, *(item->v.val_bool) ? "yes" : "no") < 0)
                    return -6;
                break;
            
            case ITEM_SECTION:
                {
                int res;
                if (fprintf(f->file, "%s = {\n", item->name) < 0)
                    return -7;
                res = th_cfg_write_sect(f, item->v.section, nesting + 1);
                if (res != 0) return res;
                if (fprintf(f->file, "}\n\n") < 0)
                    return -8;
                }
                break;
            
            case ITEM_HEX_TRIPLET:
                if (fprintf(f->file, "%s = \"%06x\"\n",
                    item->name, *(item->v.val_int)) < 0)
                    return -6;
                break;
            }
        }
        item = item->next;
    }
    
    return 0;
}


int th_cfg_write(FILE *outFile, char *filename, cfgitem_t *cfg)
{
    conffile_t f;
    
    if (cfg == NULL)
        return -1;

    f.file = outFile;
    f.filename = filename;
    f.line = 1;
    
    fprintf(outFile, "# Configuration written by %s %s\n\n", 
        th_prog_fullname, th_prog_version);
    
    return th_cfg_write_sect(&f, cfg, 0);
}