/* eurephia-auth.c -- Main OpenVPN plug-in functions. * The API level between OpenVPN and eurephia-auth * * GPLv2 only - Copyright (C) 2008 - 2013 * David Sommerseth * * 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 * @date 2008-08-06 * * @brief Implements the API which the OpenVPN plug-in interface requires * */ #include #include #include #define ENABLE_SSL #include "openvpn-plugin.h" #define EUREPHIA_FWINTF #include #include #include #include #include #include #include #include /** * A structure which is prepared in openvpn_plugin_client_constructor_v1(). * This function is called by OpenVPN to initialise a per client memory buffer. * */ typedef struct _eurephiaClientCTX { certinfo *cert; } eurephiaClientCTX; #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, const 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; free_nullsafe(ctx, devtype); 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. This is used by OpenVPN 2.2. * * @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, NULL); if( context ) { // Make sure tunnel type is unknown context->tuntype = tuntype_UNKN; } return (openvpn_plugin_handle_t) context; } /** * Prepares a eurephiaCTX (context) for the openvpn process and tells openvpn which hooks eurephia * will make use of. This is used by OpenVPN 2.3 and newer, using the OpenVPN v3 plug-in API. * * @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 int openvpn_plugin_open_v3(const int apiversion, struct openvpn_plugin_args_open_in const *arguments, struct openvpn_plugin_args_open_return *ovpnret) { eurephiaCTX *context = NULL; #ifdef MEMWATCH mwStatistics(3); #warning MEMWATCH enabled #endif // Check that we are API compatible if( apiversion != OPENVPN_PLUGINv3_STRUCTVER ) { arguments->callbacks->plugin_log(PLOG_ERR, "eurephia", "OpenVPN and eurephia's plug-in API is " "not compatible."); return OPENVPN_PLUGIN_FUNC_ERROR; } #if OPENVPN_PLUGINv3_STRUCTVER > 1 // Check that OpenVPN uses OpenSSL if( arguments->ssl_api != SSLAPI_OPENSSL ) { arguments->callbacks->plugin_log(PLOG_ERR, "eurephia", "OpenVPN is not compiled against OpenSSL. " "eurephia requires OpenSSL."); return OPENVPN_PLUGIN_FUNC_ERROR; } #else #warning openvpn-plugins.h does not indicate SSL library. If OpenVPN is not compiled against OpenSSL, eurephia will fail badly. You have been warned. #endif // Define what will trigger eurephia ovpnret->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(arguments->argv, arguments->envp, arguments->callbacks); if( context ) { // Make sure tunnel type is unknown at this point. context->tuntype = tuntype_UNKN; } eurephia_log(context, LOG_INFO, 3, "Using OpenVPN plug-in v3 API"); ovpnret->handle = (void *) context; return OPENVPN_PLUGIN_FUNC_SUCCESS; } /** * On each hook defined in openvpn_plugin_open_v1(), this function will be called when * openvpn reaches that phase. This is used by OpenVPN 2.2. * * @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; certinfo *ci = NULL; 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 if( type != OPENVPN_PLUGIN_UP ) { // Exctract certificate information from either environment variables ci = parse_tlsid(GETENV_TLSID(ctx, envp, atoi_nullsafe(argv[1])), GETENV_TLSDIGEST(ctx, envp, atoi_nullsafe(argv[1]))); if( ci == NULL ) { eurephia_log(ctx, LOG_FATAL, 0, "Failed to extract certificate info"); return OPENVPN_PLUGIN_FUNC_ERROR; } } 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], ci); break; case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY: // Validate user name and password result = eurephia_userauth(ctx, envp, ci); break; case OPENVPN_PLUGIN_CLIENT_CONNECT: // Register login result = eurephia_connect(ctx, envp, ci); break; case OPENVPN_PLUGIN_CLIENT_DISCONNECT: // Register logout result = eurephia_disconnect(ctx, envp, ci); break; case OPENVPN_PLUGIN_LEARN_ADDRESS: // Log IP address, MAC address and update firewall result = eurephia_learn_address(ctx, argv[1], argv[2], envp, ci); break; default: // This should normally not be reached at all eurephia_log(ctx, LOG_FATAL, 0, "Unknown OPENVPN_PLUGIN type: %i", type); break; } free_certinfo(ci); return (result == 1 ? OPENVPN_PLUGIN_FUNC_SUCCESS : OPENVPN_PLUGIN_FUNC_ERROR); } /** * On each hook defined in openvpn_plugin_open_v3(), this function will be called when * openvpn reaches that phase. This is used by OpenVPN 2.3 or newer, using the * OpenVPN v3 plug-in API. * * @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_v3(const int apiver, struct openvpn_plugin_args_func_in const *args, struct openvpn_plugin_args_func_return *ovpn_return) { eurephiaCTX *ctx = (eurephiaCTX *) args->handle; eurephiaClientCTX *cctx = NULL; int result = 0; certinfo *ci = NULL; if( (ctx == NULL) || (ctx->dbc == NULL) || (ctx->dbc->dbhandle == NULL) ) { return OPENVPN_PLUGIN_FUNC_ERROR; } DEBUG(ctx, 10, "openvpn_plugin_func_v3(ctx, %s, ...)", plugin_type_name(args->type)); #ifdef ENABLE_DEBUG if( (ctx->log->loglevel >= 30) && (ctx->log->logfile != NULL) ) { dump_env(ctx->log->logfile, "ENV: ", args->envp); dump_env(ctx->log->logfile, "ARG: ", args->argv); } #endif if( args->type != OPENVPN_PLUGIN_UP ) { cctx = (eurephiaClientCTX *) args->per_client_context; } switch( args->type ) { case OPENVPN_PLUGIN_UP: // Figure out what kind of tunnel type we got. result = detect_tunnel_type(ctx, args->envp); 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 // Exctract certificate information from either environment variables // or the X.509 certificate if we have that available if( args->current_cert != NULL ) { ci = parse_x509_cert(args->current_cert); } if( ci == NULL ) { eurephia_log(ctx, LOG_FATAL, 0, "Failed to extract certificate info"); return OPENVPN_PLUGIN_FUNC_ERROR; } result = eurephia_tlsverify(ctx, args->envp, args->argv[1], ci); if( args->argv[1][0] == '0' ) { // It's a user certificate, // save a pointer to this certificate info cctx->cert = ci; } else { // CA/sub-CA certificates can be released free_certinfo(ci); } break; case OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY: // Validate user name and password result = eurephia_userauth(ctx, args->envp, cctx->cert); break; case OPENVPN_PLUGIN_CLIENT_CONNECT: // Register login result = eurephia_connect(ctx, args->envp, cctx->cert); break; case OPENVPN_PLUGIN_CLIENT_DISCONNECT: // Register logout result = eurephia_disconnect(ctx, args->envp, cctx->cert); break; case OPENVPN_PLUGIN_LEARN_ADDRESS: // Log IP address, MAC address and update firewall result = eurephia_learn_address(ctx, args->argv[1], args->argv[2], args->envp, cctx->cert); if( strncmp(args->argv[1], "delete", 6) == 0 ) { // if we receive a 'delete' command, // release the certificate information too free_certinfo(cctx->cert); cctx->cert = NULL; } break; default: // This should normally not be reached at all eurephia_log(ctx, LOG_FATAL, 0, "Unknown OPENVPN_PLUGIN type: %i", args->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); } /** * Called when openvpn is wants to allocate a memory buffer for a new client * * @param handle Contains a pointer to the eurephiaCTX * * @return Returns a pointer to the initialised client context */ OPENVPN_EXPORT void * openvpn_plugin_client_constructor_v1(openvpn_plugin_handle_t handle) { eurephiaCTX *ctx = (eurephiaCTX *) handle; struct eurephiaClientCTX *cctx = malloc_nullsafe(ctx, sizeof(eurephiaClientCTX)+2); return cctx; } /** * Called when a client disconnects and openvpn wants to destroy the client context * * @param handle Contains a pointer to the eurephiaCTX * @paran per_client_ctx Pointer to the client context to be released */ OPENVPN_EXPORT void openvpn_plugin_client_destructor_v1(openvpn_plugin_handle_t handle, void *per_client_ctx) { eurephiaClientCTX *cctx = (eurephiaClientCTX *) per_client_ctx; if( cctx->cert ) { free_certinfo(cctx->cert); } free(per_client_ctx); }