]> git.ipfire.org Git - people/teissler/ipfire-2.x.git/blobdiff - src/misc-progs/openvpnctrl.c
openvpnctrl: Pass external IP address to N2N daemons.
[people/teissler/ipfire-2.x.git] / src / misc-progs / openvpnctrl.c
index 1a40c9a6129beddff13c89b944f0a51bacfbcadd..272db0faade2dc3abb79687ad1d077c7308b4175 100644 (file)
@@ -4,8 +4,11 @@
 #include <unistd.h>
 #include <stdlib.h>
 #include <sys/types.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
 #include <fcntl.h>
 #include "setuid.h"
+#include "netutil.h"
 #include "libsmooth.h"
 
 #define noovpndebug
@@ -22,15 +25,20 @@ char enableblue[STRING_SIZE] = "off";
 char enableorange[STRING_SIZE] = "off";
 
 // consts
-char OVPNRED[STRING_SIZE] = "OVPN";
-char OVPNBLUE[STRING_SIZE] = "OVPN_BLUE_";
-char OVPNORANGE[STRING_SIZE] = "OVPN_ORANGE_";
-char WRAPPERVERSION[STRING_SIZE] = "ipfire-2.1.2";
+char OVPNINPUT[STRING_SIZE] = "OVPNINPUT";
+char OVPNBLOCK[STRING_SIZE] = "OVPNBLOCK";
+char OVPNNAT[STRING_SIZE] = "OVPNNAT";
+char WRAPPERVERSION[STRING_SIZE] = "ipfire-2.2.4";
 
 struct connection_struct {
        char name[STRING_SIZE];
+       char type[STRING_SIZE];
        char proto[STRING_SIZE];
-       int port;
+       char status[STRING_SIZE];
+       char local_subnet[STRING_SIZE];
+       char transfer_subnet[STRING_SIZE];
+       char role[STRING_SIZE];
+       char port[STRING_SIZE];
        struct connection_struct *next;
 };
 
@@ -59,16 +67,18 @@ void usage(void)
        printf("      kills/stops OpenVPN\n");
        printf(" -r   --restart\n");
        printf("      restarts OpenVPN (implicitly creates chains and firewall rules)\n");
+       printf(" -sn2n --start-net-2-net\n");
+       printf("      starts all net2net connections\n");
+       printf("      you may pass a connection name to the switch to only start a specific one\n");
+       printf(" -kn2n --kill-net-2-net\n");
+       printf("      kills all net2net connections\n");
+       printf("      you may pass a connection name to the switch to only start a specific one\n");
        printf(" -d   --display\n");
        printf("      displays OpenVPN status to syslog\n");
        printf(" -fwr --firewall-rules\n");
        printf("      removes current OpenVPN chains and rules and resets them according to the config\n");
        printf(" -sdo --start-daemon-only\n");
        printf("      starts OpenVPN daemon only\n");
-       printf(" -ccr --create-chains-and-rules\n");
-       printf("      creates chains and rules for OpenVPN\n");
-       printf(" -dcr --delete-chains-and-rules\n");
-       printf("      removes all chains for OpenVPN\n");
        exit(1);
 }
 
@@ -81,7 +91,8 @@ connection *getConnections() {
        }
 
        char line[STRING_SIZE] = "";
-       char *result;
+       char result[STRING_SIZE] = "";
+       char *resultptr;
        int count;
        connection *conn_first = NULL;
        connection *conn_last = NULL;
