Generate a tinc-up script from an invitation.
authorGuus Sliepen <guus@tinc-vpn.org>
Sat, 16 Apr 2016 23:13:27 +0000 (01:13 +0200)
committerGuus Sliepen <guus@tinc-vpn.org>
Sat, 16 Apr 2016 23:13:27 +0000 (01:13 +0200)
This adds the ability for an invitation to provision an invitee with a
tinc-up script. This is quite strictly controlled; only address configuration
and routes are supported by adding "Ifconfig" and "Route" statements to
the invitation file. The "tinc join" command will generate a tinc-up script
from those statements, and will ask before enabling the tinc-up script.

src/Makefile.am
src/ifconfig.c [new file with mode: 0644]
src/ifconfig.h [new file with mode: 0644]
src/invitation.c

index f54b476..200c71f 100644 (file)
@@ -104,6 +104,7 @@ tincd_SOURCES = \
 tinc_SOURCES = \
        dropin.c dropin.h \
        fsck.c fsck.h \
+       ifconfig.c ifconfig.h \
        info.c info.h \
        invitation.c invitation.h \
        list.c list.h \
diff --git a/src/ifconfig.c b/src/ifconfig.c
new file mode 100644 (file)
index 0000000..d063b17
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+    ifconfig.c -- Generate platform specific interface configuration commands
+    Copyright (C) 2016 Guus Sliepen <guus@tinc-vpn.org>
+
+    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; either version 2 of the License, or
+    (at your option) any later version.
+
+    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.
+*/
+
+#include "system.h"
+
+#include "conf.h"
+#include "ifconfig.h"
+#include "subnet.h"
+
+static long start;
+
+#ifndef HAVE_MINGW
+void ifconfig_header(FILE *out) {
+       fprintf(out, "#!/bin/sh\n");
+       start = ftell(out);
+}
+
+void ifconfig_dhcp(FILE *out) {
+       fprintf(out, "dhclient -nw \"$INTERFACE\"\n");
+}
+
+void ifconfig_dhcp6(FILE *out) {
+       fprintf(out, "dhclient -6 -nw \"$INTERFACE\"\n");
+}
+
+void ifconfig_slaac(FILE *out) {
+#ifdef HAVE_LINUX
+       fprintf(out, "echo 1 >\"/proc/sys/net/ipv6/conf/$INTERFACE/accept_ra\"\n");
+       fprintf(out, "echo 1 >\"/proc/sys/net/ipv6/conf/$INTERFACE/autoconf\"\n");
+#else
+       fprintf(out, "rtsol \"$INTERFACE\" &\n");
+#endif
+}
+
+bool ifconfig_footer(FILE *out) {
+       if(ftell(out) == start) {
+               fprintf(out, "echo 'Unconfigured tinc-up script, please edit '$0'!'\n\n#ifconfig $INTERFACE <your vpn IP address> netmask <netmask of whole VPN>\n");
+               return false;
+       } else {
+#ifdef HAVE_LINUX
+               fprintf(out, "ip link set \"$INTERFACE\" up\n");
+#else
+               fprintf(out, "ifconfig \"$INTERFACE\" up\n");
+#endif
+               return true;
+       }
+}
+#else
+void ifconfig_header(FILE *out) {
+       start = ftell(out);
+}
+
+void ifconfig_dhcp(FILE *out) {
+       fprintf(out, "netsh interface ipv4 set address \"%INTERFACE%\" dhcp\n");
+}
+
+void ifconfig_dhcp6(FILE *out) {
+       fprintf(stderr, "DHCPv6 requested, but not supported by tinc on this platform\n");
+}
+
+void ifconfig_slaac(FILE *out) {
+       // It's the default?
+}
+
+bool ifconfig_footer(FILE *out) {
+       return ftell(out) != start;
+}
+#endif
+
+static subnet_t ipv4, ipv6;
+
+void ifconfig_address(FILE *out, const char *value) {
+       subnet_t subnet = {};
+       char str[MAXNETSTR];
+       if(!str2net(&subnet, value) || !net2str(str, sizeof str, &subnet)) {
+               fprintf(stderr, "Could not parse Ifconfig statement\n");
+               return;
+       }
+       switch(subnet.type) {
+               case SUBNET_IPV4: ipv4 = subnet; break;
+               case SUBNET_IPV6: ipv6 = subnet; break;
+       }
+#if defined(HAVE_LINUX)
+       switch(subnet.type) {
+               case SUBNET_MAC:  fprintf(out, "ip link set \"$INTERFACE\" address %s\n", str); break;
+               case SUBNET_IPV4: fprintf(out, "ip addr replace %s dev \"$INTERFACE\"\n", str); break;
+               case SUBNET_IPV6: fprintf(out, "ip addr replace %s dev \"$INTERFACE\"\n", str); break;
+       }
+#elif defined(HAVE_BSD)
+       switch(subnet.type) {
+               case SUBNET_MAC:  fprintf(out, "ifconfig \"$INTERFACE\" link %s\n", str); break;
+               case SUBNET_IPV4: fprintf(out, "ifconfig \"$INTERFACE\" %s\n", str); break;
+               case SUBNET_IPV6: fprintf(out, "ifconfig \"$INTERFACE\" inet6 %s\n", str); break;
+       }
+#elif defined(HAVE_MINGW) || defined(HAVE_CYGWIN)
+       switch(subnet.type) {
+               case SUBNET_MAC:  fprintf(out, "ip link set \"$INTERFACE\" address %s\n", str); break;
+               case SUBNET_IPV4: fprintf(out, "netsh inetface ipv4 set address \"$INTERFACE\" static %s\n", str); break;
+               case SUBNET_IPV6: fprintf(out, "netsh inetface ipv6 set address \"$INTERFACE\" static %s\n", str); break;
+       }
+#endif
+}
+
+void ifconfig_route(FILE *out, const char *value) {
+       subnet_t subnet = {};
+       char str[MAXNETSTR];
+       if(!str2net(&subnet, value) || !net2str(str, sizeof str, &subnet) || subnet.type == SUBNET_MAC) {
+               fprintf(stderr, "Could not parse Ifconfig statement\n");
+               return;
+       }
+#if defined(HAVE_LINUX)
+       switch(subnet.type) {
+               case SUBNET_IPV4: fprintf(out, "ip route add %s dev \"$INTERFACE\"\n", str); break;
+               case SUBNET_IPV6: fprintf(out, "ip route add %s dev \"$INTERFACE\"\n", str); break;
+       }
+#elif defined(HAVE_BSD)
+       // BSD route command is silly and doesn't accept an interface name as a destination.
+       char gwstr[MAXNETSTR] = "";
+       switch(subnet.type) {
+               case SUBNET_IPV4:
+                       if(!ipv4.type) {
+                               fprintf(stderr, "Route requested but no Ifconfig\n");
+                               return;
+                       }
+                       net2str(gwstr, sizeof gwstr, &ipv4);
+                       char *p = strchr(gwstr, '/'); if(p) *p = 0;
+                       fprintf(out, "route add %s %s\n", str, gwstr);
+                       break;
+               case SUBNET_IPV6:
+                       if(!ipv6.type) {
+                               fprintf(stderr, "Route requested but no Ifconfig\n");
+                               return;
+                       }
+                       net2str(gwstr, sizeof gwstr, &ipv6);
+                       char *p = strchr(gwstr, '/'); if(p) *p = 0;
+                       fprintf(out, "route add -inet6 %s %s\n", str, gwstr);
+                       break;
+       }
+#elif defined(HAVE_MINGW) || defined(HAVE_CYGWIN)
+       switch(subnet.type) {
+               case SUBNET_IPV4: fprintf(out, "netsh inetface ipv4 add route %s \"$INTERFACE\"\n", str); break;
+               case SUBNET_IPV6: fprintf(out, "netsh inetface ipv6 add route %s \"$INTERFACE\"\n", str); break;
+       }
+#endif
+}
diff --git a/src/ifconfig.h b/src/ifconfig.h
new file mode 100644 (file)
index 0000000..3dbf9f6
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+    ifconfig.h -- header for ifconfig.c.
+    Copyright (C) 2016 Guus Sliepen <guus@tinc-vpn.org>
+
+    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; either version 2 of the License, or
+    (at your option) any later version.
+
+    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.
+*/
+
+#ifndef __TINC_IFCONFIG_H__
+#define __TINC_IFCONFIG_H__
+
+extern void ifconfig_dhcp(FILE *out);
+extern void ifconfig_dhcp6(FILE *out);
+extern void ifconfig_slaac(FILE *out);
+extern void ifconfig_address(FILE *out, const char *value);
+extern void ifconfig_route(FILE *out, const char *value);
+extern void ifconfig_header(FILE *out);
+extern bool ifconfig_footer(FILE *out);
+
+#endif
index 3102e41..07594ff 100644 (file)
 #include "crypto.h"
 #include "ecdsa.h"
 #include "ecdsagen.h"
