From 73348be58ecb9c40cf435122a00e72ac4d1a4c9b Mon Sep 17 00:00:00 2001 From: Guus Sliepen Date: Thu, 2 Aug 2012 17:24:42 +0200 Subject: [PATCH] Have tincctl act as a shell when no command is given. By default it uses readline to read commands. If the input and output are not a tty, no prompt is shown. --- configure.in | 1 + m4/readline.m4 | 41 ++++++++ src/Makefile.am | 2 +- src/tincctl.c | 254 +++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 249 insertions(+), 49 deletions(-) create mode 100644 m4/readline.m4 diff --git a/configure.in b/configure.in index bdac4bab..5f1902bf 100644 --- a/configure.in +++ b/configure.in @@ -182,6 +182,7 @@ dnl These are defined in files in m4/ AC_ARG_WITH(libgcrypt, AC_HELP_STRING([--with-libgcrypt], [enable use of libgcrypt instead of OpenSSL])], []) tinc_CURSES +tinc_READLINE tinc_LIBEVENT tinc_ZLIB tinc_LZO diff --git a/m4/readline.m4 b/m4/readline.m4 new file mode 100644 index 00000000..9c423d4a --- /dev/null +++ b/m4/readline.m4 @@ -0,0 +1,41 @@ +dnl Check to find the readline headers/libraries + +AC_DEFUN([tinc_READLINE], +[ + AC_ARG_ENABLE([readline], + AS_HELP_STRING([--disable-readline], [disable readline support])) + AS_IF([test "x$enable_readline" != "xno"], [ + AC_DEFINE(HAVE_READLINE, 1, [have readline support]) + readline=true + AC_ARG_WITH(readline, + AS_HELP_STRING([--with-readline=DIR], [readline base directory, or:]), + [readline="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib"] + ) + + AC_ARG_WITH(readline-include, + AS_HELP_STRING([--with-readline-include=DIR], [readline headers directory]), + [readline_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval"] + ) + + AC_ARG_WITH(readline-lib, + AS_HELP_STRING([--with-readline-lib=DIR], [readline library directory]), + [readline_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval"] + ) + + AC_CHECK_HEADERS([readline/readline.h readline/history.h], + [], + [AC_MSG_ERROR("readline header files not found."); break] + ) + + AC_CHECK_LIB(readline, readline, + [READLINE_LIBS="-lreadline"], + [AC_MSG_ERROR("readline library not found.")] + ) + ]) + + AC_SUBST(READLINE_LIBS) +]) diff --git a/src/Makefile.am b/src/Makefile.am index 2bd1b6bf..f0a24e0e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -37,7 +37,7 @@ if TUNEMU tincd_SOURCES += bsd/tunemu.c endif -tincctl_LDADD = $(CURSES_LIBS) +tincctl_LDADD = $(CURSES_LIBS) $(READLINE_LIBS) DEFAULT_INCLUDES = diff --git a/src/tincctl.c b/src/tincctl.c index 39bd326d..3b0c614f 100644 --- a/src/tincctl.c +++ b/src/tincctl.c @@ -21,6 +21,11 @@ #include +#ifdef HAVE_READLINE +#include "readline/readline.h" +#include "readline/history.h" +#endif + #include "xalloc.h" #include "protocol.h" #include "control_common.h" @@ -35,6 +40,7 @@ #define mkdir(a, b) mkdir(a) #endif + /* The name this program was run with. */ static char *program_name = NULL; @@ -62,6 +68,7 @@ static int code; static int req; static int result; static bool force = false; +static bool tty = true; #ifdef HAVE_MINGW static struct WSAData wsa_state; @@ -207,14 +214,14 @@ static bool parse_options(int argc, char **argv) { return true; } -static FILE *ask_and_open(const char *filename, const char *what, const char *mode) { +static FILE *ask_and_open(const char *filename, const char *what, const char *mode, bool ask) { FILE *r; char *directory; char buf[PATH_MAX]; char buf2[PATH_MAX]; /* Check stdin and stdout */ - if(isatty(0) && isatty(1)) { + if(ask && tty) { /* Ask for a file and/or directory name. */ fprintf(stdout, "Please enter a file to save %s to [%s]: ", what, filename); @@ -263,7 +270,7 @@ static FILE *ask_and_open(const char *filename, const char *what, const char *mo Generate a public/private ECDSA keypair, and ask for a file to store them in. */ -static bool ecdsa_keygen() { +static bool ecdsa_keygen(bool ask) { ecdsa_t key; FILE *f; char *filename; @@ -277,7 +284,7 @@ static bool ecdsa_keygen() { fprintf(stderr, "Done.\n"); xasprintf(&filename, "%s" SLASH "ecdsa_key.priv", confbase); - f = ask_and_open(filename, "private ECDSA key", "a"); + f = ask_and_open(filename, "private ECDSA key", "a", ask); if(!f) return false; @@ -300,7 +307,7 @@ static bool ecdsa_keygen() { else xasprintf(&filename, "%s" SLASH "ecdsa_key.pub", confbase); - f = ask_and_open(filename, "public ECDSA key", "a"); + f = ask_and_open(filename, "public ECDSA key", "a", ask); if(!f) return false; @@ -322,7 +329,7 @@ static bool ecdsa_keygen() { Generate a public/private RSA keypair, and ask for a file to store them in. */ -static bool rsa_keygen(int bits) { +static bool rsa_keygen(int bits, bool ask) { rsa_t key; FILE *f; char *filename; @@ -336,7 +343,7 @@ static bool rsa_keygen(int bits) { fprintf(stderr, "Done.\n"); xasprintf(&filename, "%s" SLASH "rsa_key.priv", confbase); - f = ask_and_open(filename, "private RSA key", "a"); + f = ask_and_open(filename, "private RSA key", "a", ask); if(!f) return false; @@ -359,7 +366,7 @@ static bool rsa_keygen(int bits) { else xasprintf(&filename, "%s" SLASH "rsa_key.pub", confbase); - f = ask_and_open(filename, "public RSA key", "a"); + f = ask_and_open(filename, "public RSA key", "a", ask); if(!f) return false; @@ -609,13 +616,14 @@ static bool remove_service(void) { } #endif -static bool connect_tincd() { +static bool connect_tincd(bool verbose) { if(fd >= 0) return true; FILE *f = fopen(pidfilename, "r"); if(!f) { - fprintf(stderr, "Could not open pid file %s: %s\n", pidfilename, strerror(errno)); + if(verbose) + fprintf(stderr, "Could not open pid file %s: %s\n", pidfilename, strerror(errno)); return false; } @@ -623,13 +631,18 @@ static bool connect_tincd() { char port[128]; if(fscanf(f, "%20d %1024s %128s port %128s", &pid, controlcookie, host, port) != 4) { - fprintf(stderr, "Could not parse pid file %s\n", pidfilename); + if(verbose) + fprintf(stderr, "Could not parse pid file %s\n", pidfilename); + fclose(f); return false; } + fclose(f); + #ifdef HAVE_MINGW if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) { - fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); + if(verbose) + fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); return false; } #endif @@ -644,13 +657,15 @@ static bool connect_tincd() { struct addrinfo *res = NULL; if(getaddrinfo(host, port, &hints, &res) || !res) { - fprintf(stderr, "Cannot resolve %s port %s: %s", host, port, strerror(errno)); + if(verbose) + fprintf(stderr, "Cannot resolve %s port %s: %s", host, port, strerror(errno)); return false; } fd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP); if(fd < 0) { - fprintf(stderr, "Cannot create TCP socket: %s\n", sockstrerror(sockerrno)); + if(verbose) + fprintf(stderr, "Cannot create TCP socket: %s\n", sockstrerror(sockerrno)); return false; } @@ -658,12 +673,14 @@ static bool connect_tincd() { unsigned long arg = 0; if(ioctlsocket(fd, FIONBIO, &arg) != 0) { - fprintf(stderr, "ioctlsocket failed: %s", sockstrerror(sockerrno)); + if(verbose) + fprintf(stderr, "ioctlsocket failed: %s", sockstrerror(sockerrno)); } #endif if(connect(fd, res->ai_addr, res->ai_addrlen) < 0) { - fprintf(stderr, "Cannot connect to %s port %s: %s\n", host, port, sockstrerror(sockerrno)); + if(verbose) + fprintf(stderr, "Cannot connect to %s port %s: %s\n", host, port, sockstrerror(sockerrno)); return false; } @@ -673,14 +690,16 @@ static bool connect_tincd() { int version; if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %s %d", &code, data, &version) != 3 || code != 0) { - fprintf(stderr, "Cannot read greeting from control socket: %s\n", sockstrerror(sockerrno)); + if(verbose) + fprintf(stderr, "Cannot read greeting from control socket: %s\n", sockstrerror(sockerrno)); return false; } sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT); if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &version, &pid) != 3 || code != 4 || version != TINC_CTL_VERSION_CURRENT) { - fprintf(stderr, "Could not fully establish control socket connection\n"); + if(verbose) + fprintf(stderr, "Could not fully establish control socket connection\n"); return false; } @@ -720,7 +739,7 @@ static int cmd_start(int argc, char *argv[]) { static int cmd_stop(int argc, char *argv[]) { #ifndef HAVE_MINGW - if(!connect_tincd()) { + if(!connect_tincd(true)) { if(pid) { if(kill(pid, SIGTERM)) return 1; @@ -749,7 +768,7 @@ static int cmd_restart(int argc, char *argv[]) { } static int cmd_reload(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d", CONTROL, REQ_RELOAD); @@ -769,7 +788,7 @@ static int cmd_dump(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; bool do_graph = false; @@ -825,7 +844,7 @@ static int cmd_dump(int argc, char *argv[]) { } static int cmd_purge(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d", CONTROL, REQ_PURGE); @@ -843,7 +862,7 @@ static int cmd_debug(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; int debuglevel = atoi(argv[1]); @@ -860,7 +879,7 @@ static int cmd_debug(int argc, char *argv[]) { } static int cmd_retry(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d", CONTROL, REQ_RETRY); @@ -883,7 +902,7 @@ static int cmd_connect(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d %s", CONTROL, REQ_CONNECT, argv[1]); @@ -906,7 +925,7 @@ static int cmd_disconnect(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; sendline(fd, "%d %d %s", CONTROL, REQ_DISCONNECT, argv[1]); @@ -920,7 +939,7 @@ static int cmd_disconnect(int argc, char *argv[]) { static int cmd_top(int argc, char *argv[]) { #ifdef HAVE_CURSES - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; top(fd); @@ -932,7 +951,7 @@ static int cmd_top(int argc, char *argv[]) { } static int cmd_pcap(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; pcap(fd, stdout, argc > 1 ? atoi(argv[1]) : 0); @@ -940,7 +959,7 @@ static int cmd_pcap(int argc, char *argv[]) { } static int cmd_log(int argc, char *argv[]) { - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; logcontrol(fd, stdout, argc > 1 ? atoi(argv[1]) : -1); @@ -948,7 +967,7 @@ static int cmd_log(int argc, char *argv[]) { } static int cmd_pid(int argc, char *argv[]) { - if(!connect_tincd() && !pid) + if(!connect_tincd(true) && !pid) return 1; printf("%d\n", pid); @@ -1216,6 +1235,7 @@ static int cmd_config(int argc, char *argv[]) { tf = fopen(tmpfile, "w"); if(!tf) { fprintf(stderr, "Could not open temporary file %s: %s\n", tmpfile, strerror(errno)); + fclose(f); return 1; } } @@ -1340,9 +1360,7 @@ static int cmd_config(int argc, char *argv[]) { } // Silently try notifying a running tincd of changes. - fclose(stderr); - - if(connect_tincd()) + if(connect_tincd(false)) sendline(fd, "%d %d", CONTROL, REQ_RELOAD); return 0; @@ -1367,7 +1385,7 @@ static int cmd_init(int argc, char *argv[]) { } if(argc < 2) { - if(isatty(0) && isatty(1)) { + if(tty) { char buf[1024]; fprintf(stdout, "Enter the Name you want your tinc node to have: "); fflush(stdout); @@ -1422,8 +1440,7 @@ static int cmd_init(int argc, char *argv[]) { fprintf(f, "Name = %s\n", name); fclose(f); - fclose(stdin); - if(!rsa_keygen(2048) || !ecdsa_keygen()) + if(!rsa_keygen(2048, false) || !ecdsa_keygen(false)) return 1; #ifndef HAVE_MINGW @@ -1446,15 +1463,15 @@ static int cmd_init(int argc, char *argv[]) { } static int cmd_generate_keys(int argc, char *argv[]) { - return !(rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048) && ecdsa_keygen()); + return !(rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true) && ecdsa_keygen(true)); } static int cmd_generate_rsa_keys(int argc, char *argv[]) { - return !rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048); + return !rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true); } static int cmd_generate_ecdsa_keys(int argc, char *argv[]) { - return !ecdsa_keygen(); + return !ecdsa_keygen(true); } static int cmd_help(int argc, char *argv[]) { @@ -1473,7 +1490,7 @@ static int cmd_info(int argc, char *argv[]) { return 1; } - if(!connect_tincd()) + if(!connect_tincd(true)) return 1; return info(fd, argv[1]); @@ -1532,9 +1549,7 @@ static int cmd_edit(int argc, char *argv[]) { return result; // Silently try notifying a running tincd of changes. - fclose(stderr); - - if(connect_tincd()) + if(connect_tincd(false)) sendline(fd, "%d %d", CONTROL, REQ_RELOAD); return 0; @@ -1558,6 +1573,7 @@ static int export(const char *name, FILE *out) { if(ferror(in)) { fprintf(stderr, "Error while reading configuration file %s: %s\n", filename, strerror(errno)); + fclose(in); return 1; } @@ -1700,6 +1716,149 @@ static const struct { {NULL, NULL}, }; +#ifdef HAVE_READLINE +static char *complete_command(const char *text, int state) { + static int i; + + if(!state) + i = 0; + else + i++; + + while(commands[i].command) { + if(!strncasecmp(commands[i].command, text, strlen(text))) + return xstrdup(commands[i].command); + i++; + } + + return NULL; +} + +static char *complete_dump(const char *text, int state) { + const char *matches[] = {"nodes", "edges", "subnets", "connections", "graph", NULL}; + static int i; + + if(!state) + i = 0; + else + i++; + + while(matches[i]) { + if(!strncasecmp(matches[i], text, strlen(text))) + return xstrdup(matches[i]); + i++; + } + + return NULL; +} + +static char **completion (const char *text, int start, int end) { + char **matches = NULL; + + if(!start) + matches = rl_completion_matches(text, complete_command); + else if(!strncasecmp(rl_line_buffer, "dump ", 5)) + matches = rl_completion_matches(text, complete_dump); + + return matches; +} +#endif + +static int cmd_shell(int argc, char *argv[]) { + char *prompt; + xasprintf(&prompt, "%s> ", identname); + int result = 0; + char buf[4096]; + char *line = NULL; + int maxargs = argc + 16; + char **nargv = xmalloc(maxargs * sizeof *nargv); + optind = argc; + + for(int i = 0; i < argc; i++) + nargv[i] = argv[i]; + +#ifdef HAVE_READLINE + rl_readline_name = "tinc"; + rl_attempted_completion_function = completion; + rl_filename_completion_desired = 0; + char *copy = NULL; +#endif + + while(true) { +#ifdef HAVE_READLINE + if(tty) { + free(copy); + free(line); + line = readline(prompt); + if(line) + copy = xstrdup(line); + } else { + line = fgets(buf, sizeof buf, stdin); + } +#else + if(tty) + fputs(stdout, prompt); + + line = fgets(buf, sizeof buf, stdin); +#endif + + if(!line) + break; + + /* Ignore comments */ + + if(*line == '#') + continue; + + /* Split */ + + int nargc = argc; + char *p = line + strspn(line, " \t\n"); + char *next = strtok(p, " \t\n"); + + while(p && *p) { + if(nargc >= maxargs) { + fprintf(stderr, "next %p '%s', p %p '%s'\n", next, next, p, p); + abort(); + maxargs *= 2; + nargv = xrealloc(nargv, maxargs * sizeof *nargv); + } + + nargv[nargc++] = p; + p = next; + next = strtok(NULL, " \t\n"); + } + + if(nargc == argc) + continue; + + bool found = false; + + for(int i = 0; commands[i].command; i++) { + if(!strcasecmp(nargv[argc], commands[i].command)) { + result |= commands[i].function(nargc - argc - 1, nargv + argc + 1); + found = true; + break; + } + } + +#ifdef HAVE_READLINE + if(found) + add_history(copy); +#endif + + if(!found) { + fprintf(stderr, "Unknown command `%s'.\n", nargv[argc]); + result |= 1; + } + } + + if(tty) + printf("\n"); + return result; +} + + int main(int argc, char *argv[]) { program_name = argv[0]; @@ -1718,11 +1877,10 @@ int main(int argc, char *argv[]) { return 0; } - if(optind >= argc) { - fprintf(stderr, "No command given.\n"); - usage(true); - return 1; - } + tty = isatty(0) && isatty(1); + + if(optind >= argc) + return cmd_shell(argc, argv); for(int i = 0; commands[i].command; i++) { if(!strcasecmp(argv[optind], commands[i].command)) -- 2.39.5