/* efw_iptables.c -- iptables implementation - updates Linux iptables * * GPLv2 only - Copyright (C) 2008 - 2010 * 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 efw-iptables.c * @author David Sommerseth * @date 2008-08-10 * * @brief Firewall driver for iptables. Understands how to update iptables, in other words. * */ #include #include #include #include #include #include #include #define EUREPHIA_FWINTF /**< Include the proper eurephiaFWINTF declaration in eurephiaCTX */ #include #include #include #include #include #define INTERFACEVER "1.0" /**< The version of this firewall interface (driver) */ #define INTERFACEAPIVER 1 /**< Define the API level this firewall interface uses. */ /** * Mandatory function, contains driver information. * * @return Retuns a static string, containing the version information. */ const char *eFWinterfaceVersion() { return "eFW-iptables (v"INTERFACEVER") David Sommerseth 2008 (C) GPLv2"; } /** * Mandatory function, contains driver information. * * @return Retuns an integer which correponds to the API level this driver corresponds to. */ int eFWinterfaceAPIversion() { return INTERFACEAPIVER; } int process_input(eurephiaCTX *ctx, const char *fwcmd, const char *msg); int call_iptables(eurephiaCTX *ctx, const char *fwcmd, char **ipt_args); /** * The main routine of the firewall interface. This loops until it gets a shutdown message. * * @param fwargs efw_threaddata pointer, with needed information to communicate with the openvpn process. */ void eFW_RunFirewall(void *fwargs) { efw_threaddata *cfg = (efw_threaddata *) fwargs; eurephiaCTX *ctx = (eurephiaCTX *) cfg->ctx; int quit = 0; unsigned int prio; char buf[EFW_MSG_SIZE+2]; struct timespec tsp; DEBUG(ctx, 28, "eFW_RunFirewall: Waiting for eFW master to get ready"); sem_wait(cfg->semp_master); DEBUG(ctx, 28, "eFW_RunFirewall: Telling eFW master that the worker process is ready"); sem_post(cfg->semp_worker); if( cfg->fw_command == NULL ) { eurephia_log(ctx, LOG_FATAL, 0, "eFW_RunFirewall: firewall_command is not configured. " "iptables will not be updated."); exit(3); } eurephia_log(ctx, LOG_INFO, 1, "efw_iptables: Firewall interface started"); // Main loop ... grab messages of the messague queue until shutdown command is sent, or a failure happens while( quit == 0 ) { memset(buf, 0, EFW_MSG_SIZE+2); if( mq_receive(cfg->msgq, &buf[0], EFW_MSG_SIZE, &prio) == -1 ) { eurephia_log(ctx, LOG_FATAL, 0, "eFW_RunFirewall: Error while reading messages from queue: %s", strerror(errno)); exit(2); } quit = (strncmp(buf, "FWSHUTDOWN", 10) == 0 ); if( !quit ) { int res = 0; DEBUG(ctx, 20, "eFW_RunFirewall: Received '%s'", buf); res = process_input(ctx, cfg->fw_command, buf); if( ! res ) { quit = 1; eurephia_log(ctx, LOG_FATAL, 0, "eFW_RunFirewall: Failed updating iptables"); } } } efwRemoveMessageQueue(ctx, fwargs); DEBUG(ctx, 28, "eFW_RunFirewall: Telling eFW master that the worker process is about to shut down"); sem_post(cfg->semp_worker); DEBUG(ctx, 28, "eFW_RunFirewall: Waiting for eFW master to acknowledge"); // Prepare a timeout if( clock_gettime(CLOCK_REALTIME, &tsp) == -1 ) { eurephia_log(ctx, LOG_FATAL, 0, "eFW_RunFirewall: Could not prepare timeout for shutdown ack: %s", strerror(errno)); sleep(10); } else { tsp.tv_sec += 30; // Wait up to 30 seconds for shutdown ack. // Wait for acknowledge if( sem_timedwait(cfg->semp_master, &tsp) == -1 ) { eurephia_log(ctx, LOG_PANIC, 0, "eFW_RunFirewall: Did not receive any shutdown ack: %s", strerror(errno)); } else { eurephia_log(ctx, LOG_INFO, 1, "efw_iptables: Firewall interface is shut down"); } } efwRemoveSemaphores(ctx, fwargs); exit(0); } /** * Internal function. Processes firewall update messages recieved via POSIX MQ. * * @param ctx eurephiaCTX - This is just a shadow context, to make logging possible * @param fwcmd The command to be executed, can be 'A'-add, 'D'-delete, 'F'-flush, 'B'-blacklist, 'I'-init * @param input Contains a string with information for the command. Format varies with command mode. * * @return Returns 1 on success, otherwise 0. If 0 is sent, it means the firewall process should shut down, * and it should only be used in very critical situations. */ int process_input(eurephiaCTX *ctx, const char *fwcmd, const char *input) { char mode[3], *addr = NULL, *destchain = NULL, *jump = NULL; char *msg = NULL, *orig_msg = NULL; char *iptables_args[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; int ret = 0; orig_msg = strdup_nullsafe(input); msg = orig_msg; DEBUG(ctx, 36, "eFW_RunFirewall::process_input(ctx, '%s')", msg); // // Simple parsing of the input string // mode[0] = '-'; mode[1] = *msg; mode[2] = 0; msg += 2; iptables_args[0] = (char *)fwcmd; switch( mode[1] ) { case 'A': case 'D': iptables_args[1] = mode; addr = msg; // start of string for macaddr // Search for end of macaddr and NULL terminate it destchain = addr+1; while( (*destchain != 0x20) || (*destchain == 0) ) { destchain++; } if( *destchain == 0 ) { return 0; } *destchain = 0; // end of string for macaddr destchain++; // start of string for destchain // Search for end of destchain and NULL terminate it jump = destchain+1; while( (*jump != 0x20) || (*jump == 0) ) { jump++; } *jump = 0; // end of string for destchain jump++; // start of string for jump // Prepare iptables arguments iptables_args[2] = destchain; iptables_args[3] = "-m\0"; iptables_args[4] = "mac\0"; iptables_args[5] = "--mac-source\0"; iptables_args[6] = addr; iptables_args[7] = "-m\0"; iptables_args[8] = "state\0"; iptables_args[9] = "--state\0"; iptables_args[10] = "NEW\0"; iptables_args[11] = "-j\0"; iptables_args[12] = jump; iptables_args[13] = NULL; eurephia_log(ctx, LOG_INFO, 3, "eFW_RunFirewall - updating iptables rules " "==> mode: %s macaddr: '%s' destchain: '%s' jump: '%s'", (mode[1] == 'A' ? "ADD":"DELETE"), addr, destchain, jump); ret = call_iptables(ctx, fwcmd, iptables_args); break; case 'B': addr = msg; // start of string for IP address to block // Search for end of IP address and NULL terminate it destchain = addr+1; while( (*destchain != 0x20) || (*destchain == 0) ) { destchain++; } if( *destchain == 0 ) { return 0; } *destchain = 0; // end of string for IP address destchain++; // start of string for destchain // Search for end of destchain and NULL terminate it jump = destchain+1; while( (*jump != 0x20) || (*jump == 0) ) { jump++; } *jump = 0; // end of string for destchain jump++; // start of string for jump if( *jump == 0 ) { return 0; } iptables_args[1] = "-A\0"; iptables_args[2] = destchain; iptables_args[3] = "-s\0"; iptables_args[4] = addr; iptables_args[5] = "-j\0"; iptables_args[6] = jump; iptables_args[7] = NULL; eurephia_log(ctx, LOG_INFO, 3, "eFW_RunFirewall - updating iptables rules " "==> mode: BLACKLIST destchain: '%s' IP address: %s Send to: '%s'", destchain, addr, jump); ret = call_iptables(ctx, fwcmd, iptables_args); break; case 'F': iptables_args[1] = mode; destchain = msg; iptables_args[2] = destchain; iptables_args[3] = NULL; eurephia_log(ctx, LOG_INFO, 3, "eFW_RunFirewall - updating iptables rules " "==> mode: FLUSH destchain: '%s'", destchain); ret = call_iptables(ctx, fwcmd, iptables_args); break; case 'I': // Init chain - flush it and then add needed rule for stateful inspection destchain = msg; eurephia_log(ctx, LOG_INFO, 3, "eFW_RunFirewall - Initialising iptables chain '%s'", destchain); // Flush phase iptables_args[1] = "-F"; destchain = msg; iptables_args[2] = destchain; iptables_args[3] = NULL; ret = call_iptables(ctx, fwcmd, iptables_args); // Add stateful inspection iptables_args[1] = "-I\0"; iptables_args[2] = destchain; iptables_args[3] = "-m\0"; iptables_args[4] = "state\0"; iptables_args[5] = "--state\0"; iptables_args[6] = "ESTABLISHED,RELATED\0"; iptables_args[7] = "-j\0"; iptables_args[8] = "ACCEPT\0"; ret &= call_iptables(ctx, fwcmd, iptables_args); break; default: eurephia_log(ctx, LOG_CRITICAL, 0, "eFW_RunFirewall::process_input: Malformed update request"); ret = 1; } free_nullsafe(ctx, orig_msg); return ret; } /** * This function does the actual iptables call. It will fork out a process and do the * assigned iptables command. * * @param ctx eurephiaCTX - shadow context, only with pointers to log files. * @param fwcmd String containing full filename to the binary to execute * @param ipt_args The iptables arguments * * @return Returns 1 on success, otherwise 0. When 0 is returned, the complete firewall process will be * shut down. */ int call_iptables(eurephiaCTX *ctx, const char *fwcmd, char **ipt_args) { pid_t pid; int cmdret = -1; // Fork out a child process which will run the iptables command. Since the execve replaces // the current process, we need to do the forking first. if( (pid = fork()) < 0) { eurephia_log(ctx, LOG_FATAL, 0, "eFW_RunFirewall::process_input: Failed to fork process for %s", fwcmd); return 0; } switch( pid ) { case 0: // child process - execute the program and exit execve(fwcmd, ipt_args, NULL); exit(1); // execve should replace the process, but if it fails to do so, make sure we exit default: // parent process if( waitpid(pid, &cmdret, 0) != pid ) { eurephia_log(ctx, LOG_WARNING, 0, "eFW_RunFirewall::process_input: Failed to wait for process for %s" " to complete (%s)", fwcmd, strerror(errno)); } eurephia_log(ctx, LOG_INFO, 4, "eFW_RunFirewall - iptables exited with code: %i ", cmdret); } return 1; }