+#include "ifconfig.h"
 #include "invitation.h"
 #include "names.h"
 #include "netutl.h"
 #include "rsagen.h"
 #include "script.h"
 #include "sptps.h"
+#include "subnet.h"
 #include "tincctl.h"
 #include "utils.h"
 #include "xalloc.h"
@@ -602,7 +604,20 @@ make_names:
                return false;
        }
 
+       snprintf(filename, sizeof filename, "%s" SLASH "tinc-up.invitation", confbase);
+       FILE *fup = fopen(filename, "w");
+       if(!fup) {
+               fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno));
+               fclose(f);
+               fclose(fh);
+               return false;
+       }
+
+       fprintf(fup, "#!/bin/sh\n");
+       long fuppos = ftell(fup);
+
        // Filter first chunk on approved keywords, split between tinc.conf and hosts/Name
+       // Generate a tinc-up script from Ifconfig and Route keywords.
        // Other chunks go unfiltered to their respective host config files
        const char *p = data;
        char *l, *value;
@@ -641,6 +656,24 @@ make_names:
                        break;
                }
 
+               // Handle Ifconfig and Route statements
+               if(!found) {
+                       if(!strcasecmp(l, "Ifconfig")) {
+                               if(!strcasecmp(value, "dhcp"))
+                                       ifconfig_dhcp(fup);
+                               else if(!strcasecmp(value, "dhcp6"))
+                                       ifconfig_dhcp6(fup);
+                               else if(!strcasecmp(value, "slaac"))
+                                       ifconfig_slaac(fup);
+                               else
+                                       ifconfig_address(fup, value);
+                               continue;
+                       } else if(!strcasecmp(l, "Route")) {
+                               ifconfig_route(fup, value);
+                               continue;
+                       }
+               }
+
                // Ignore unknown and unsafe variables
                if(!found) {
                        fprintf(stderr, "Ignoring unknown variable '%s' in invitation.\n", l);
@@ -655,6 +688,8 @@ make_names:
        }
 
        fclose(f);
