/***************************************************************************
 *   Copyright (C) 2007 International Business Machines  Corp.             *
 *   All Rights Reserved.                                                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 *                                                                         *
 * Authors:                                                                *
 *   Klaus Heinrich Kiwi <klausk@br.ibm.com>                               *
 *   based on code by Steve Grubb <sgrubb@redhat.com>                      *
 ***************************************************************************/

#include "zos-remote-config.h"

#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include "zos-remote-log.h"

/* Local prototypes */
struct nv_pair
{
        const char *name;
        const char *value;
        const char *option;
};

struct kw_pair
{
        const char *name;
        int (*parser) (struct nv_pair *, int, plugin_conf_t *);
        int max_options;
};

struct nv_list
{
        const char *name;
        int option;
};

static char *get_line(FILE *, char *);
static int nv_split(char *, struct nv_pair *);
static const struct kw_pair *kw_lookup(const char *);
static int server_parser(struct nv_pair *, int, plugin_conf_t *);
static int port_parser(struct nv_pair *, int, plugin_conf_t *);
static int timeout_parser(struct nv_pair *, int, plugin_conf_t *);
static int user_parser(struct nv_pair *, int, plugin_conf_t *);
static int password_parser(struct nv_pair *, int, plugin_conf_t *);
static int q_depth_parser(struct nv_pair *, int, plugin_conf_t *);
static int sanity_check(plugin_conf_t *);

static const struct kw_pair keywords[] = {
        {"server", server_parser, 0},
        {"port", port_parser, 0},
        {"timeout", timeout_parser, 0},
        {"user", user_parser, 0},
        {"password", password_parser, 0},
        {"q_depth", q_depth_parser, 0},
        {NULL, NULL, 0}
};

#define UNUSED(x) (void)(x)

/*
 * Set everything to its default value
*/
void plugin_clear_config(plugin_conf_t * c)
{
        c->server = NULL;
        c->port = 0;
        c->user = NULL;
        c->password = NULL;
        c->timeout = 15;
        c->q_depth = 64;
        /* not re-setting counter */
}

int plugin_load_config(plugin_conf_t * c, const char *file)
{
        int fd, rc, mode, lineno = 1;
        struct stat st;
        FILE *f;
        char buf[128];

        plugin_clear_config(c);

        /* open the file */
        mode = O_RDONLY;
        rc = open(file, mode);
        if (rc < 0) {
                if (errno != ENOENT) {
                        log_err("Error opening %s (%s)", file,
                                strerror(errno));
                        return 1;
                }
                log_warn("Config file %s doesn't exist, skipping", file);
                return 1;
        }
        fd = rc;

        /* check the file's permissions: owned by root, not world anything,
         * not symlink.
         */
        if (fstat(fd, &st) < 0) {
                log_err("Error fstat'ing config file (%s)",
                        strerror(errno));
                close(fd);
                return 1;
        }
        if (st.st_uid != 0) {
                log_err("Error - %s isn't owned by root", file);
                close(fd);
                return 1;
        }
        if ((st.st_mode & (S_IRUSR | S_IWUSR | S_IRGRP)) !=
            (S_IRUSR | S_IWUSR | S_IRGRP)) {
                log_err("%s permissions should be 0640", file);
                close(fd);
                return 1;
        }
        if (!S_ISREG(st.st_mode)) {
                log_err("Error - %s is not a regular file", file);
                close(fd);
                return 1;
        }

        /* it's ok, read line by line */
        f = fdopen(fd, "r");
        if (f == NULL) {
                log_err("Error - fdopen failed (%s)", strerror(errno));
                close(fd);
                return 1;
        }

        while (get_line(f, buf)) {
                /* convert line into name-value pair */
                const struct kw_pair *kw;
                struct nv_pair nv;

                rc = nv_split(buf, &nv);
                switch (rc) {
                case 0:        /* fine */
                        break;
                case 1:        /* not the right number of tokens. */
                        log_err("Wrong number of arguments for line %d in %s", lineno, file);
                        break;
                case 2:        /* no '=' sign */
                        log_err("Missing equal sign for line %d in %s",
                                lineno, file);
                        break;
                default:       /* something else went wrong... */
                        log_err("Unknown error for line %d in %s",
                                lineno, file);
                        break;
                }
                if (nv.name == NULL) {
                        lineno++;
                        continue;
                }
                if (nv.value == NULL) {
                        fclose(f);
                        return 1;
                }

                /* identify keyword or error */
                kw = kw_lookup(nv.name);
                if (kw->name == NULL) {
                        log_err("Unknown keyword \"%s\" in line %d of %s",
                                nv.name, lineno, file);
                        fclose(f);
                        return 1;
                }

                /* Check number of options */
                if (kw->max_options == 0 && nv.option != NULL) {
                        log_err("Keyword \"%s\" has invalid option "
                                "\"%s\" in line %d of %s",
                                nv.name, nv.option, lineno, file);
                        fclose(f);
                        return 1;
                }

                /* dispatch to keyword's local parser */
                rc = kw->parser(&nv, lineno, c);
                if (rc != 0) {
                        fclose(f);
                        return 1;       /* local parser puts message out */
                }

                lineno++;
        }

        fclose(f);
        c->name = strdup(basename(file));
        if (lineno > 1)
                return sanity_check(c);
        return 0;
}

