Allow configuration variables to be added/removed using tincctl.
authorGuus Sliepen <guus@tinc-vpn.org>
Sun, 15 Jul 2012 16:16:35 +0000 (18:16 +0200)
committerGuus Sliepen <guus@tinc-vpn.org>
Sun, 15 Jul 2012 16:16:35 +0000 (18:16 +0200)
doc/tinc.texi
doc/tincctl.8.in
src/tincctl.c

index 3b312a3..2dbfdfb 100644 (file)
@@ -2044,6 +2044,18 @@ the value of this environment variable is used.
 Create initial configuration files and RSA and ECDSA keypairs with default length.
 If no @var{name} for this node is given, it will be asked for.
 
 Create initial configuration files and RSA and ECDSA keypairs with default length.
 If no @var{name} for this node is given, it will be asked for.
 
+@item config [set] @var{variable} @var{value}
+Set configuration variable @var{variable} to the given @var{value}.
+All previously existing configuration variables with the same name are removed.
+To set a variable for a specific host, use the notation @var{host}.@var{variable}.
+
+@item config add @var{variable} @var{value}
+As above, but without removing any previously existing configuration variables.
+
+@item config del @var{variable} [@var{value}]
+Remove configuration variables with the same name and @var{value}.
+If no @var{value} is given, all configuration variables with the same name will be removed.
+
 @item start
 Start @samp{tincd}.
 
 @item start
 Start @samp{tincd}.
 
@@ -2126,6 +2138,15 @@ tincctl -n vpn pcap | tcpdump -r -
 tincctl -n vpn top
 @end example
 
 tincctl -n vpn top
 @end example
 
+Example of configuring tinc using tincctl:
+
+@example
+tincctl -n vpn init foo
+tincctl -n vpn config Subnet 192.168.1.0/24
+tincctl -n vpn config bar.Address bar.example.com
+tincctl -n vpn config ConnectTo bar
+@end example
+
 @c ==================================================================
 @node    tincctl top
 @section tincctl top
 @c ==================================================================
 @node    tincctl top
 @section tincctl top
index 751e16f..9f11e11 100644 (file)
@@ -52,6 +52,22 @@ Create initial configuration files and RSA and ECDSA keypairs with default lengt
 If no
 .Ar name
 for this node is given, it will be asked for.
 If no
 .Ar name
 for this node is given, it will be asked for.
+.It config Oo set Oc Ar variable Ar value
+Set configuration variable
+.Ar variable
+to the given
+.Ar value .
+All previously existing configuration variables with the same name are removed.
+To set a variable for a specific host, use the notation
+.Ar host Ns Li . Ns Ar variable .
+.It config add Ar variable Ar value
+As above, but without removing any previously existing configuration variables.
+.It config del Ar variable Op Ar value
+Remove configuration variables with the same name and
+.Ar value .
+If no
+.Ar value
+is given, all configuration variables with the same name will be removed.
 .It start
 Start
 .Xr tincd 8 .
 .It start
 Start
 .Xr tincd 8 .
@@ -139,7 +155,15 @@ Examples of some commands:
 tincctl -n vpn dump graph | circo -Txlib
 tincctl -n vpn pcap | tcpdump -r -
 tincctl -n vpn top
 tincctl -n vpn dump graph | circo -Txlib
 tincctl -n vpn pcap | tcpdump -r -
 tincctl -n vpn top
+.Pp
 .Ed
 .Ed
+Example of configuring tinc using
+.Nm :
+.Bd -literal -offset indent
+tincctl -n vpn init foo
+tincctl -n vpn config Subnet 192.168.1.0/24
+tincctl -n vpn config bar.Address bar.example.com
+tincctl -n vpn config ConnectTo bar
 .Sh TOP
 The top command connects to a running tinc daemon and repeatedly queries its per-node traffic counters.
 It displays a list of all the known nodes in the left-most column,
 .Sh TOP
 The top command connects to a running tinc daemon and repeatedly queries its per-node traffic counters.
 It displays a list of all the known nodes in the left-most column,