+       bool valid_tinc_up = ifconfig_footer(fup);
+       fclose(fup);
 
        while(l && !strcasecmp(l, "Name")) {
                if(!check_id(value)) {
@@ -769,6 +804,60 @@ ask_netname:
                make_names(false);
        }
 
+       char filename2[PATH_MAX];
+       snprintf(filename, sizeof filename, "%s" SLASH "tinc-up.invitation", confbase);
+       snprintf(filename2, sizeof filename2, "%s" SLASH "tinc-up", confbase);
+
+       if(valid_tinc_up) {
+               if(tty) {
+                       FILE *fup = fopen(filename, "r");
+                       if(fup) {
+                               fprintf(stderr, "\nPlease review the following tinc-up script:\n\n");
+
+                               char buf[MAXSIZE];
+                               while(fgets(buf, sizeof buf, fup))
+                                       fputs(buf, stderr);
+                               fclose(fup);
+
+                               int response = 0;
+                               do {
+                                       fprintf(stderr, "\nDo you want to use this script [y]es/[n]o/[e]dit? ");
+                                       response = tolower(getchar());
+                               } while(!strchr("yne", response));
+
+                               fprintf(stderr, "\n");
+
+                               if(response == 'e') {
+                                       char *command;
+#ifndef HAVE_MINGW
+                                       xasprintf(&command, "\"%s\" \"%s\"", getenv("VISUAL") ?: getenv("EDITOR") ?: "vi", filename);
+#else
+                                       xasprintf(&command, "edit \"%s\"", filename);
+#endif
+                                       if(system(command))
+                                               response = 'n';
+                                       else
+                                               response = 'y';
+                                       free(command);
+                               }
+
+                               if(response == 'y') {
+                                       rename(filename, filename2);
+                                       chmod(filename2, 0755);
+                                       fprintf(stderr, "tinc-up enabled.\n");
+                               } else {
+                                       fprintf(stderr, "tinc-up has been left disabled.\n");
+                               }
+                       }
+               } else {
+                       fprintf(stderr, "A tinc-up script was generated, but has been left disabled.\n");
+               }
+       } else {
+               // A placeholder was generated.
+               rename(filename, filename2);
+               chmod(filename2, 0755);
+       }
+
        fprintf(stderr, "Configuration stored in: %s\n", confbase);
 
        return true;