static char *get_line(FILE * f, char *buf)
{
        if (fgets_unlocked(buf, 128, f)) {
                /* remove newline */
                char *ptr = strchr(buf, 0x0a);

                if (ptr)
                        *ptr = 0;
                return buf;
        }
        return NULL;
}

static int nv_split(char *buf, struct nv_pair *nv)
{
        /* Get the name part */
        char *ptr, *saved;

        nv->name = NULL;
        nv->value = NULL;
        nv->option = NULL;
        ptr = strtok_r(buf, " ", &saved);
        if (ptr == NULL)
                return 0;       /* If there's nothing, go to next line */
        if (ptr[0] == '#')
                return 0;       /* If there's a comment, go to next line */
        nv->name = ptr;

        /* Check for a '=' */
        ptr = strtok_r(NULL, " ", &saved);
        if (ptr == NULL)
                return 1;
        if (strcmp(ptr, "=") != 0)
                return 2;

        /* get the value */
        ptr = strtok_r(NULL, " ", &saved);
        if (ptr == NULL)
                return 1;
        nv->value = ptr;

        /* See if there's an option */
        ptr = strtok_r(NULL, " ", &saved);
        if (ptr) {
                nv->option = ptr;

                /* Make sure there's nothing else */
                ptr = strtok_r(NULL, " ", &saved);
                if (ptr)
                        return 1;
        }

        /* Everything is OK */
        return 0;
}

static const struct kw_pair *kw_lookup(const char *val)
{
        int i = 0;

        while (keywords[i].name != NULL) {
                if (strcasecmp(keywords[i].name, val) == 0)
                        break;
                i++;
        }
        return &keywords[i];
}


static int server_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
{
	UNUSED(line);
        if (nv->value == NULL)
                c->server = NULL;
        else
                c->server = strdup(nv->value);

        return 0;
}

static int port_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
{
        const char *ptr = nv->value;
        unsigned long i;

        /* check that all chars are numbers */
        for (i = 0; ptr[i]; i++) {
                if (!isdigit((unsigned char)ptr[i])) {
                        log_err("Value %s should only be numbers - line %d", nv->value, line);
                        return 1;
                }
        }

        /* convert to unsigned long */
        errno = 0;
        i = strtoul(nv->value, NULL, 10);
        if (errno) {
                log_err("Error converting string to a number (%s) - line %d", strerror(errno), line);
                return 1;
        }

        c->port = i;
        return 0;

}

static int timeout_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
{
        const char *ptr = nv->value;
        unsigned long i;

        /* check that all chars are numbers */
        for (i = 0; ptr[i]; i++) {
                if (!isdigit((unsigned char)ptr[i])) {
                        log_err("Value %s should only be numbers - line %d", nv->value, line);
                        return 1;
                }
        }

        /* convert to unsigned long */
        errno = 0;
        i = strtoul(nv->value, NULL, 10);
        if (errno) {
                log_err("Error converting string to a number (%s) - line %d", strerror(errno), line);
                return 1;
        }

        c->timeout = i;
        return 0;

}


static int user_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
{
	UNUSED(line);
        if (nv->value == NULL)
                c->user = NULL;
        else
                c->user = strdup(nv->value);

        return 0;
}

static int password_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
{
	UNUSED(line);
        if (nv->value == NULL)
                c->password = NULL;
        else
                c->password = strdup(nv->value);

        return 0;
}

static int q_depth_parser(struct nv_pair *nv, int line, plugin_conf_t * c)
{
        const char *ptr = nv->value;
        unsigned long i;

        /* check that all chars are numbers */
        for (i = 0; ptr[i]; i++) {
                if (!isdigit((unsigned char)ptr[i])) {
                        log_err("Value %s should only be numbers - line %d", nv->value, line);
                        return 1;
                }
        }

        /* convert to unsigned long */
        errno = 0;
        i = strtoul(nv->value, NULL, 10);
        if (errno) {
                log_err("Error converting string to a number (%s) - line %d", strerror(errno), line);
                return 1;
        }
        
        if (i < 16 || i > 99999) {
                log_err("q_depth must be between 16 and 99999");
                return 1;
        }

        c->q_depth = i;
        return 0;

}


/*
 * Check configuration.At this point, all fields have been read. 
 * Returns 0 if no problems and 1 if problems detected.
 */
static int sanity_check(plugin_conf_t * c)
{
        /* Error checking */
        if (!c->server) {
                log_err("Error - no server hostname given");
                return 1;
        }

        if (!c->user) {
                log_err("Error - no bind user given");
                return 1;
        }

        if (!c->password) {
                log_err("Error - no password given");
                return 1;
        }
        
        if (!c->timeout) {
                log_err("Error - timeout can't be zero");
                return 1;
        }
        return 0;
}

void plugin_free_config(plugin_conf_t * c)
{

        if (c == NULL)
                return;

        free((void *) c->server);
        free((void *) c->user);
        free((void *) c->password);
        free((void *) c->name);
}