index 1fe4fff..46688f3 100644 (file)
@@ -45,6 +45,8 @@ static char *pidfilename = NULL;                      /* pid file location */
 static char controlcookie[1024];
 char *netname = NULL;
 char *confbase = NULL;
 static char controlcookie[1024];
 char *netname = NULL;
 char *confbase = NULL;
+static char *tinc_conf = NULL;
+static char *hosts_dir = NULL;
 
 // Horrible global variables...
 static int pid = 0;
 
 // Horrible global variables...
 static int pid = 0;
@@ -92,6 +94,10 @@ static void usage(bool status) {
                                "\n"
                                "Valid commands are:\n"
                                "  init [name]                Create initial configuration files.\n"
                                "\n"
                                "Valid commands are:\n"
                                "  init [name]                Create initial configuration files.\n"
+                               "  config                     Change configuration:\n"
+                               "    [set] VARIABLE VALUE     - set VARIABLE to VALUE\n"
+                               "    add VARIABLE VALUE       - add VARIABLE with the given VALUE\n"
+                               "    del VARIABLE [VALUE]     - remove VARIABLE [only ones with watching VALUE]\n"
                                "  start                      Start tincd.\n"
                                "  stop                       Stop tincd.\n"
                                "  restart                    Restart tincd.\n"
                                "  start                      Start tincd.\n"
                                "  stop                       Stop tincd.\n"
                                "  restart                    Restart tincd.\n"
@@ -363,9 +369,9 @@ static void make_names(void) {
                if(!pidfilename)
                        xasprintf(&pidfilename, "%s/pid", confbase);
                RegCloseKey(key);
                if(!pidfilename)
                        xasprintf(&pidfilename, "%s/pid", confbase);
                RegCloseKey(key);
-               if(*installdir)
-                       return;
        }
        }
+
+       if(!*installdir) {
 #endif
 
        if(!pidfilename)
 #endif
 
        if(!pidfilename)
@@ -380,6 +386,13 @@ static void make_names(void) {
                if(!confbase)
                        xasprintf(&confbase, CONFDIR "/tinc");
        }
                if(!confbase)
                        xasprintf(&confbase, CONFDIR "/tinc");
        }
+
+#ifdef HAVE_MINGW
+       }
+#endif
+
+       xasprintf(&tinc_conf, "%s/tinc.conf", confbase);
+       xasprintf(&hosts_dir, "%s/hosts", confbase);
 }
 
 static char buffer[4096];
 }
 
 static char buffer[4096];
@@ -886,15 +899,265 @@ static int cmd_pid(int argc, char *argv[]) {
        return 0;
 }
 
        return 0;
 }
 