@@ -102,17 +113,39 @@ connection *getConnections() {
                conn_last = conn_curr;
 
                count = 0;
-               result = strtok(line, ",");
-               while (result) {
-                       if (count == 2) {
+               char *lineptr = &line;
+               while (1) {
+                       if (*lineptr == NULL)
+                               break;
+
+                       resultptr = result;
+                       while (*lineptr != NULL) {
+                               if (*lineptr == ',') {
+                                       lineptr++;
+                                       break;
+                               }
+                               *resultptr++ = *lineptr++;
+                       }
+                       *resultptr = '\0';
+
+                       if (count == 1) {
+                               strcpy(conn_curr->status, result);
+                       } else if (count == 2) {
                                strcpy(conn_curr->name, result);
-                       } else if (count == 12) {
+                       } else if (count == 4) {
+                               strcpy(conn_curr->type, result);
+                       } else if (count == 7) {
+                               strcpy(conn_curr->role, result);
+                       } else if (count == 9) {
+                               strcpy(conn_curr->local_subnet, result);
+                       } else if (count == 28) {
+                               strcpy(conn_curr->transfer_subnet, result);
+                       } else if (count == 29) {
                                strcpy(conn_curr->proto, result);
-                       } else if (count == 13) {
-                               conn_curr->port = atoi(result);
+                       } else if (count == 30) {
+                               strcpy(conn_curr->port, result);
                        }
 
-                       result = strtok(NULL, ",");
                        count++;
                }
        }
@@ -125,7 +158,6 @@ connection *getConnections() {
 int readPidFile(const char *pidfile) {
        FILE *fp = fopen(pidfile, "r");
        if (fp == NULL) {
-               fprintf(stderr, "PID file not found: '%s'\n", pidfile);
                exit(1);
        }
 
@@ -136,8 +168,30 @@ int readPidFile(const char *pidfile) {
        return pid;
 }
 
+int readExternalAddress(char* address) {
+       FILE *fp = fopen("/var/ipfire/red/local-ipaddress", "r");
+       if (!fp)
+               goto ERROR;
+
+       int r = fscanf(fp, "%s", address);
+       fclose(fp);
+
+       if (r < 0)
+               goto ERROR;
+
+       /* In case the read IP address is not valid, we empty
+        * the content of address and return non-zero. */
+       if (!VALID_IP(address))
+               goto ERROR;
+
+       return 0;
+
+ERROR:
+       address = NULL;
+       return 1;
+}
+
 void ovpnInit(void) {
-       
        // Read OpenVPN configuration
        kv = initkeyvalues();
        if (!readkeyvalues(kv, CONFIG_ROOT "/ovpn/settings")) {
@@ -146,17 +200,14 @@ void ovpnInit(void) {
        }
 
        if (!findkey(kv, "ENABLED", enablered)) {
-               fprintf(stderr, "Cannot read ENABLED\n");
                exit(1);
        }
 
        if (!findkey(kv, "ENABLED_BLUE", enableblue)){
-               fprintf(stderr, "Cannot read ENABLED_BLUE\n");
                exit(1);
        }
 
        if (!findkey(kv, "ENABLED_ORANGE", enableorange)){
-               fprintf(stderr, "Cannot read ENABLED_ORANGE\n");
                exit(1);
        }
        freekeyvalues(kv);
@@ -182,24 +233,22 @@ void ovpnInit(void) {
        }
 
        kv=initkeyvalues();
-       if (!readkeyvalues(kv, CONFIG_ROOT "/ethernet/settings"))
-       {
+       if (!readkeyvalues(kv, CONFIG_ROOT "/ethernet/settings")) {
                fprintf(stderr, "Cannot read ethernet settings\n");
                exit(1);
        }
        
-       if (strcmp(enableblue, "on")==0){
-               if (!findkey(kv, "BLUE_DEV", blueif)){
-                       fprintf(stderr, "Cannot read BLUE_DEV\n");
+       if (strcmp(enableblue, "on") == 0) {
+               if (!findkey(kv, "BLUE_DEV", blueif)) {
                        exit(1);
                }
        }
-       if (strcmp(enableorange, "on")==0){
-               if (!findkey(kv, "ORANGE_DEV", orangeif)){
-                       fprintf(stderr, "Cannot read ORNAGE_DEV\n");
+
+       if (strcmp(enableorange, "on") == 0) {
+               if (!findkey(kv, "ORANGE_DEV", orangeif)) {
                        exit(1);
                }
-       }               
+       }
        freekeyvalues(kv);
 }
 
@@ -210,108 +259,109 @@ void executeCommand(char *command) {
        safe_system(strncat(command, " >/dev/null 2>&1", 17));
 }
 
-void setChainRules(char *chain, char *interface, char *protocol, char *port)
-{
-       char str[STRING_SIZE];
+void addRule(const char *chain, const char *interface, const char *protocol, const char *port) {
+       char command[STRING_SIZE];
 
-       sprintf(str, "/sbin/iptables -A %sINPUT -i %s -p %s --dport %s -j ACCEPT", chain, interface, protocol, port);
-       executeCommand(str);
-       sprintf(str, "/sbin/iptables -A %sINPUT -i tun+ -j ACCEPT", chain);
-       executeCommand(str);
-       sprintf(str, "/sbin/iptables -A %sFORWARD -i tun+ -j ACCEPT", chain);
-       executeCommand(str);
+       snprintf(command, STRING_SIZE - 1, "/sbin/iptables -A %s -i %s -p %s --dport %s -j ACCEPT",
+               chain, interface, protocol, port);
+       executeCommand(command);
 }
 
 void flushChain(char *chain) {
        char str[STRING_SIZE];
 
-       sprintf(str, "/sbin/iptables -F %sINPUT", chain);
+       snprintf(str, STRING_SIZE - 1, "/sbin/iptables -F %s", chain);
        executeCommand(str);
-       sprintf(str, "/sbin/iptables -F %sFORWARD", chain);
-       executeCommand(str);
-       safe_system(str);
 }
 
-void deleteChainReference(char *chain) {
+void flushChainNAT(char *chain) {
        char str[STRING_SIZE];
 
-       sprintf(str, "/sbin/iptables -D INPUT -j %sINPUT", chain);
-       executeCommand(str);
-       safe_system(str);
-       sprintf(str, "/sbin/iptables -D FORWARD -j %sFORWARD", chain);
+       snprintf(str, STRING_SIZE - 1, "/sbin/iptables -t nat -F %s", chain);
        executeCommand(str);
-       safe_system(str);
 }
 
-void deleteChain(char *chain) {
-       char str[STRING_SIZE];
+char* calcTransferNetAddress(const connection* conn) {
+       char *subnetmask = strdup(conn->transfer_subnet);
+       char *address = strsep(&subnetmask, "/");
 
-       sprintf(str, "/sbin/iptables -X %sINPUT", chain);
-       executeCommand(str);
-       sprintf(str, "/sbin/iptables -X %sFORWARD", chain);
-       executeCommand(str);
-}
+       if ((address == NULL) || (subnetmask == NULL)) {
+               goto ERROR;
+       }
 
-void deleteAllChains(void) {
-       // not an elegant solution, but to avoid timing problems with undeleted chain references
-       deleteChainReference(OVPNRED);
-       deleteChainReference(OVPNBLUE);
-       deleteChainReference(OVPNORANGE);
-       flushChain(OVPNRED);
-       flushChain(OVPNBLUE);
-       flushChain(OVPNORANGE);
-       deleteChain(OVPNRED);
-       deleteChain(OVPNBLUE);
-       deleteChain(OVPNORANGE);
-}
+       in_addr_t _address    = inet_addr(address);
+       in_addr_t _subnetmask = inet_addr(subnetmask);
+       _address &= _subnetmask;
 
-void createChainReference(char *chain) {
-       char str[STRING_SIZE];
-       sprintf(str, "/sbin/iptables -I INPUT %s -j %sINPUT", "14", chain);
-       executeCommand(str);
-       sprintf(str, "/sbin/iptables -I FORWARD %s -j %sFORWARD", "12", chain);
-       executeCommand(str);
-}
+       if (strcmp(conn->role, "server") == 0) {
+               _address += 1 << 24;
+       } else if (strcmp(conn->role, "client") == 0) {
+               _address += 2 << 24;
+       } else {
+               goto ERROR;
+       }
 
-void createChain(char *chain) {
-       char str[STRING_SIZE];
-       sprintf(str, "/sbin/iptables -N %sINPUT", chain);
-       executeCommand(str);
-       sprintf(str, "/sbin/iptables -N %sFORWARD", chain);
-       executeCommand(str);
+       struct in_addr address_info;
+       address_info.s_addr = _address;
+
+       return inet_ntoa(address_info);
+
+ERROR:
+       fprintf(stderr, "Could not determine transfer net address: %s\n", conn->name);
+
+       free(address);
+       return NULL;
 }
 
-void createAllChains(void) {
-       // create chain and chain references
-       if (!strcmp(enableorange, "on")) {
-               if (strlen(orangeif)) {
-                       createChain(OVPNORANGE);
-                       createChainReference(OVPNORANGE);
-               } else {
-                       fprintf(stderr, "OpenVPN enabled on orange but no orange interface found\n");
-                       //exit(1);
-               }
+char* getLocalSubnetAddress(const connection* conn) {
+       kv = initkeyvalues();
+       if (!readkeyvalues(kv, CONFIG_ROOT "/ethernet/settings")) {
+               fprintf(stderr, "Cannot read ethernet settings\n");
+               exit(1);
        }
 
-       if (!strcmp(enableblue, "on")) {
-               if (strlen(blueif)) {
-                       createChain(OVPNBLUE);
-                       createChainReference(OVPNBLUE);
-               } else {
-                       fprintf(stderr, "OpenVPN enabled on blue but no blue interface found\n");
-                       //exit(1);
-               }
+       const char *zones[] = {"GREEN", "BLUE", "ORANGE", NULL};
+       char *zone = NULL;
+
+       // Get net address of the local openvpn subnet.
+       char *subnetmask = strdup(conn->local_subnet);
+       char *address = strsep(&subnetmask, "/");
+
+       if ((address == NULL) || (subnetmask == NULL)) {
+               goto ERROR;
        }
 
-       if (!strcmp(enablered, "on")) {
-               if (strlen(redif)) {
-                       createChain(OVPNRED);
-                       createChainReference(OVPNRED);
-               } else {
-                       fprintf(stderr, "OpenVPN enabled on red but no red interface found\n");
-                       //exit(1);
+       in_addr_t _address    = inet_addr(address);
+       in_addr_t _subnetmask = inet_addr(subnetmask);
+
+       in_addr_t _netaddr    = (_address &  _subnetmask);
+       in_addr_t _broadcast  = (_address | ~_subnetmask);
+
+       char zone_address_key[STRING_SIZE];
+       char zone_address[STRING_SIZE];
+       in_addr_t zone_addr;
+
+       int i = 0;
+       while (zones[i]) {
+               zone = zones[i++];
+               snprintf(zone_address_key, STRING_SIZE, "%s_ADDRESS", zone);
+
+               if (!findkey(kv, zone_address_key, zone_address))
+                       continue;
+
+               zone_addr = inet_addr(zone_address);
+               if ((zone_addr > _netaddr) && (zone_addr < _broadcast)) {
+                       freekeyvalues(kv);
+
+                       return strdup(zone_address);
                }
        }
+
+ERROR:
+       fprintf(stderr, "Could not determine local subnet address: %s\n", conn->name);
+
+       freekeyvalues(kv);
+       return NULL;
 }
 
 void setFirewallRules(void) {
@@ -339,31 +389,48 @@ void setFirewallRules(void) {
 
        if (!findkey(kv, "VPN_IP", dovpnip)){
                fprintf(stderr, "Cannot read VPN_IP\n");
-//             exit(1); step further as we don't need an ip
        }
        freekeyvalues(kv);
 
-       // read connection configuration
-       connection *conn = getConnections();
-
        // Flush all chains.
-       flushChain(OVPNRED);
-       flushChain(OVPNBLUE);
-       flushChain(OVPNORANGE);
+       flushChain(OVPNINPUT);
+       flushChain(OVPNBLOCK);
+       flushChainNAT(OVPNNAT);
 
        // set firewall rules
        if (!strcmp(enablered, "on") && strlen(redif))
-               setChainRules(OVPNRED, redif, protocol, dport);
+               addRule(OVPNINPUT, redif, protocol, dport);
        if (!strcmp(enableblue, "on") && strlen(blueif))
-               setChainRules(OVPNBLUE, blueif, protocol, dport);
+               addRule(OVPNINPUT, blueif, protocol, dport);
        if (!strcmp(enableorange, "on") && strlen(orangeif))
-               setChainRules(OVPNORANGE, orangeif, protocol, dport);
+               addRule(OVPNINPUT, orangeif, protocol, dport);
+
+       // read connection configuration
+       connection *conn = getConnections();
 
        // set firewall rules for n2n connections
-       char *port;
-       while (conn) {
-               sprintf(port, "%d", conn->port);
-               setChainRules(OVPNRED, redif, conn->proto, port);
+       char command[STRING_SIZE];
+       char *local_subnet_address = NULL;
+       char *transfer_subnet_address = NULL;
+       while (conn != NULL) {
+               if (strcmp(conn->type, "net") == 0) {
+                       addRule(OVPNINPUT, redif, conn->proto, conn->port);
+
+                       /* Block all communication from the transfer nets. */
+                       snprintf(command, STRING_SIZE - 1, "/sbin/iptables -A %s -s %s -j DROP",
+                               OVPNBLOCK, conn->transfer_subnet);
+                       executeCommand(command);
+
+                       local_subnet_address = getLocalSubnetAddress(conn);
+                       transfer_subnet_address = calcTransferNetAddress(conn);
+
+                       if ((local_subnet_address) && (transfer_subnet_address)) {
+                               snprintf(command, STRING_SIZE - 1, "/sbin/iptables -t nat -A %s -s %s -j SNAT --to-source %s",
+                                       OVPNNAT, transfer_subnet_address, local_subnet_address);
+                               executeCommand(command);
+                       }
+               }
+
                conn = conn->next;
        }
 }
@@ -386,7 +453,7 @@ void stopDaemon(void) {
 void startDaemon(void) {
        char command[STRING_SIZE];
        
-       if (!((strcmp(enablered, "on")==0) || (strcmp(enableblue, "on")==0) || (strcmp(enableorange, "on")==0))){
+       if (!((strcmp(enablered, "on") == 0) || (strcmp(enableblue, "on") == 0) || (strcmp(enableorange, "on") == 0))) {
                fprintf(stderr, "OpenVPN is not enabled on any interface\n");
                exit(1);
        } else {
@@ -397,14 +464,14 @@ void startDaemon(void) {
        }
 }
 
-void startNet2Net(char *name) {
+int startNet2Net(char *name) {
        connection *conn = NULL;
        connection *conn_iter;
 
        conn_iter = getConnections();
 
        while (conn_iter) {
-               if (strcmp(conn_iter->name, name) == 0) {
+               if ((strcmp(conn_iter->type, "net") == 0) && (strcmp(conn_iter->name, name) == 0)) {
                        conn = conn_iter;
                        break;
                }
@@ -413,9 +480,16 @@ void startNet2Net(char *name) {
 
        if (conn == NULL) {
                fprintf(stderr, "Connection not found.\n");
-               exit(1);
+               return 1;
        }
 
+       if (strcmp(conn->status, "on") != 0) {
+               fprintf(stderr, "Connection '%s' is not enabled.\n", conn->name);
+               return 1;
+       }
+
+       fprintf(stderr, "Starting connection %s...\n", conn->name);
+
        char configfile[STRING_SIZE];
        snprintf(configfile, STRING_SIZE - 1, CONFIG_ROOT "/ovpn/n2nconf/%s/%s.conf",
                conn->name, conn->name);
@@ -424,19 +498,31 @@ void startNet2Net(char *name) {
        if (fp == NULL) {
                fprintf(stderr, "Could not find configuration file for connection '%s' at '%s'.\n",
                        conn->name, configfile);
-               exit(2);
+               return 2;
        }
        fclose(fp);
 
        // Make sure all firewall rules are up to date.
        setFirewallRules();
 
+       // Get the external IP address.
+       char address[STRING_SIZE] = "";
+       int r = readExternalAddress(address);
+       if (r) {
+               fprintf(stderr, "Could not read the external address\n");
+               exit(1);
+       }
+
        char command[STRING_SIZE];
-       sprintf(command, "/usr/sbin/openvpn --config %s", configfile);
+       snprintf(command, STRING_SIZE-1, "/sbin/modprobe tun");
+       executeCommand(command);
+       snprintf(command, STRING_SIZE-1, "/usr/sbin/openvpn --local %s --config %s", address, configfile);
        executeCommand(command);
+
+       return 0;
 }
 
-void killNet2Net(char *name) {
+int killNet2Net(char *name) {
        connection *conn = NULL;
        connection *conn_iter;
 
@@ -452,7 +538,7 @@ void killNet2Net(char *name) {
 
        if (conn == NULL) {
                fprintf(stderr, "Connection not found.\n");
-               exit(1);
+               return 1;
        }
 
        char pidfile[STRING_SIZE];
@@ -460,13 +546,64 @@ void killNet2Net(char *name) {
 
        int pid = readPidFile(pidfile);
        if (!pid > 0) {
-               exit(1);
+               fprintf(stderr, "Could not read pid file of connection %s.", conn->name);
+               return 1;
        }
 
-       fprintf(stderr, "Killing PID %d.\n", pid);
+       fprintf(stderr, "Killing connection %s (PID %d)...\n", conn->name, pid);
        kill(pid, SIGTERM);
 
-       exit(0);
+       char command[STRING_SIZE];
+       snprintf(command, STRING_SIZE - 1, "/bin/rm -f %s", pidfile);
+       executeCommand(command);
+
+       return 0;
+}
+
+void startAllNet2Net() {
+       int exitcode = 0, _exitcode = 0;
+
+       connection *conn = getConnections();
+
+       while(conn) {
+               /* Skip all connections that are not of type "net" or disabled. */
+               if ((strcmp(conn->type, "net") != 0) || (strcmp(conn->status, "on") != 0)) {
+                       conn = conn->next;
+                       continue;
+               }
+
+               _exitcode = startNet2Net(conn->name);
+               conn = conn->next;
+
+               if (_exitcode > exitcode) {
+                       exitcode = _exitcode;
+               }
+       }
+
+       exit(exitcode);
+}
+
+void killAllNet2Net() {
+       int exitcode = 0, _exitcode = 0;
+
+       connection *conn = getConnections();
+
+       while(conn) {
+               /* Skip all connections that are not of type "net". */
+               if (strcmp(conn->type, "net") != 0) {
+                       conn = conn->next;
+                       continue;
+               }
+
+               _exitcode = killNet2Net(conn->name);
+               conn = conn->next;
+
+               if (_exitcode > exitcode) {
+                       exitcode = _exitcode;
+               }
+       }
+
+       exit(exitcode);
 }
 
 void displayopenvpn(void) {
@@ -483,6 +620,8 @@ int main(int argc, char *argv[]) {
            usage();
 
        if(argc == 3) {
+               ovpnInit();
+
                if( (strcmp(argv[1], "-sn2n") == 0) || (strcmp(argv[1], "--start-net-2-net") == 0) ) {
                        startNet2Net(argv[2]);
                        return 0;
@@ -504,40 +643,33 @@ int main(int argc, char *argv[]) {
                        displayopenvpn();
                        return 0;
                }
-               else if( (strcmp(argv[1], "-dcr") == 0) || (strcmp(argv[1], "--delete-chains-and-rules") == 0) ) {
-                       deleteAllChains();
-                       return 0;
-               }
                else {
                        ovpnInit();
                        
                        if( (strcmp(argv[1], "-s") == 0) || (strcmp(argv[1], "--start") == 0) ) {
-                               deleteAllChains();
-                               createAllChains();
                                setFirewallRules();
                                startDaemon();
                                return 0;
                        }
+                       else if( (strcmp(argv[1], "-sn2n") == 0) || (strcmp(argv[1], "--start-net-2-net") == 0) ) {
+                               startAllNet2Net();
+                               return 0;
+                       }
+                       else if( (strcmp(argv[1], "-kn2n") == 0) || (strcmp(argv[1], "--kill-net-2-net") == 0) ) {
+                               killAllNet2Net();
+                               return 0;
+                       }
                        else if( (strcmp(argv[1], "-sdo") == 0) || (strcmp(argv[1], "--start-daemon-only") == 0) ) {
                                startDaemon();
                                return 0;
                        }
                        else if( (strcmp(argv[1], "-r") == 0) || (strcmp(argv[1], "--restart") == 0) ) {
                                stopDaemon();
-                               deleteAllChains();
-                               createAllChains();
                                setFirewallRules();
                                startDaemon();
                                return 0;
                        }
                        else if( (strcmp(argv[1], "-fwr") == 0) || (strcmp(argv[1], "--firewall-rules") == 0) ) {
-                               deleteAllChains();
-                               createAllChains();
-                               setFirewallRules();
-                               return 0;
-                       }
-                       else if( (strcmp(argv[1], "-ccr") == 0) || (strcmp(argv[1], "--create-chains-and-rules") == 0) ) {
-                               createAllChains();
                                setFirewallRules();
                                return 0;
                        }