/* efw_iptables.c -- iptables implementation - updates Linux iptables * * 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 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.1" /**< The version of this firewall interface (driver) */ #define INTERFACEAPIVER 2 /**< Define the API level this firewall interface uses. */ /** * eFWmode string translation table */ static const char *eFWmode_str[7] = { "ADD", "DELETE", "BLACKLIST", "FLUSH", "INITIALISE", "SHUTDOWN", NULL }; /** * 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 eFWupdateRequest *req); int call_iptables(eurephiaCTX *ctx, const char *fwcmd, const 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; eFWupdateRequest req; 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(&req, 0, sizeof(req)); if( mq_receive(cfg->msgq, (char *)&req, EFW_MSG_SIZE, &prio) == -1 ) { eurephia_log(ctx, LOG_FATAL, 0, "eFW_RunFirewall: Error while reading messages from queue: %s", strerror(errno)); exit(2); } if( req.mode != fwSHUTDOWN ) { int res = 0; DEBUG(ctx, 20, "eFW_RunFirewall: Received %s", eFWmode_str[req.mode]); res = process_input(ctx, cfg->fw_command, &req); if( ! res ) { quit = 1; eurephia_log(ctx, LOG_FATAL, 0, "eFW_RunFirewall: Failed updating iptables"); } } else { quit = 1; } } 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 req Pointer a eFWupdateRequest struct with the iptables update details * * @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 eFWupdateRequest *req) { const char *iptables_args[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; int ret = 0, argc = 2; DEBUG(ctx, 36, "eFW_RunFirewall::process_input(ctx, %s, %s, %s, %s, %s)", eFWmode_str[req->mode], req->ipaddress, req->macaddress, req->rule_destination, req->goto_destination); // // Simple parsing of the input string // iptables_args[0] = (char *)fwcmd; switch( req->mode ) { case fwADD: iptables_args[1] = "-A"; case fwDELETE: if( iptables_args[1] == NULL ) { iptables_args[1] = "-D"; } // Prepare iptables arguments iptables_args[argc++] = req->rule_destination; if( strlen_nullsafe(req->ipaddress) > 0 ) { iptables_args[argc++] = "-s\0"; iptables_args[argc++] = req->ipaddress; } if( strlen_nullsafe(req->macaddress) > 0 ) { iptables_args[argc++] = "-m\0"; iptables_args[argc++] = "mac\0"; iptables_args[argc++] = "--mac-source\0"; iptables_args[argc++] = req->macaddress; } iptables_args[argc++] = "-m\0"; iptables_args[argc++] = "conntrack\0"; iptables_args[argc++] = "--ctstate\0"; iptables_args[argc++] = "NEW\0"; iptables_args[argc++] = "-j\0"; iptables_args[argc++] = req->goto_destination; iptables_args[argc++] = NULL; eurephia_log(ctx, LOG_INFO, 3, "eFW_RunFirewall - updating iptables rules " "==> mode: %s ipaddr: %s macaddr: '%s' destchain: '%s' jump: '%s'", eFWmode_str[req->mode], req->ipaddress, req->macaddress, req->rule_destination, req->goto_destination); ret = call_iptables(ctx, fwcmd, iptables_args); break; case fwBLACKLIST: iptables_args[1] = "-A\0"; iptables_args[2] = req->rule_destination; iptables_args[3] = "-s\0"; iptables_args[4] = req->ipaddress; iptables_args[5] = "-j\0"; iptables_args[6] = strlen_nullsafe(req->goto_destination) > 0 ? req->goto_destination : "DROP\0"; 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'", req->rule_destination, req->ipaddress, req->goto_destination); ret = call_iptables(ctx, fwcmd, iptables_args); break; case fwFLUSH: iptables_args[1] = "-F\0"; iptables_args[2] = req->rule_destination; iptables_args[3] = NULL; eurephia_log(ctx, LOG_INFO, 3, "eFW_RunFirewall - updating iptables rules " "==> mode: FLUSH destchain: '%s'", req->rule_destination); ret = call_iptables(ctx, fwcmd, iptables_args); break; case fwINITIALISE: // Init chain - flush it and then add needed rule for stateful inspection eurephia_log(ctx, LOG_INFO, 3, "eFW_RunFirewall - Initialising iptables chain '%s'", req->rule_destination); // Flush phase iptables_args[1] = "-F"; iptables_args[2] = req->rule_destination; iptables_args[3] = NULL; ret = call_iptables(ctx, fwcmd, iptables_args); // Add stateful inspection iptables_args[1] = "-I\0"; iptables_args[2] = req->rule_destination; iptables_args[3] = "-m\0"; iptables_args[4] = "conntrack\0"; iptables_args[5] = "--ctstate\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; } 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, const 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, (char * const*) 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; }