/*
  Copyright (c) 2008-2012 Red Hat, Inc. <http://www.redhat.com>
  This file is part of GlusterFS.

  This file is licensed to you under your choice of the GNU Lesser
  General Public License, version 3 or any later version (LGPLv3 or
  later), or the GNU General Public License, version 2 (GPLv2), in all
  cases as published by the Free Software Foundation.
*/

#include <fnmatch.h>
#include "authenticate.h"

/* Note on strict_auth
 * - Strict auth kicks in when authentication is using the username, password
 *   in the volfile to login
 * - If enabled, auth is rejected if the username and password is not matched
 *   or is not present
 * - When using SSL names, this is automatically strict, and allows only those
 *   names that are present in the allow list, IOW strict auth checking has no
 *   implication when using SSL names
 */

auth_result_t
gf_auth(dict_t *input_params, dict_t *config_params)
{
    auth_result_t result = AUTH_DONT_CARE;
    int ret = 0;
    data_t *allow_user = NULL;
    data_t *username_data = NULL;
    data_t *passwd_data = NULL;
    data_t *password_data = NULL;
    char *username = NULL;
    char *password = NULL;
    char *brick_name = NULL;
    char *searchstr = NULL;
    char *username_str = NULL;
    char *tmp = NULL;
    char *username_cpy = NULL;
    gf_boolean_t using_ssl = _gf_false;
    gf_boolean_t strict_auth = _gf_false;

    username_data = dict_get(input_params, "ssl-name");
    if (username_data) {
        gf_log("auth/login", GF_LOG_INFO, "connecting user name: %s",
               username_data->data);
        using_ssl = _gf_true;
    } else {
        ret = dict_get_str_boolean(config_params, "strict-auth-accept",
                                   _gf_false);
        if (ret == -1)
            strict_auth = _gf_false;
        else
            strict_auth = ret;

        username_data = dict_get(input_params, "username");
        if (!username_data) {
            if (strict_auth) {
                gf_log("auth/login", GF_LOG_DEBUG,
                       "username not found, strict auth"
                       " configured returning REJECT");
                result = AUTH_REJECT;
            } else {
                gf_log("auth/login", GF_LOG_DEBUG,
                       "username not found, returning"
                       " DONT-CARE");
            }
            goto out;
        }
        password_data = dict_get(input_params, "password");
        if (!password_data) {
            if (strict_auth) {
                gf_log("auth/login", GF_LOG_DEBUG,
                       "password not found, strict auth"
                       " configured returning REJECT");
                result = AUTH_REJECT;
            } else {
                gf_log("auth/login", GF_LOG_WARNING,
                       "password not found, returning"
                       " DONT-CARE");
            }
            goto out;
        }
        password = data_to_str(password_data);
    }
    username = data_to_str(username_data);

    brick_name = data_to_str(dict_get(input_params, "remote-subvolume"));
    if (!brick_name) {
        gf_log("auth/login", GF_LOG_ERROR, "remote-subvolume not specified");
        result = AUTH_REJECT;
        goto out;
    }

    ret = gf_asprintf(&searchstr, "auth.login.%s.%s", brick_name,
                      using_ssl ? "ssl-allow" : "allow");
    if (-1 == ret) {
        gf_log("auth/login", GF_LOG_ERROR,
               "asprintf failed while setting search string, "
               "returning REJECT");
        result = AUTH_REJECT;
        goto out;
    }

    allow_user = dict_get(config_params, searchstr);
    GF_FREE(searchstr);

    if (allow_user) {
        gf_log("auth/login", GF_LOG_INFO, "allowed user names: %s",
               allow_user->data);
        /*
         * There's a subtle difference between SSL and non-SSL behavior
         * if we can't match anything in the "while" loop below.
         * Intuitively, we should AUTH_REJECT if there's no match.
         * However, existing code depends on allowing untrusted users
         * to connect with *no credentials at all* by falling through
         * the loop.  They're still distinguished from trusted users
         * who do provide a valid username and password (in fact that's
         * pretty much the only thing we use non-SSL login auth for),
         * but they are allowed to connect.  It's wrong, but it's not
         * worth changing elsewhere.  Therefore, we do the sane thing
         * only for SSL here.
         *
         * For SSL, if there's a list *you must be on it*.  Note that
         * if there's no list we don't care.  In that case (and the
         * ssl-allow=* case as well) authorization is effectively
         * disabled, though authentication and encryption are still
         * active.
         *
         * Read NOTE on strict_auth above.
         */
        if (using_ssl || strict_auth) {
            result = AUTH_REJECT;
        }
        username_cpy = gf_strdup(allow_user->data);
        if (!username_cpy)
            goto out;

        username_str = strtok_r(username_cpy, " ,", &tmp);

        /*
         * We have to match a user's *authenticated* name to one in the
         * list.  If we're using SSL, they're already authenticated.
         * Otherwise, they need a matching password to complete the
         * process.
         */
        while (username_str) {
            if (!fnmatch(username_str, username, 0)) {
                if (using_ssl) {
                    result = AUTH_ACCEPT;
                    break;
                }
                ret = gf_asprintf(&searchstr, "auth.login.%s.password",
                                  username);
                if (-1 == ret) {
                    gf_log("auth/login", GF_LOG_WARNING,
                           "asprintf failed while setting search string");
                    goto out;
                }
                passwd_data = dict_get(config_params, searchstr);
                GF_FREE(searchstr);

                if (!passwd_data) {
                    gf_log("auth/login", GF_LOG_ERROR,
                           "wrong username/password combination");
                    result = AUTH_REJECT;
                    goto out;
                }

                result = !((strcmp(data_to_str(passwd_data), password))
                               ? AUTH_ACCEPT
                               : AUTH_REJECT);
                if (result == AUTH_REJECT)
                    gf_log("auth/login", GF_LOG_ERROR,
                           "wrong password for user %s", username);

                break;
            }
            username_str = strtok_r(NULL, " ,", &tmp);
        }
    }

out:
    GF_FREE(username_cpy);

    return result;
}

struct volume_options options[] = {
    {
        .key = {"auth.login.*.allow"},
        .type = GF_OPTION_TYPE_ANY,
        .default_value = "*",
        .description = "Username to be allowed access to the volume",
        .op_version = {1},
        .flags = OPT_FLAG_SETTABLE | OPT_FLAG_DOC,
        .tags = {},
        /* option_validation_fn validate_fn; */
    },
    {
        .key = {"auth.login.*.password"},
        .type = GF_OPTION_TYPE_ANY,
        .default_value = "*",
        .description = "Password for the allowed username",
        .op_version = {1},
        .flags = OPT_FLAG_SETTABLE | OPT_FLAG_DOC,
        .tags = {},
        /* option_validation_fn validate_fn; */
    },
    {.key = {NULL}}};
