/* eurephia.c -- Main functions for the eurephia authentication module * * GPLv2 only - Copyright (C) 2008 - 2015 * 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.c * @author David Sommerseth * @date 2008-08-06 * * @brief The core eurephia functions which is called from OpenVPN. * */ #include #include #include #include #include #include #include #include #include #include #define EUREPHIA_FWINTF /**< Include the proper eurephiaFWINTF declaration in eurephiaCTX */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_ARGUMENTS 64 /**< Maximum arguments we will parse from the openvpn plug-in configuration */ /** * Initialises the eurephia OpenVPN plug-in and prepares a eurephiaCTX (context) * * @param argv Arguments from the openvpn configuration file. * * @return returns a pointer to a eurephiaCTX context. On failure NULL is returned. */ eurephiaCTX *eurephiaInit(const char const **argv, const char const **envp, struct openvpn_plugin_callbacks const *ovpn_callbacks) { static struct option eurephia_opts[] = { {"log-destination", required_argument, 0, 'l'}, {"log-level", required_argument, 0, 'L'}, {"database-interface", required_argument, 0, 'i'}, {0, 0, 0 ,0} }; int argc = 0, error = 0, loglvl = 0, dbargc = 0; const char *dbargv[MAX_ARGUMENTS]; const char *fwintf = NULL, *logfile = NULL, *dbi = NULL; eurephiaCTX *ctx = NULL; // // Parse input arguments // // Count arguments for( argc = 0; argv[argc] != NULL; argc++ ) {} while(1) { int opt_idx = 0; int c = 0; c = getopt_long(argc, (char **)argv, "l:L:i:", eurephia_opts, &opt_idx); if( c == -1 ) { break; } switch( c ) { case 'l': logfile = optarg; break; case 'L': loglvl = atoi_nullsafe(optarg); break; case 'i': dbi = optarg; break; default: fprintf(stderr, "Error parsing eurephia-auth arguments.\n"); return NULL; break; } } // Put the rest of the arguments into an own array which will be the db module arguments if( optind < argc ) { // copy arguments, but make sure we do not exceed our limit while( (dbargc < MAX_ARGUMENTS) && (optind < argc) ) { dbargv[dbargc] = argv[optind++]; dbargc++; dbargv[dbargc] = NULL; } } // End of argument parsing // Prepare a context area for eurephia-auth ctx = (eurephiaCTX *) malloc_nullsafe(NULL, sizeof(eurephiaCTX)+2); assert( ctx != NULL ); if( mlock(ctx, sizeof(eurephiaCTX)+2) < 0 ) { eurephia_log(ctx, LOG_CRITICAL, 0, "Could not mlock() eurephia context: %s", strerror(errno)); }; ctx->context_type = ECTX_PLUGIN_AUTH; // Open a log file if( (logfile == NULL) || (strcmp(logfile, "openvpn:") == 0) ) { // If no logfile is given, or openvpn: is given, // log to the OpenVPN plug-in v3 log callback if available, // otherwise stderr which OpenVPN will take care of in a different way if( (ovpn_callbacks != NULL) && (ovpn_callbacks->plugin_vlog != NULL) ) { eurephia_log_init(ctx, "eurephia-auth", "openvpn:", loglvl, ovpn_callbacks->plugin_vlog); } else { eurephia_log_init(ctx, "eurephia-auth", "stderr:", loglvl, NULL); } } else { // If another log file is given, process that eurephia_log_init(ctx, "eurephia-auth", logfile, loglvl, NULL); } eurephia_log(ctx, LOG_INFO, 0, "Initialising eurephia v" EUREPHIAVERSION " (David Sommerseth (C) 2008-2015 GPLv2)"); // Load the database driver if( (error == 0) && eDBlink_init(ctx, dbi, 4) ) { // Connect to the database if( !eDBconnect(ctx, dbargc, dbargv) ) { eurephia_log(ctx, LOG_PANIC, 0, "Could not connect to the database"); error = 1; eDBlink_close(ctx); } } else { eurephia_log(ctx, LOG_PANIC, 0, "Could not load the database driver"); error = 1; } if( error > 0 ) { eurephia_log(ctx, LOG_PANIC, 0, "eurephia-auth is not available"); eurephia_log_close(ctx); free_nullsafe(ctx, ctx); return NULL; } // Get data for server_salt - which will be used for the password cache ctx->server_salt = (char *) malloc_nullsafe(ctx, SIZE_PWDCACHE_SALT+2); assert( ctx->server_salt != NULL ); if( mlock(ctx->server_salt, SIZE_PWDCACHE_SALT+2) < 0 ) { eurephia_log(ctx, LOG_CRITICAL, 0, "Could not mlock() eurephia server salt: %s", strerror(errno)); } if( !eurephia_randstring(ctx, ctx->server_salt, SIZE_PWDCACHE_SALT) ) { eurephia_log(ctx, LOG_PANIC, 0 , "Could not get enough random data for password cache."); free_nullsafe(ctx, ctx->server_salt); eDBdisconnect(ctx); eurephia_log_close(ctx); free_nullsafe(ctx, ctx); return NULL; } // Check if firewall functionality is enabled, load module if given fwintf = eGet_value(ctx->dbc->config, "firewall_interface"); if( fwintf != NULL ) { if( eFW_load(ctx, fwintf) ) { char *daemon_s = GETENV_DAEMON(envp); char *logredir_s = GETENV_DAEMONLOGREDIR(envp); eurephia_log(ctx, LOG_INFO, 0, "Loaded firewall interface: %s", fwintf); eFW_StartFirewall(ctx, (daemon_s && (daemon_s[0] == '1')), (logredir_s && logredir_s[0] == '1')); free_nullsafe(ctx, daemon_s); free_nullsafe(ctx, logredir_s); } else { eurephia_log(ctx, LOG_FATAL, 0, "Loading of firewall interface failed (%s)", fwintf); ctx->eurephia_fw_intf = NULL; } } else { ctx->eurephia_fw_intf = NULL; } // Initialise authentication plug-ins. If no authentication plug-ins have been enabled, // the authplugs context will be NULL. ctx->authplugs = eAuthPlugin_Init(ctx); ctx->nointernalauth = atoi_nullsafe(eGet_value(ctx->dbc->config, "auth_disable_internal")) > 0; // Prepare an empty disconnected list. // This one is used to track all clients IP addresses and their corresponding eurephia session ID // when they disconnect. This is especially needed in TUN mode, the eurephia_learn_address() // delete call with otherwise have too little information to identify the session. ctx->disconnected = eCreate_value_space(ctx, 12); eurephia_log(ctx, LOG_INFO, 1, "eurephia-auth is initialised"); return ctx; } /** * Shutdowns the eurephia plug-in properly, disconnecting from database, unloading drivers, * closing log files and cleaning up the memory used. * * @param ctx The eurephiaCTX used by openvpn. * * @return Returns 1 on success, otherwise 0. */ int eurephiaShutdown(eurephiaCTX *ctx) { if( ctx == NULL ) { return 0; } if( ctx->disconnected != NULL ) { eFree_values(ctx, ctx->disconnected); ctx->disconnected = NULL; } if( ctx->eurephia_fw_intf != NULL ) { if( ctx->fwcfg != NULL ) { eFW_StopFirewall(ctx); } eFW_unload(ctx); } if( ctx->authplugs != NULL ) { eAuthPlugin_Close(ctx, ctx->authplugs); } if( (ctx->dbc != NULL) && (ctx->dbc->dbhandle != NULL) ) { eDBdisconnect(ctx); } if( ctx->eurephia_driver != NULL ) { eDBlink_close(ctx); } eurephia_log_close(ctx); memset(ctx->server_salt, 0xff, SIZE_PWDCACHE_SALT+2); munlock(ctx->server_salt, SIZE_PWDCACHE_SALT+2); free_nullsafe(ctx, ctx->server_salt); munlock(ctx, sizeof(eurephiaCTX)+2); free_nullsafe(ctx, ctx); return 1; } /** * Verifies the certificate digest (SHA1 fingerprint) against the database. * * @param ctx eurephiaCTX * @param env openvpn environment table containing all the information we need * @param depth_str If depth is 0, it's a client certificate, or else it's a CA certificate. * The input is a string containing the depth value. * * @return Returns 0 on failure and 1 on success. */ int eurephia_tlsverify(eurephiaCTX *ctx, const char **env, const char *depth_str, certinfo *ci) { int result = 0; char *ipaddr; unsigned int depth = atoi_nullsafe(depth_str); DEBUG(ctx, 10, "** Function call: eurephia_tlsverify(...)"); // Check if IP address is blacklisted ipaddr = GETENV_UNTRUSTEDIP(ctx, env); if( eDBblacklist_check(ctx, attempt_IPADDR, ipaddr) == 1 ) { eDBregister_attempt(ctx, attempt_IPADDR, ATTEMPT_REGISTER, ipaddr); // If fw blacklisting is configured, also blacklist there too if( (ctx->fwcfg != NULL) && (ctx->fwcfg->fwblacklist != NULL ) ) { eFWupdateRequest req = { .mode = fwBLACKLIST, }; strncpy(req.ipaddress, ipaddr, sizeof(req.ipaddress)); eFW_UpdateFirewall(ctx, &req); } free_nullsafe(ctx, ipaddr); return 0; } // Check if certificate digest is blacklisted if( eDBblacklist_check(ctx, attempt_CERTIFICATE, ci->digest) == 1 ) { eDBregister_attempt(ctx, attempt_IPADDR, ATTEMPT_REGISTER, ipaddr); eDBregister_attempt(ctx, attempt_CERTIFICATE, ATTEMPT_REGISTER, ci->digest); free_nullsafe(ctx, ipaddr); return 0; } // Check if certificate is registered and allowed result = eDBauth_TLS(ctx, ci->org, ci->common_name, ci->email, ci->digest, depth); if( result < 1 ) { eDBregister_attempt(ctx, attempt_IPADDR, ATTEMPT_REGISTER, ipaddr); eDBregister_attempt(ctx, attempt_CERTIFICATE, ATTEMPT_REGISTER, ci->digest); } if( result > 0 ) { // Certificate is okay, result contains the certificate ID eurephia_log(ctx, LOG_INFO, (depth == 0 ? 0 : 1), "Found certid %i for user: %s/%s/%s", result, ci->org, ci->common_name, ci->email); } else { eurephia_log(ctx, LOG_WARNING, 0, "Unknown certificate for: %s/%s/%s (depth %i, digest: %s)", ci->org, ci->common_name, ci->email, depth, ci->digest); } free_nullsafe(ctx, ipaddr); DEBUG(ctx, 10, "** Function result: eurephia_tlsverify(...) == %i", result > 0); return (result > 0); } /** * Authenticates the given user name, password and client certificate against the database. * * @param ctx eurephiaCTX * @param env openvpn environment table * * @return Returns 0 on failure and 1 on success. */ int eurephia_userauth(eurephiaCTX *ctx, const char **env, certinfo *ci) { eurephiaSESSION *authsess = NULL; eDBauthMethodResult *authmeth = NULL; eAuthPlugin *authplug = NULL; eAuthResult *authres = NULL; int result = 0, certid = 0; char *remport, *ipaddr = NULL; char *username = NULL; char *passwd = NULL, *pwdcache = NULL, *chkpwd = NULL; DEBUG(ctx, 10, "** Function call: eurephia_userauth(...)"); // Check if IP address is blacklisted ipaddr = GETENV_UNTRUSTEDIP(ctx, env); if( eDBblacklist_check(ctx, attempt_IPADDR, ipaddr) == 1 ) { eDBregister_attempt(ctx, attempt_IPADDR, ATTEMPT_REGISTER, ipaddr); // If fw blacklisting is configured, also blacklist there too if( (ctx->fwcfg != NULL) && (ctx->fwcfg->fwblacklist != NULL ) ) { eFWupdateRequest req = { .mode = fwBLACKLIST, }; strncpy(req.ipaddress, ipaddr, sizeof(req.ipaddress)); eFW_UpdateFirewall(ctx, &req); } free_nullsafe(ctx, ipaddr); return 0; } // Check if certificate digest is blacklisted if( eDBblacklist_check(ctx, attempt_CERTIFICATE, ci->digest) == 1 ) { eDBregister_attempt(ctx, attempt_IPADDR, ATTEMPT_REGISTER, ipaddr); eDBregister_attempt(ctx, attempt_CERTIFICATE, ATTEMPT_REGISTER, ci->digest); free_nullsafe(ctx, ipaddr); return 0; } // Check if username is blacklisted username = GETENV_USERNAME(ctx, env); if( eDBblacklist_check(ctx, attempt_USERNAME, username) == 1 ) { eDBregister_attempt(ctx, attempt_IPADDR, ATTEMPT_REGISTER, ipaddr); eDBregister_attempt(ctx, attempt_CERTIFICATE, ATTEMPT_REGISTER, ci->digest); eDBregister_attempt(ctx, attempt_USERNAME, ATTEMPT_REGISTER, username); free_nullsafe(ctx, username); free_nullsafe(ctx, ipaddr); return 0; } // Get certificate ID certid = eDBauth_TLS(ctx, ci->org, ci->common_name, ci->email, ci->digest, 0); if( certid < 1 ) { eDBregister_attempt(ctx, attempt_IPADDR, ATTEMPT_REGISTER, ipaddr); eDBregister_attempt(ctx, attempt_CERTIFICATE, ATTEMPT_REGISTER, ci->digest); eDBregister_attempt(ctx, attempt_USERNAME, ATTEMPT_REGISTER, username); free_nullsafe(ctx, username); free_nullsafe(ctx, ipaddr); return 0; } // Do username/password/certificate authentication passwd = GETENV_PASSWORD(ctx, env); mlock(passwd, strlen_nullsafe(passwd)); if( (passwd == NULL) || (strlen_nullsafe(passwd) == 0) ) { eurephia_log(ctx, LOG_WARNING, 0, "eurephia-auth: No password received. Action aborted"); memset(passwd, 0, strlen_nullsafe(passwd)); munlock(passwd, strlen_nullsafe(passwd)); free_nullsafe(ctx, passwd); free_nullsafe(ctx, username); free_nullsafe(ctx, ipaddr); return 0; } // Check if we have a valid password cached chkpwd = eurephia_quick_hash(ctx->server_salt, passwd); // Get an authentication session, which is not a real session ticket // but it uses almost the same system remport = GETENV_UNTRUSTEDPORT(ctx, env); // an authentication session do not use assigned VPN address authsess = eDBopen_session_seed(ctx, ci->digest, ci->common_name, username, NULL, ipaddr, remport); if( authsess == NULL ) { // No session found goto chk_pwd; } // Get cached password from password cache pwdcache = eGet_value(authsess->sessvals, "pwdcache"); if( (pwdcache != NULL) && (chkpwd != NULL) && (strcmp(pwdcache, chkpwd) == 0) ) { // if cached password matches users password, we're done eurephia_log(ctx, LOG_INFO, 3, "Authenticated user '%s' against password cache", username); eDBfree_session(ctx, authsess); result = 1; goto exit; } // If we do not have a valid password cached, check against the user database chk_pwd: authmeth = eDBauth_GetAuthMethod(ctx, certid, username); if( authmeth == NULL ) { eurephia_log(ctx, LOG_ERROR, 0, "Failed to identify authentication method for user '%s' with" "certid %i", username, certid); result = 0; goto exit; } switch (authmeth->method) { case eAM_INTERNDB: DEBUG(ctx, 12, "Using internal authentication for user '%s'/certid %i", username, certid); if( ctx->nointernalauth == 0 ) { /* Authenticate against the internal eurephia database */ result = eDBauth_user(ctx, certid, username, passwd); } else { eurephia_log(ctx, LOG_WARNING, 0, "Internal authentication has been disabled. Enable " "authentication plug-in for user '%s' with certid %i", username, certid); result = 0; } break; case eAM_PLUGIN: authplug = eAuthPlugin_Get(ctx->authplugs, authmeth->authplugid); if( authplug == NULL ) { eurephia_log(ctx, LOG_ERROR, 0, "Failed to find authentication plug-in %i to authenticate" "user '%s' with certid %i", authmeth->authplugid, username, certid); result = 0; goto exit; } DEBUG(ctx, 12, "Using authentication plugin %i for user '%s'/certid %i", authmeth->authplugid, username, certid); /* Authenticate the user via the auth plug-in */ authres = authplug->AuthenticateUser(ctx, authmeth->username, passwd); if( authres == NULL ) { eurephia_log(ctx, LOG_FATAL, 0, "Invalid response from authentication plug-in %i", authmeth->authplugid); result = 0; goto exit; } /* Parse the authentication result */ switch( authres->status ) { case eAUTH_FAILED: eurephia_log(ctx, LOG_WARNING, 0,"Authentication failed for user '%s': %s", username, authres->msg); sleep(2); result = -1; break; case eAUTH_PLGERROR: eurephia_log(ctx, LOG_ERROR, 0, "Authentication plug-in %i returned with an internal error " "while authenticating user '%s' (uicid: %i): %s", authmeth->authplugid, authmeth->username, authmeth->uicid, authres->msg); result = 0; goto exit; case eAUTH_SUCCESS: result = authmeth->uicid; if( authres->msg != NULL ) { eurephia_log(ctx, LOG_INFO, 1, "Authentication plug-in (%i) success response " "for user '%s' (uicid: %i): %s", authmeth->authplugid, authmeth->username, result, authres->msg); } break; default: eurephia_log(ctx, LOG_FATAL, 0, "Invalid response from authentication plug-in %i when " "authenticating user '%s': %i", authmeth->authplugid, authmeth->username, authres->status); result = 0; break; } break; case eAM_BLACKLISTED: DEBUG(ctx, 12, "User '%s'/certid %i is blacklisted", username, certid); result = -1; break; case eAM_INACTIVE: DEBUG(ctx, 12, "User '%s' is not activated", username); result = -1; break; default: eurephia_log(ctx, LOG_FATAL, 0, "Invalid authentication method attempted (%i) for " "user '%s' with certid %i", authmeth->method, username, certid); break; } eDBauth_FreeAuthMethodResult(ctx, authmeth); /* If the authentication failed, register the failed attempt */ if( result < 0 ) { eDBregister_attempt(ctx, attempt_IPADDR, ATTEMPT_REGISTER, ipaddr); eDBregister_attempt(ctx, attempt_CERTIFICATE, ATTEMPT_REGISTER, ci->digest); eDBregister_attempt(ctx, attempt_USERNAME, ATTEMPT_REGISTER, username); } if( result > 0 ) { // If we have a valid result, reset all attempt counters. eDBregister_attempt(ctx, attempt_IPADDR, ATTEMPT_RESET, ipaddr); eDBregister_attempt(ctx, attempt_CERTIFICATE, ATTEMPT_RESET, ci->digest); eDBregister_attempt(ctx, attempt_USERNAME, ATTEMPT_RESET, username); if( !eDBset_session_value(ctx, authsess, "pwdcache", chkpwd) ) { eurephia_log(ctx, LOG_WARNING, 0, "Failed to cache password for user '%s'", username); } eurephia_log(ctx, LOG_INFO, 0, "User '%s' authenticated", username); } exit: memset(passwd, 0, strlen_nullsafe(passwd)); munlock(passwd, strlen_nullsafe(passwd)); eDBfree_session(ctx, authsess); free_nullsafe(ctx, remport); free_nullsafe(ctx, passwd); free_nullsafe(ctx, username); free_nullsafe(ctx, ipaddr); free_nullsafe(ctx, chkpwd); DEBUG(ctx, 10, "** Function result: eurephia_userauth(...) = %i", (result>0)); return (result > 0); } /** * Called when openvpn has authenticated the user and granted it access. This function * will log information about the client * * @param ctx eurephiaCTX * @param env openvpn environment table * * @return Returns 0 on failure and 1 on success. */ int eurephia_connect(eurephiaCTX *ctx, const char **env, certinfo *ci) { eurephiaSESSION *session = NULL; char *uname, *vpnipaddr, *remipaddr, *remport, *proto; int certid = 0, uid = 0, accprofile = -1, ret = 0; DEBUG(ctx, 10, "** Function call: eurephia_connect(...)"); // Fetch needed info uname = GETENV_USERNAME(ctx, env); vpnipaddr = GETENV_POOLIPADDR(ctx, env); remipaddr = GETENV_TRUSTEDIP(ctx, env); remport = GETENV_TRUSTEDPORT(ctx, env); proto = GETENV_PROTO1(ctx, env); // Get a session ticket session = eDBopen_session_seed(ctx, ci->digest, ci->common_name, uname, vpnipaddr, remipaddr, remport); if( session == NULL ) { free_nullsafe(ctx, proto); free_nullsafe(ctx, remport); free_nullsafe(ctx, remipaddr); free_nullsafe(ctx, vpnipaddr); free_nullsafe(ctx, uname); return 0; } certid = eDBauth_TLS(ctx, ci->org, ci->common_name, ci->email, ci->digest, 0); if( certid < 1 ) { eurephia_log(ctx, LOG_WARNING, 0, "Failed to lookup certificate: %s/%s/%s [%s]", ci->org, ci->common_name, ci->email, ci->digest); return 0; } uid = eDBget_uid(ctx, certid, uname); // Register the session login accprofile = eDBget_accessprofile(ctx, uid, certid); ret = eDBregister_login2(ctx, session, certid, uid, accprofile, proto, remipaddr, remport); eDBfree_session(ctx, session); eurephia_log(ctx, LOG_INFO, 1, "User '%s' connected", uname); free_nullsafe(ctx, proto); free_nullsafe(ctx, remport); free_nullsafe(ctx, remipaddr); free_nullsafe(ctx, vpnipaddr); free_nullsafe(ctx, uname); DEBUG(ctx, 10, "** Function result: eurephia_connect(...) = %i", ret); return ret; } /** * Called when the client disconnects. This function logs some statistics about the session * * @param ctx eurephiaCTX * @param env openvpn environment table * * @return Returns 0 on failure and 1 on success. */ int eurephia_disconnect(eurephiaCTX *ctx, const char **env, certinfo *ci) { eurephiaSESSION *session = NULL; char *uname, *vpnipaddr, *remipaddr, *remport; char *bytes_sent, *bytes_rec, *duration; int ret = 0; DEBUG(ctx, 10, "** Function call: eurephia_disconnect(...)"); // Fetch needed info uname = GETENV_USERNAME(ctx, env); vpnipaddr = GETENV_POOLIPADDR(ctx, env); remipaddr = GETENV_TRUSTEDIP(ctx, env); remport = GETENV_TRUSTEDPORT(ctx, env); bytes_sent= GETENV_BYTESSENT(ctx, env); bytes_rec = GETENV_BYTESRECEIVED(ctx, env); duration = GETENV_TIMEDURATION(ctx, env); // Get a session ticket session = eDBopen_session_seed(ctx, ci->digest, ci->common_name, uname, vpnipaddr, remipaddr, remport); if( session == NULL ) { free_nullsafe(ctx, duration); free_nullsafe(ctx, bytes_rec); free_nullsafe(ctx, bytes_sent); free_nullsafe(ctx, remport); free_nullsafe(ctx, remipaddr); free_nullsafe(ctx, vpnipaddr); free_nullsafe(ctx, uname); return 0; } if( ctx->tuntype == tuntype_TUN ) { // Put the clients IP address into the disconnected list, to be used and freed // by eurephia_learn_address() afterwards eAdd_value(ctx, ctx->disconnected, vpnipaddr, session->sessionkey); } ret = eDBregister_logout(ctx, session, bytes_sent, bytes_rec, duration); eDBfree_session(ctx, session); eurephia_log(ctx, LOG_INFO, 1, "User '%s' logged out", uname); // Get the authentication session and destroy it session = eDBopen_session_seed(ctx, ci->digest, ci->common_name, uname, NULL, remipaddr, remport); if( !eDBdestroy_session(ctx, session) ) { eurephia_log(ctx, LOG_WARNING, 0, "Could not destroy authentication session (%s/%s/%s)", uname, ci->common_name, ci->digest); } eDBfree_session(ctx, session); free_nullsafe(ctx, duration); free_nullsafe(ctx, bytes_rec); free_nullsafe(ctx, bytes_sent); free_nullsafe(ctx, remport); free_nullsafe(ctx, remipaddr); free_nullsafe(ctx, vpnipaddr); free_nullsafe(ctx, uname); DEBUG(ctx, 10, "** Function result: eurephia_disconnect(...) = %i", ret); return ret; } /** * Called whenever openvpn assigns or changes IP addresses of the client. The function logs * this information to keep track of which user was assigned which IP address and which MAC address * the user had during the connection as well. If the firewall interface is enabled, it will also * request an update via the firewall driver. * * @param ctx eurephiaCTX * @param mode strings which can be "add", "delete". In some cases also "update". * @param macaddr string containing the MAC address of the client, if received from openvpn * @param env openvpn environment table * * @return Returns 0 on failure and 1 on success. */ int eurephia_learn_address(eurephiaCTX *ctx, const char *mode, const char *clientaddr, const char **env, certinfo *ci) { eurephiaSESSION *session = NULL; char *uname = NULL, *vpnipaddr = NULL, *remipaddr = NULL, *remport = NULL; char *fwprofile = NULL, *fwdest = NULL; int ret = 0, fw_enabled = 0; DEBUG(ctx, 10, "** Function call: eurephia_learn_address(ctx, '%.10s', '%.64s', ...)", mode, clientaddr); // Get firewall information fw_enabled = (eGet_value(ctx->dbc->config, "firewall_interface") != NULL); fwdest = eGet_value(ctx->dbc->config, "firewall_destination"); if( fw_enabled && (fwdest == NULL) ) { eurephia_log(ctx, LOG_CRITICAL, 0, "No firewall destination defined in the config."); } // FIXME: Figure out a way to differentiate IP addresses from VPN clients // and IP adresses behind clients. Only the former needs to be // processed by eurephia. // In TUN mode, if a client reconnects quickly after a disconnect, // OpenVPN re-uses the session for this and just sends an update // call to LEARN_ADDRESS. As eurephia in TUN mode treats disconnects // with an explicit 'delete' (see eurephia-auth.c for more info) and // don't wait for a separate LEARN_ADDRESS delete call (which never // seems to come), we treat 'update' as an ordinary 'add' in TUN mode. if( (strncmp(mode, "add", 3) == 0) || ((ctx->tuntype == tuntype_TUN) && (strncmp(mode, "update", 6) == 0)) ) { // Fetch needed info uname = GETENV_USERNAME(ctx, env); vpnipaddr = GETENV_POOLIPADDR(ctx, env); remipaddr = GETENV_TRUSTEDIP(ctx, env); remport = GETENV_TRUSTEDPORT(ctx, env); // Get a session ticket session = eDBopen_session_seed(ctx, ci->digest, ci->common_name, uname, vpnipaddr, remipaddr, remport); if( session == NULL ) { ret = 0; goto exit; } // Update openvpn_lastlog with the active VPN client addresses, and save it as a // session variable. clientaddr will contain the MAC address in TAP mode ret = eDBregister_vpnclientaddr(ctx, session, (ctx->tuntype == tuntype_TAP ? clientaddr : NULL), vpnipaddr, NULL); if( (fw_enabled) && (fwdest != NULL) ) { fwprofile = eDBget_firewall_profile(ctx, session); if( fwprofile != NULL ) { eFWupdateRequest req = { .mode = fwADD, }; if( ctx->tuntype == tuntype_TAP ) { strncpy(req.macaddress, clientaddr, sizeof(req.macaddress)); } else { strncpy(req.ipaddress, vpnipaddr, sizeof(req.ipaddress)); } strncpy(req.rule_destination, fwdest, sizeof(req.rule_destination)); strncpy(req.goto_destination, fwprofile, sizeof(req.goto_destination)); eFW_UpdateFirewall(ctx, &req); free_nullsafe(ctx, fwprofile); } } eDBfree_session(ctx, session); } else if( strncmp(mode, "delete", 6) == 0 ) { // Load the session if( ctx->tuntype == tuntype_TAP ) { // in TAP mode - use the MAC address session = eDBopen_session_macaddr(ctx, clientaddr); } else { // in TUN mode - use the disconnected list to find the session key eurephiaVALUES *sessval = eGet_valuestruct(ctx->disconnected, clientaddr); if( sessval ) { session = eDBsession_load(ctx, sessval->val, stSESSION); if( ctx->disconnected->next != NULL ) { ctx->disconnected = eRemove_value(ctx, ctx->disconnected, sessval->evgid, sessval->evid); } else { eClear_key_value(ctx->disconnected); } } } if( session == NULL ) { DEBUG(ctx, 25, "[learn-address] Untracked IP address: %s", clientaddr); ret = 1; /* Return success, as this isn't a failure in eurephia */ goto exit; } if( (fw_enabled) && (fwdest != NULL) ) { fwprofile = eDBget_firewall_profile(ctx, session); if( fwprofile != NULL ) { eFWupdateRequest req = { .mode = fwDELETE, }; if( ctx->tuntype == tuntype_TAP) { strncpy(req.macaddress, clientaddr, sizeof(req.macaddress)); } else { strncpy(req.ipaddress, clientaddr, sizeof(req.ipaddress)); } strncpy(req.rule_destination, fwdest, sizeof(req.rule_destination)); strncpy(req.goto_destination, fwprofile, sizeof(req.goto_destination)); eFW_UpdateFirewall(ctx, &req); free_nullsafe(ctx, fwprofile); } } ret = eDBdestroy_session(ctx, session); eDBfree_session(ctx, session); } exit: free_nullsafe(ctx, remport); free_nullsafe(ctx, remipaddr); free_nullsafe(ctx, vpnipaddr); free_nullsafe(ctx, uname); DEBUG(ctx, 10, "** Function result: eurephia_learn_address(ctx, '%s', '%.18s', ...) = %i", mode, clientaddr, ret); return ret; }