/* eurephia-auth.c  --  Main OpenVPN plug-in functions.
 *                      The API level between OpenVPN and eurephia-auth
 *
 *  GPLv2 only - Copyright (C) 2008 - 2019
 *               David Sommerseth <dazo@eurephia.org>
 *
 *  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; version 2
 *  of the License.
 *
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

/**
 * @file   eurephia-auth.c
 * @author David Sommerseth <dazo@users.sourceforge.net>
 * @date   2008-08-06
 *
 * @brief  Implements the API which the OpenVPN plug-in interface requires
 *
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "openvpn-plugin.h"
#define EUREPHIA_FWINTF
#include <eurephiafw_struct.h>
#include <eurephia_context.h>
#include <eurephiadb.h>
#include <eurephia.h>
#include <eurephia_nullsafe.h>
#include <eurephia_values.h>
#include <environment.h>

#ifdef ENABLE_DEBUG  /* To avoid compiler warnings when ENABLE_DEBUG is not defined */

/**
 * Simple "converter" from OPENVPN_PLUGIN_* type IDs to string
 *
 * @param type int value, corresponding to an OPENVPN_PLUGIN_* type
 *
 * @return String containing the string version of the type
 */
static const char *plugin_type_name(const int type)
{
  switch (type)
    {
    case OPENVPN_PLUGIN_UP:
      return "PLUGIN_UP";
    case OPENVPN_PLUGIN_DOWN:
      return "PLUGIN_DOWN";
    case OPENVPN_PLUGIN_ROUTE_UP:
      return "PLUGIN_ROUTE_UP";
    case OPENVPN_PLUGIN_IPCHANGE:
      return "PLUGIN_IPCHANGE";
    case OPENVPN_PLUGIN_TLS_VERIFY:
      return "PLUGIN_TLS_VERIFY";
    case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY:
      return "PLUGIN_AUTH_USER_PASS_VERIFY";
    case OPENVPN_PLUGIN_CLIENT_CONNECT:
      return "PLUGIN_CLIENT_CONNECT";
    case OPENVPN_PLUGIN_CLIENT_DISCONNECT:
      return "PLUGIN_CLIENT_DISCONNECT";
    case OPENVPN_PLUGIN_LEARN_ADDRESS:
      return "PLUGIN_LEARN_ADDRESS";
    default:
      return "(UNKNOWN PLUGIN CODE)";
    }
}

/**
 * Dumps the contents of the environmental table to the given FILE.  This function is only available 
 * if DEBUG is defined during compilation.  If SHOW_SECRETS is not defined, it will mask the contents
 * of the password field, if found.
 *
 * @param f       FILE * where the contents will be dumped
 * @param prefix  Adds a fixed prefix to each of the lines
 * @param envp    openvpn environmental table
 */
static void dump_env(FILE *f, const char *prefix, const char *envp[]) {
        int i;
        for (i = 0; envp[i]; i++) {
#ifdef SHOW_SECRETS
                fprintf(f, "%s%s\n", prefix, envp[i]);
#else
		fprintf(f, "%s%s\n", prefix ,
			(strncmp(envp[i], "password=", 9) == 0) ? "password=xxxxxxx" : envp[i]);
#endif // SHOW_SECRETS
        }
}
#endif // ENABLE_DEBUG


/**
 * Simple function for converting a device name/type string to
 * a OVPN_tunnelType constant
 */
static OVPN_tunnelType conv_str2tuntype(const char *str) {
        if( str ) {
                if( strncmp(str, "tun", 3) == 0 ) {
                        return tuntype_TUN;
                } else if( strncmp(str, "tap", 3) == 0 ) {
                        return tuntype_TAP;
                }
        }
        return tuntype_UNKN;
}


static inline int detect_tunnel_type(eurephiaCTX *ctx, char const *env[]) {
        char *devname = NULL;
        char *devtype = NULL;
        OVPN_tunnelType tuntype = tuntype_UNKN;
        int forced = 0;

        devname = GETENV_DEVNAME(ctx, env);

        // Check if eurephia is configured to override the device type
        devtype = eGet_value(ctx->dbc->config, "openvpn_devtype");
        if( devtype ) {
                tuntype = conv_str2tuntype(devtype);
                if( tuntype == tuntype_UNKN ) {
                        eurephia_log(ctx, LOG_ERR, 0,
                                     "Unkown openvpn_devtype configuration value: '%s'.  "
                                     "Will try to auto-detect the type for the %s device.",
                                     devtype, devname);
                } else {
                        ctx->tuntype = tuntype;
                        forced = 1;
                        goto success;
                }
        }

        // Check if we got the 'dev_type' environment variable
        devtype = GETENV_DEVTYPE(ctx, env);
        tuntype = conv_str2tuntype(devtype);
        if( tuntype != tuntype_UNKN ) {
                ctx->tuntype = tuntype;
                goto success;
        }
        free_nullsafe(ctx, devtype);

        // If no 'dev_type', try to guess the dev-type based on the dev name
        tuntype = conv_str2tuntype(devname);
        if( tuntype != tuntype_UNKN ) {
                ctx->tuntype = tuntype;
                goto success;
        }

        // If we're here, it's an unknown device type
        eurephia_log(ctx, LOG_FATAL, 0,
                     "Could not detect automatically which device type the %s device is.  "
                     "You need to force the tunnel device type setting the 'openvpn_devtype' "
                     "configuration value.",
                     devname);
        free_nullsafe(ctx, devname);
        return 0;

 success:
        eurephia_log(ctx, LOG_INFO, 1,
                     "OpenVPN device type is %s %s on the %s device.",
                     (forced ? "forced to" : "detected as"),
                     (tuntype == tuntype_TUN ? "TUN" : "TAP"), devname);
        free_nullsafe(ctx, devname);
        return 1;
}