+static int rstrip(char *value) {
+       int len = strlen(value);
+       while(len && strchr("\t\r\n ", value[len - 1]))
+               value[--len] = 0;
+       return len;
+}
+
+static char *get_my_name() {
+       FILE *f = fopen(tinc_conf, "r");
+       if(!f) {
+               fprintf(stderr, "Could not open %s: %s\n", tinc_conf, strerror(errno));
+               return NULL;
+       }
+
+       char buf[4096];
+       char *value;
+       while(fgets(buf, sizeof buf, f)) {
+               int len = strcspn(buf, "\t =");
+               value = buf + len;
+               value += strspn(value, "\t ");
+               if(*value == '=') {
+                       value++;
+                       value += strspn(value, "\t ");
+               }
+               if(!rstrip(value))
+                       continue;
+               buf[len] = 0;
+               if(strcasecmp(buf, "Name"))
+                       continue;
+               if(*value) {
+                       fclose(f);
+                       return strdup(value);
+               }
+       }
+
+       fclose(f);
+       fprintf(stderr, "Could not find Name in %s.\n", tinc_conf);
+       return NULL;
+}
+
+static char *hostvariables[] = {
+       "Address",
+       "Port",
+       "PublicKey",
+       "Subnet",
+       NULL,
+};
+
+static int cmd_config(int argc, char *argv[]) {
+       if(argc < 2) {
+               fprintf(stderr, "Invalid number of arguments.\n");
+               return 1;
+       }
+
+       int action = 0;
+       if(!strcasecmp(argv[1], "add")) {
+               argv++, argc--, action = 1;
+       } else if(!strcasecmp(argv[1], "del")) {
+               argv++, argc--, action = -1;
+       } else if(!strcasecmp(argv[1], "replace") || !strcasecmp(argv[1], "set") || !strcasecmp(argv[1], "change")) {
+               argv++, argc--, action = 0;
+       }
+
+       if(argc < 2) {
+               fprintf(stderr, "Invalid number of arguments.\n");
+               return 1;
+       }
+
+       // Concatenate the rest of the command line
+       strncpy(line, argv[1], sizeof line - 1);
+       for(int i = 2; i < argc; i++) {
+               strncat(line, " ", sizeof line - 1 - strlen(line));
+               strncat(line, argv[i], sizeof line - 1 - strlen(line));
+       }
+
+       // Liberal parsing into node name, variable name and value.
+       char *node = NULL;
+       char *variable;
+       char *value;
+       int len;
+
+        len = strcspn(line, "\t =");
+        value = line + len;
+        value += strspn(value, "\t ");
+        if(*value == '=') {
+                value++;
+                value += strspn(value, "\t ");
+        }
+        line[len] = '\0';
+       variable = strchr(line, '.');
+       if(variable) {
+               node = line;
+               *variable++ = 0;
+       } else {
+               variable = line;
+       }
+
+       if(!*variable) {
+               fprintf(stderr, "No variable given.\n");
+               return 1;
+       }
+
+       if(action >= 0 && !*value) {
+               fprintf(stderr, "No value for variable given.\n");
+               return 1;
+       }
+
+       // Should this go into our own host config file?
+       if(!node) {
+               for(int i = 0; hostvariables[i]; i++) {
+                       if(!strcasecmp(hostvariables[i], variable)) {
+                               node = get_my_name();
+                               if(!node)
+                                       return 1;
+                               break;
+                       }
+               }
+       }
+
+       // Open the right configuration file.
+       char *filename;
+       if(node)
+               xasprintf(&filename, "%s/%s", hosts_dir, node);
+       else
+               filename = tinc_conf;
+
+       FILE *f = fopen(filename, "r");
+       if(!f) {
+               if(action < 0 || errno != ENOENT) {
+                       fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno));
+                       return 1;
+               }
+
+               // If it doesn't exist, create it.
+               f = fopen(filename, "a+");
+               if(!f) {
+                       fprintf(stderr, "Could not create configuration file %s: %s\n", filename, strerror(errno));
+                       return 1;
+               } else {
+                       fprintf(stderr, "Created configuration file %s.\n", filename);
+               }
+       }
+
+       char *tmpfile;
+       xasprintf(&tmpfile, "%s.config.tmp", filename);
+       FILE *tf = fopen(tmpfile, "w");
+       if(!tf) {
+               fprintf(stderr, "Could not open temporary file %s: %s\n", tmpfile, strerror(errno));
+               return 1;
+       }
+
+       // Copy the file, making modifications on the fly.
+       char buf1[4096];
+       char buf2[4096];
+       bool set = false;
+       bool removed = false;
+
+       while(fgets(buf1, sizeof buf1, f)) {
+               buf1[sizeof buf1 - 1] = 0;
+               strcpy(buf2, buf1);
+
+               // Parse line in a simple way
+               char *bvalue;
+               int len;
+
+               len = strcspn(buf2, "\t =");
+               bvalue = buf2 + len;
+               bvalue += strspn(bvalue, "\t ");
+               if(*bvalue == '=') {
+                       bvalue++;
+                       bvalue += strspn(bvalue, "\t ");
+               }
+               rstrip(bvalue);
+               buf2[len] = '\0';
+
+               // Did it match?
+               if(!strcasecmp(buf2, variable)) {
+                       // Del
+                       if(action < 0) {
+                               if(!*value || !strcasecmp(bvalue, value)) {
+                                       removed = true;
+                                       continue;
+                               }
+                       // Set
+                       } else if(action == 0) {
+                               // Already set? Delete the rest...
+                               if(set)
+                                       continue;
+                               // Otherwise, replace.
+                               if(fprintf(tf, "%s = %s\n", variable, value) < 0) {
+                                       fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
+                                       return 1;
+                               }
+                               set = true;
+                               continue;
+                       }
+               }
+
+               // Copy original line...
+               if(fputs(buf1, tf) < 0) {
+                       fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
+                       return 1;
+               }
+       }
+
+       // Make sure we read everything...
+       if(ferror(f) || !feof(f)) {
+               fprintf(stderr, "Error while reading from configuration file %s: %s\n", filename, strerror(errno));
+               return 1;
+       }
+
+       if(fclose(f)) {
+               fprintf(stderr, "Error closing configuration file %s: %s\n", filename, strerror(errno));
+               return 1;
+       }
+
+       // Add new variable if necessary.
+       if(action > 0 || (action == 0 && !set)) {
+               if(fprintf(tf, "%s = %s\n", variable, value) < 0) {
+                       fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno));
+                       return 1;
+               }
+       }
+
+       // Make sure we wrote everything...
+       if(fclose(tf)) {
+               fprintf(stderr, "Error closing temporary file %s: %s\n", tmpfile, strerror(errno));
+               return 1;
+       }
+
+       // Could we find what we had to remove?
+       if(action < 0 && !removed) {
+               remove(tmpfile);
+               fprintf(stderr, "No configuration variables deleted.\n");
+               return *value;
+       }
+
+       // Replace the configuration file with the new one
+#ifdef HAVE_MINGW
+       if(remove(filename)) {
+               fprintf(stderr, "Error replacing file %s: %s\n", filename, strerror(errno));
+               return 1;
+       }
+#endif
+       if(rename(tmpfile, filename)) {
+               fprintf(stderr, "Error renaming temporary file %s to configuration file %s: %s\n", tmpfile, filename, strerror(errno));
+               return 1;
+       }
+
+       return 0;
+}
+
 static int cmd_init(int argc, char *argv[]) {
 static int cmd_init(int argc, char *argv[]) {
-       char *tinc_conf = NULL;
-       xasprintf(&tinc_conf, "%s/tinc.conf", confbase);
-       if(!access(confbase, F_OK)) {
+       if(!access(tinc_conf, F_OK)) {
                fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
                return 1;
        }
 
                fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf);
                return 1;
        }
 
-       if(optind >= argc - 1) {
+       if(argc < 2) {
                if(isatty(0) && isatty(1)) {
                        char buf[1024];
                        fprintf(stdout, "Enter the Name you want your tinc node to have: ");
                if(isatty(0) && isatty(1)) {
                        char buf[1024];
                        fprintf(stdout, "Enter the Name you want your tinc node to have: ");
@@ -903,9 +1166,7 @@ static int cmd_init(int argc, char *argv[]) {
                                fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
                                return 1;
                        }
                                fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno));
                                return 1;
                        }
-                       int len = strlen(buf);
-                       if(len)
-                               buf[--len] = 0;
+                       int len = rstrip(buf);
                        if(!len) {
                                fprintf(stderr, "No name given!\n");
                                return 1;
                        if(!len) {
                                fprintf(stderr, "No name given!\n");
                                return 1;
@@ -916,7 +1177,7 @@ static int cmd_init(int argc, char *argv[]) {
                        return 1;
                }
        } else {
                        return 1;
                }
        } else {
-               name = strdup(argv[optind + 1]);
+               name = strdup(argv[1]);
                if(!*name) {
                        fprintf(stderr, "No Name given!\n");
                        return 1;
                if(!*name) {
                        fprintf(stderr, "No Name given!\n");
                        return 1;
@@ -1004,6 +1265,7 @@ static const struct {
        {"pcap", cmd_pcap},
        {"log", cmd_log},
        {"pid", cmd_pid},
        {"pcap", cmd_pcap},
        {"log", cmd_log},
        {"pid", cmd_pid},
+       {"config", cmd_config},
        {"init", cmd_init},
        {"generate-keys", cmd_generate_keys},
        {"generate-rsa-keys", cmd_generate_rsa_keys},
        {"init", cmd_init},
        {"generate-keys", cmd_generate_keys},
        {"generate-rsa-keys", cmd_generate_rsa_keys},