/* eurephiafw.c -- Firewall interface loader for the eurephia module * * GPLv2 only - Copyright (C) 2008 - 2012 * 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 eurephiafw.c * @author David Sommerseth * @date 2008-08-10 * * @brief Takes care of loading the configured firewall driver and provides a * generic API for updating the firewall rules. * */ #include #include #include #include #include #include #include #include #include #include #include #define EUREPHIA_FWINTF #include #include #include "eurephia_log.h" #include "eurephiafw.h" #include "eurephiafw_intf.h" #include "eurephia_getsym.h" #include "eurephia_nullsafe.h" #include "eurephia_values.h" #include "eurephiafw_helpers.h" #include "eurephiadb_driver.h" /** * Unloads the firewall driver * * @param ctx eurephiaCTX * * @return Returns 1 on success, otherwise 0. */ int eFW_unload(eurephiaCTX *ctx) { if( ctx == NULL ) { return 1; } if( ctx->eurephia_fw_intf != NULL ) { eurephia_log(ctx, LOG_INFO, 3, "Unloading eurephia firewall interface"); dlclose(ctx->eurephia_fw_intf); ctx->eurephia_fw_intf = NULL; return 0; } return 1; } /** * Loads the given firewall driver/interface * * @param ctx eurephiaCTX * @param intf full path to the firewall interface * * @return Returns 1 on success, otherwise 0. */ int eFW_load(eurephiaCTX *ctx, const char *intf) { if( (intf == NULL) || (strlen(intf) == 0) ) { eurephia_log(ctx, LOG_FATAL, 0, "No valid eurephia firewall interface indicated"); return 0; } eurephia_log(ctx, LOG_INFO, 2, "Loading eurephia firewall interface: %s", intf); ctx->eurephia_fw_intf = dlopen(intf, RTLD_NOW); if( ctx->eurephia_fw_intf == NULL ) { eurephia_log(ctx, LOG_FATAL, 0, "Could not open the eurephia firewall interface (%s)", intf); eurephia_log(ctx, LOG_FATAL, 1, "dlopen error: %s", dlerror()); return 0; } // Mandatory functions eFWinterfaceVersion = eGetSym(ctx, ctx->eurephia_fw_intf, "eFWinterfaceVersion"); eFWinterfaceAPIversion = eGetSym(ctx, ctx->eurephia_fw_intf, "eFWinterfaceAPIversion"); eurephia_log(ctx, LOG_INFO, 1, "Firewall interface loaded: %s (API version %i)", eFWinterfaceVersion(), eFWinterfaceAPIversion()); if( eFWinterfaceAPIversion() < 2 ) { eurephia_log(ctx, LOG_FATAL, 0, "The firewall interface must use API version 2 at minimum."); return 0; } // Configure firewall interface functions switch( eFWinterfaceAPIversion() ) { default: eurephia_log(ctx, LOG_WARNING, 0, "eurephia Firewall interface API is newer than what the running eurephia version is " "familiar with. Please consider to upgrade eurephia to take advantage of newer " "features in the firewall driver."); case 2: eFW_RunFirewall = eGetSym(ctx, ctx->eurephia_fw_intf, "eFW_RunFirewall"); break; } if( ctx->fatal_error > 0 ) { eurephia_log(ctx, LOG_FATAL, 0, "eurephia Firewall interface is not correctly initialised. " "eurephia authentication will not be available"); eFW_unload(ctx); return 0; } return 1; } /** * daemonize the firewall thread if "daemon" environment variable is set. * preserves stderr access after being daemonized, but * only if "daemon_log_direct" environment variable is set. * * @param ctx eurephiaCTX - Used for error logging only * @param logdir Set to 1 if logging should be redirected */ static void efw_daemonize(eurephiaCTX *ctx, const int logredir) { int fd = -1; if( logredir ) { fd = dup (2); } if( daemon(0, 0) < 0 ) { eurephia_log(ctx, LOG_WARNING, 0, "efw_daemonize() failed"); } else if( fd >= 3 ) { dup2(fd, 2); close(fd); } } /** * Starts the firewall thread. It is started as a separate process, to make sure it will run with * root privileges. * * @param ctx eurephiaCTX */ void eFW_StartFirewall(eurephiaCTX *ctx, const int daemon, const int logredir) { struct mq_attr mqattr; eurephiaCTX *shadowctx = NULL; eFWupdateRequest updreq; char *fwdest = NULL; unsigned int prio; int mqres = 0; ctx->fwcfg = (eurephiaFWINTF *) malloc_nullsafe(ctx, sizeof(eurephiaFWINTF)+2); // Create a fake eurephia context, just for logging shadowctx = (eurephiaCTX *) malloc_nullsafe(ctx, sizeof(eurephiaCTX)+2); assert( shadowctx != NULL ); if( mlock(shadowctx, sizeof(eurephiaCTX)+2) < 0 ) { eurephia_log(ctx, LOG_CRITICAL, 0, "Could not mlock() firewall context: %s", strerror(errno)); }; shadowctx->context_type = ECTX_NO_PRIVILEGES; shadowctx->log = ctx->log; (*ctx->fwcfg).thrdata.ctx = shadowctx; (*ctx->fwcfg).thrdata.fw_command = strdup_nullsafe(eGet_value(ctx->dbc->config, "firewall_command")); if( (*ctx->fwcfg).thrdata.fw_command == NULL) { eurephia_log(ctx, LOG_PANIC, 0, "Could not find firewall_command in configuration. " "Firewall updates will not be available."); return; } else { eurephia_log(ctx, LOG_INFO, 1, "Using %s to update the firewall rules.", (*ctx->fwcfg).thrdata.fw_command ); } fwdest = eGet_value(ctx->dbc->config, "firewall_destination"); if( fwdest == NULL ) { eurephia_log(ctx, LOG_PANIC, 0, "Could not find firewall_destination in configuration. " "Firewall updates will not be available."); return; } else { eurephia_log(ctx, LOG_INFO, 1, "Using '%s' as firewall rule for VPN accesses", fwdest); } ctx->fwcfg->fwblacklist = eGet_value(ctx->dbc->config, "firewall_blacklist_destination"); if( ctx->fwcfg->fwblacklist != NULL ) { eurephia_log(ctx, LOG_INFO, 1, "Blacklisted IP addresses will also be blocked in '%s'", ctx->fwcfg->fwblacklist); // Create value space for blacklisted IP addresses ctx->fwcfg->blacklisted = eCreate_value_space(ctx, 20); // Setup where to send the blacklisted IP addresses - default is to drop them. ctx->fwcfg->fwblacklist_sendto = eGet_value(ctx->dbc->config, "firewall_blacklist_send_to"); if( ctx->fwcfg->fwblacklist_sendto == NULL ) { eurephia_log(ctx, LOG_INFO, 2,"Blacklisted IP addresses will be dropped immediately"); } else { eurephia_log(ctx, LOG_INFO, 2,"Blacklisted IP addresses will be sent to '%s'", ctx->fwcfg->fwblacklist_sendto); } } eurephia_log(ctx, LOG_INFO, 3, "Starting eurephia firewall interface"); // Setup semaphores we need if( efwSetupSemaphores(ctx, &(*ctx->fwcfg).thrdata) == 0 ) { free_nullsafe(ctx, ctx->fwcfg->thrdata.fw_command); return; }; // Setup a message queue if( efwSetupMessageQueue(ctx, &(*ctx->fwcfg).thrdata) == 0 ) { free_nullsafe(ctx, ctx->fwcfg); return; } // Make sure that these variables are not available in the child madvise(ctx, sizeof(eurephiaCTX), MADV_DONTFORK); // Start a new process (should run with root permissions) - which will do the firewall work if( (ctx->fwcfg->fwproc_pid = fork()) < 0 ) { eurephia_log(ctx, LOG_PANIC, 0, "Could not fork out a child process for the firewall interface (%s)", strerror(errno)); return; } switch( ctx->fwcfg->fwproc_pid ) { case 0: // Child process if( daemon ) { efw_daemonize(ctx, logredir); } eDBdisconnect(ctx); eFW_RunFirewall(&(*ctx->fwcfg).thrdata); exit(-1); // If our child process exits abnormally. default: // Main process eurephia_log(ctx, LOG_INFO, 2, "Firewall updater process started (pid %i)", ctx->fwcfg->fwproc_pid); } // Flush the message queue for old messages if( mq_getattr((*ctx->fwcfg).thrdata.msgq, &mqattr) == 0 ) { long i; char buf[1026]; memset(&buf, 0, 1026); if( mqattr.mq_curmsgs > 0 ) { for( i = 0; i < mqattr.mq_curmsgs; i++ ) { if( mq_receive((*ctx->fwcfg).thrdata.msgq, &buf[0], 1024, &prio) == -1 ) { eurephia_log(ctx, LOG_CRITICAL, 0, "Error while emptying messages from queue: %s", strerror(errno)); } else { DEBUG(ctx, 28, "Removed message on queue: %s", buf); } } } eurephia_log(ctx, LOG_INFO, 3, "Message queue for firewall updates is ready"); } else { eurephia_log(ctx, LOG_FATAL, 0, "Could not retrieve message queue attributes (%s)", strerror(errno)); } // Indicate for the FW module that we are ready sem_post(ctx->fwcfg->thrdata.semp_master); // Waiting for the FW module to get ready DEBUG(ctx, 28, "eFW master is ready, waiting for the eFW worker to get ready"); sem_wait(ctx->fwcfg->thrdata.semp_worker); eurephia_log(ctx, LOG_INFO, 2, "eFW interface initialised."); // Initialise the chain memset(&updreq, 0, sizeof(eFWupdateRequest)); updreq.mode = fwINITIALISE; strncpy(updreq.rule_destination, fwdest, 64); mqres = mq_send((*ctx->fwcfg).thrdata.msgq, (const char *)&updreq, EFW_MSG_SIZE, 1); if( mqres < 0 ) { eurephia_log(ctx, LOG_ERROR, 0, "Could not request firewall initialisation of the %s chain: (%i) %s", fwdest, mqres, strerror(errno)); }; if( ctx->fwcfg->fwblacklist != NULL ) { eurephiaVALUES *blacklisted = NULL, *p = NULL; eFWupdateRequest updreq; // Flushing firewall blacklist chain memset(&updreq, 0, sizeof(eFWupdateRequest)); updreq.mode = fwFLUSH; strncpy(updreq.rule_destination, ctx->fwcfg->fwblacklist, 64); errno = 0; mqres = mq_send((*ctx->fwcfg).thrdata.msgq, (const char *)&updreq, EFW_MSG_SIZE, 1); if( mqres < 0 ) { eurephia_log(ctx, LOG_ERROR, 0, "Could not request flushing of the %s chain: (%i) %s", ctx->fwcfg->fwblacklist, mqres, strerror(errno)); }; // Registering already blacklisted IP addresses into the proper firewall chain memset(&updreq, 0, sizeof(eFWupdateRequest)); updreq.mode = fwBLACKLIST; blacklisted = eDBget_blacklisted_ip(ctx); strncpy(updreq.rule_destination, ctx->fwcfg->fwblacklist, 64); strncpy(updreq.goto_destination, ctx->fwcfg->fwblacklist_sendto ? ctx->fwcfg->fwblacklist_sendto : "\0", 64); p = blacklisted; while( p != NULL ) { if( p->val != NULL ) { memset(&updreq.ipaddress, 0, 36); strncpy(updreq.ipaddress, p->val, 34); eFW_UpdateFirewall(ctx, &updreq); } p = p->next; } eFree_values(ctx, blacklisted); } } /** * Stops the firewall update process. * * @param ctx eurephiaCTX */ void eFW_StopFirewall(eurephiaCTX *ctx) { char *fwdest = NULL; eFWupdateRequest updreq; struct timespec tsp; int mqres = 0; if( ctx->fwcfg == NULL ) { return; } eurephia_log(ctx, LOG_INFO, 2, "Stopping eurephia firewall interface"); // Flush the firewall chain before shutting down, to make sure // we don't unintentionally some accesses open fwdest = eGet_value(ctx->dbc->config, "firewall_destination"); if( fwdest != NULL ) { memset(&updreq, 0, sizeof(eFWupdateRequest)); updreq.mode = fwFLUSH; strncpy(updreq.rule_destination, fwdest, 64); errno = 0; mqres = mq_send((*ctx->fwcfg).thrdata.msgq, (const char *)&updreq, EFW_MSG_SIZE, 1); if( mqres < 0 ) { eurephia_log(ctx, LOG_CRITICAL, 0, "Could not request firewall flushing of the %s chain: %s", fwdest, strerror(errno)); }; } else { eurephia_log(ctx, LOG_CRITICAL, 0, "firewall_destination not set in config. Will not flush " "firewall before shutting down the firewall interface."); } // Send shutdown message to the firewall module process memset(&updreq, 0, sizeof(eFWupdateRequest)); updreq.mode = fwSHUTDOWN; errno = 0; mqres = mq_send((*ctx->fwcfg).thrdata.msgq, (const char *)&updreq, EFW_MSG_SIZE, 1); if( mqres < 0 ) { eurephia_log(ctx, LOG_PANIC, 0, "Could not initiate shutdown on eFW module: %s", strerror(errno)); kill(ctx->fwcfg->fwproc_pid, SIGABRT); } // // Wait for the firewall module process to finish // // prepare a timeout - 30 seconder's should be enough if( clock_gettime(CLOCK_REALTIME, &tsp) == -1 ) { eurephia_log(ctx, LOG_FATAL, 0, "Could not prepare timeout for firewall shutdown: %s", strerror(errno)); sleep(3); kill(ctx->fwcfg->fwproc_pid, SIGABRT); } tsp.tv_sec += 30; // Wait for shutdown accepted signal if( sem_timedwait(ctx->fwcfg->thrdata.semp_worker, &tsp) == -1 ) { eurephia_log(ctx, LOG_PANIC, 0, "Failed to wait for eFW module process to quit: %s", strerror(errno)); sleep(3); kill(ctx->fwcfg->fwproc_pid, SIGABRT); } // Send acknowledge back sem_post(ctx->fwcfg->thrdata.semp_master); // Clean up and exit munlock(ctx->fwcfg->thrdata.ctx, sizeof(eurephiaCTX)+2); free_nullsafe(ctx, ctx->fwcfg->thrdata.ctx); free_nullsafe(ctx, ctx->fwcfg->fwblacklist_sendto); eFree_values(ctx, ctx->fwcfg->blacklisted); free_nullsafe(ctx, (*ctx->fwcfg).thrdata.fw_command); free_nullsafe(ctx, ctx->fwcfg); eurephia_log(ctx, LOG_INFO, 2, "eurephia firewall interface is stopped"); } /** * Requests an update of the firewall rules * * @param ctx eurephiaCTX * @param request eFWupdateRequest struct with the needed firewall information * * @return Returns 1 on success, otherwise 0. */ int eFW_UpdateFirewall(eurephiaCTX *ctx, eFWupdateRequest *request ) { char *blchk = NULL; unsigned int prio = 0; int mqres = 0; if( (*ctx->fwcfg).thrdata.fw_command == NULL ) { eurephia_log(ctx, LOG_FATAL, 0, "Function call: eFW_UpdateFirewall() -- " "firewall_command is not configured. Firewall rules was not updated."); return 0; } if( (request == NULL) ) { eurephia_log(ctx, LOG_FATAL, 0, "eFW_UpdateFirewall: Invalid update request"); return 0; } switch( request->mode ) { case fwADD: prio = 11; case fwDELETE: if( strlen_nullsafe(request->rule_destination) < 1 ) { eurephia_log(ctx, LOG_FATAL, 0, "eFW_UpdateFirewall: No firewall destination defined"); return 0; } if( (strlen_nullsafe(request->macaddress) < 1) && (strlen_nullsafe(request->ipaddress) < 1) ) { eurephia_log(ctx, LOG_ERROR, 0, "eFW_UpdateFirewall: No MAC address nor IP address was given received"); return 1; } if( prio == 0 ) { prio = 12; // It's a delete request, it's not as important as other messages } eurephia_log(ctx, LOG_INFO, 3, "Function call: eFW_UpdateFirewall(ctx, %s, '%.18s', '%s', '%s')", (request->mode == fwADD ? "ADD" : "DELETE"), (ctx->tuntype == tuntype_TAP ? request->macaddress : request->ipaddress), request->rule_destination, request->goto_destination); errno = 0; mqres = mq_send((*ctx->fwcfg).thrdata.msgq, (const char *)request, EFW_MSG_SIZE, prio); if( mqres < 0 ) { eurephia_log(ctx, LOG_CRITICAL, 0, "eFW_updateFirewall: Failed to send update request - %s", strerror(errno)); return 0; } return 1; case fwBLACKLIST: if( strlen_nullsafe(request->ipaddress) < 1) { eurephia_log(ctx, LOG_ERROR, 0, "eFW_UpdateFirewall: No IP address given for blacklist"); return 1; } eurephia_log(ctx, LOG_INFO, 3, "Function call: eFW_UpdateFirewall(ctx, %s, '%.34s','%s', NULL)", "BLACKLIST", request->ipaddress, request->rule_destination); // Check if IP address is already registered as blacklisted if( (blchk = eGet_value(ctx->fwcfg->blacklisted, request->ipaddress)) == NULL ) { // Setup rule destinations strncpy(request->rule_destination, ctx->fwcfg->fwblacklist, 64); if( ctx->fwcfg->fwblacklist_sendto != NULL ) { strncpy(request->goto_destination, ctx->fwcfg->fwblacklist_sendto, 64); } else { strncpy(request->goto_destination, "DROP\0", 6); } errno = 0; mqres = mq_send((*ctx->fwcfg).thrdata.msgq, (const char *)request, EFW_MSG_SIZE, prio); if( mqres < 0 ) { eurephia_log(ctx, LOG_CRITICAL, 0, "eFW_updateFirewall: Failed to send update request - %s", strerror(errno)); return 0; } eAdd_value(ctx, ctx->fwcfg->blacklisted, request->ipaddress, request->rule_destination); } else { eurephia_log(ctx, LOG_INFO, 5, "IP address already blacklisted in '%s'", blchk); } return 1; case fwFLUSH: case fwINITIALISE: eurephia_log(ctx, LOG_ERROR, 5, "eFW_UpdateFirewall: Unsupported update request"); return 0; default: eurephia_log(ctx, LOG_CRITICAL, 0, "Function call: eFW_UpdateFirewall(ctx, %s, '%s') - UNKNOWN MODE", "(unknown)", request->macaddress); return 0; } }