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.
 
+@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}.
 
@@ -2126,6 +2138,15 @@ tincctl -n vpn pcap | tcpdump -r -
 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
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.
+.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 .
@@ -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
+.Pp
 .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,
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 *tinc_conf = NULL;
+static char *hosts_dir = NULL;
 
 // 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"
+                               "  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"
@@ -363,9 +369,9 @@ static void make_names(void) {
                if(!pidfilename)
                        xasprintf(&pidfilename, "%s/pid", confbase);
                RegCloseKey(key);
-               if(*installdir)
-                       return;
        }
+
+       if(!*installdir) {
 #endif
 
        if(!pidfilename)
@@ -380,6 +386,13 @@ static void make_names(void) {
                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];
@@ -886,15 +899,265 @@ static int cmd_pid(int argc, char *argv[]) {
        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[]) {
-       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;
        }
 
-       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: ");
@@ -903,9 +1166,7 @@ static int cmd_init(int argc, char *argv[]) {
                                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;
@@ -916,7 +1177,7 @@ static int cmd_init(int argc, char *argv[]) {
                        return 1;
                }
        } else {
-               name = strdup(argv[optind + 1]);
+               name = strdup(argv[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},
+       {"config", cmd_config},
        {"init", cmd_init},
        {"generate-keys", cmd_generate_keys},
        {"generate-rsa-keys", cmd_generate_rsa_keys},