From 3273e3254107a4b89cd9963012d5fac8927c417c Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Sun, 17 Apr 2016 01:13:27 +0200 Subject: [PATCH] Generate a tinc-up script from an invitation. 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 | 1 + src/ifconfig.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++ src/ifconfig.h | 31 +++++++++ src/invitation.c | 89 ++++++++++++++++++++++++++ 4 files changed, 282 insertions(+) create mode 100644 src/ifconfig.c create mode 100644 src/ifconfig.h diff --git a/src/Makefile.am b/src/Makefile.am index f54b476f..200c71f5 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 index 00000000..d063b17b --- /dev/null +++ b/src/ifconfig.c @@ -0,0 +1,161 @@ +/* + ifconfig.c -- Generate platform specific interface configuration commands + Copyright (C) 2016 Guus Sliepen + + 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 netmask \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 index 00000000..3dbf9f68 --- /dev/null +++ b/src/ifconfig.h @@ -0,0 +1,31 @@ +/* + ifconfig.h -- header for ifconfig.c. + Copyright (C) 2016 Guus Sliepen + + 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 diff --git a/src/invitation.c b/src/invitation.c index 3102e416..07594ff6 100644 --- a/src/invitation.c +++ b/src/invitation.c @@ -23,12 +23,14 @@ #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; -- 2.20.1