/**
 * Prepares a eurephiaCTX (context) for the openvpn process and tells openvpn which hooks eurephia
 * will make use of.
 *
 * @param type_mask  int pointer, containing the hooks eurephia will make use of
 * @param argv       arguments from the openvpn --plugin configuration option.
 * @param envp       openvpn environmental table
 *
 * @return Returns a pointer to the eurephiaCTX.
 */
OPENVPN_EXPORT openvpn_plugin_handle_t openvpn_plugin_open_v1(unsigned int *type_mask,
                                                              const char *argv[], const char *envp[])
{
        eurephiaCTX *context = NULL;

#ifdef MEMWATCH
        mwStatistics(3);
#warning MEMWATCH enabled
#endif

        // Define what will trigger eurephia
        *type_mask = OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_UP)
                | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY)
                | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_TLS_VERIFY)
                | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_CONNECT)
                | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_CLIENT_DISCONNECT)
                | OPENVPN_PLUGIN_MASK(OPENVPN_PLUGIN_LEARN_ADDRESS);

        // Setup a eurephia context
        context = eurephiaInit(argv, envp);
        if( context ) {
                // Make sure tunnel type is unknown
                context->tuntype = tuntype_UNKN;
        }

        return (openvpn_plugin_handle_t) context;
}


/**
 * On each hook defined in openvpn_plugin_open_v1(), this function will be called when
 * openvpn reaches that phase.
 *
 * @param handle Contains a pointer to the eurephiaCTX
 * @param type   What kind of event is openvpn processing now
 * @param argv   openvpn arguments for the current event
 * @param envp   openvpn environmental table
 *
 * @return Returns OPENVPN_PLUGIN_FUNC_SUCCESS on success, otherwise OPENVPN_PLUGIN_FUNC_ERROR
 */
OPENVPN_EXPORT int openvpn_plugin_func_v1(openvpn_plugin_handle_t handle,
                                          const int type,
                                          const char *argv[], const char *envp[])
{
        eurephiaCTX *ctx = (eurephiaCTX *) handle;
        int result = 0;


        if( (ctx == NULL) || (ctx->dbc == NULL) || (ctx->dbc->dbhandle == NULL) ) {
                return OPENVPN_PLUGIN_FUNC_ERROR;
        }

        DEBUG(ctx, 10, "openvpn_plugin_func_v1(ctx, %s, ...)", plugin_type_name(type));

#ifdef ENABLE_DEBUG
	if( (ctx->log->loglevel >= 30) && (ctx->log->logfile != NULL) ) {
		dump_env(ctx->log->logfile, "ENV: ", envp);
		dump_env(ctx->log->logfile, "ARG: ", argv);
	}
#endif

        switch( type ) {
        case OPENVPN_PLUGIN_UP:
                result = detect_tunnel_type(ctx, envp);  // Figure out what kind of tunnel type we got.
                if( result == 0 ) {
                        // If we cannot identify the tunnel type, we will anyway reject
                        // OpenVPN from starting up.  In that case, shutdown eurephia properly
                        eurephiaShutdown(ctx);
                }
                break;

        case OPENVPN_PLUGIN_TLS_VERIFY:  // Validate certificates
                result = eurephia_tlsverify(ctx, envp, argv[1]);
                break;

        case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY: // Validate user name and password
                result = eurephia_userauth(ctx, envp);
                break;

        case OPENVPN_PLUGIN_CLIENT_CONNECT:  // Register login
                result = eurephia_connect(ctx, envp);
                break;

        case OPENVPN_PLUGIN_CLIENT_DISCONNECT: // Register logout
                result = eurephia_disconnect(ctx, envp);
                break;

        case OPENVPN_PLUGIN_LEARN_ADDRESS:  // Log IP address, MAC address and update firewall
                result = eurephia_learn_address(ctx, argv[1], argv[2], envp);
                break;

        default: // This should normally not be reached at all
                eurephia_log(ctx, LOG_FATAL, 0, "Unknown OPENVPN_PLUGIN type: %i", type);
                break;
        }
        return (result == 1 ? OPENVPN_PLUGIN_FUNC_SUCCESS : OPENVPN_PLUGIN_FUNC_ERROR);
}


/**
 * Called when openvpn is shutting down.  This makes sure that eurephia disconnects, 
 * unloads drivers and frees the memory it has been using.
 *
 * @param handle  Contains a pointer to the eurephiaCTX
 */
OPENVPN_EXPORT void openvpn_plugin_close_v1(openvpn_plugin_handle_t handle)
{
        eurephiaCTX *ctx = (eurephiaCTX *) handle;

        eurephiaShutdown(ctx);
}

