gpgme-tool: add a simple socket server (with -s/--server).
authorW. Trevor King <wking@tremily.us>
Wed, 10 Oct 2012 15:13:33 +0000 (11:13 -0400)
committerW. Trevor King <wking@tremily.us>
Thu, 11 Oct 2012 18:12:22 +0000 (14:12 -0400)
src/gpgme-tool.c (socket_path, socket_fd, cleanup_handler,
expand_path): New.
(gpgme_server): Add socket_server argument.

Signed-off-by: W. Trevor King <wking@tremily.us>
src/gpgme-tool.c

index eb1fbb86e25f1b6a6ee48b6db4634f8f4ff02869..556471aaf66bdb1a35ec7b065cf67bde15298155 100644 (file)
 #include <getopt.h>
 #include <ctype.h>
 #include <stdarg.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <wordexp.h>
+#include <unistd.h>
 #ifdef HAVE_LOCALE_H
 #include <locale.h>
 #endif
@@ -620,9 +626,27 @@ peek_membuf (membuf_t *mb, size_t *len)
 /* SUPPORT.  */
 FILE *log_stream;
 char *program_name = "gpgme-tool";
+char *socket_path = NULL;
+static int socket_fd = -1;
 
 #define spacep(p)   (*(p) == ' ' || *(p) == '\t')
 
+void
+cleanup_handler (int signum)
+{
+       if (socket_fd >= 0)
+               {
+                       close(socket_fd);
+                       socket_fd = -1;
+               }
+       if (socket_path)
+               {
+                       unlink(socket_path);
+                       free(socket_path);
+                       socket_path = NULL;
+               }
+       exit(EXIT_SUCCESS);
+}
 
 void log_error (int status, gpg_error_t errnum,
                 const char *fmt, ...) GT_GCC_A_PRINTF(3,4);
@@ -3409,12 +3433,44 @@ register_commands (assuan_context_t ctx)
   return 0;
 }
 
+char *
+expand_path(char *raw_path)
+{
+       wordexp_t words;
+       char *path;
+
+       if (wordexp (raw_path, &words, WRDE_NOCMD | WRDE_SHOWERR))
+               {
+                       /* TODO: check for WRDE_BADCHAR, etc. */
+                       fprintf (log_stream, "could not expand socket path\n");
+                       exit(EXIT_FAILURE);
+               }
+       if (words.we_wordc != 1)
+               {
+                       fprintf (log_stream, "expanded socket path into %d fields\n",
+                                words.we_wordc);
+                       exit(EXIT_FAILURE);
+               }
+       path = malloc (sizeof(char) * (strlen(words.we_wordv[0]) + 1));
+       if (! path)
+               {
+                       perror("can't allocate path");
+                       exit(EXIT_FAILURE);
+               }
+       strcpy (path, words.we_wordv[0]);
+
+       wordfree (&words);
+
+       return path;
+}
 
 /* TODO: password callback can do INQUIRE.  */
 void
-gpgme_server (gpgme_tool_t gt)
+gpgme_server (gpgme_tool_t gt, int socket_server)
 {
   gpg_error_t err;
+  pid_t pid;
+  int sock = -1;
   assuan_fd_t filedes[2];
   struct server server;
   static const char hello[] = ("GPGME-Tool " VERSION " ready");
@@ -3433,25 +3489,88 @@ gpgme_server (gpgme_tool_t gt)
   gt->write_data = server_write_data;
   gt->write_data_hook = &server;
 
-  /* We use a pipe based server so that we can work from scripts.
-     assuan_init_pipe_server will automagically detect when we are
-     called with a socketpair and ignore FIELDES in this case. */
-#ifdef HAVE_W32CE_SYSTEM
-  filedes[0] = ASSUAN_STDIN;
-  filedes[1] = ASSUAN_STDOUT;
-#else
-  filedes[0] = assuan_fdopen (0);
-  filedes[1] = assuan_fdopen (1);
-#endif
   err = assuan_new (&server.assuan_ctx);
   if (err)
     log_error (1, err, "can't create assuan context");
 
   assuan_set_pointer (server.assuan_ctx, &server);
 
-  err = assuan_init_pipe_server (server.assuan_ctx, filedes);
-  if (err)
-    log_error (1, err, "can't initialize assuan server");
+  if (socket_server)
+    {
+      struct sockaddr_un addr;
+
+      socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
+      if (socket_fd == -1)
+      {
+        perror("can't create stream socket");
+        exit(EXIT_FAILURE);
+      }
+
+      addr.sun_family = AF_UNIX;
+                       socket_path = expand_path("~/.gnupg/S.gpgme-tool");
+      strcpy (addr.sun_path, socket_path);
+
+      if (bind (socket_fd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)))
+        {
+          perror("can't bind to stream socket");
+          exit(EXIT_FAILURE);
+        }
+
+      signal(SIGINT, &cleanup_handler);
+      signal(SIGTERM, &cleanup_handler);
+
+      listen(socket_fd, 5);
+
+      for (;;)
+        {
+          sock = accept(socket_fd, NULL, 0);
+          if (sock == -1)
+            {
+              perror("accept");
+              continue;
+            }
+          if ((pid = fork ()) == -1)
+            {
+              perror("fork");
+              close(sock);
+                                                       sock = -1;
+              continue;
+            }
+          else if (pid == 0)
+            {
+              break;  /* child; continue setting up server */
+            }
+          /* parent; continue listening for other connections */
+        }
+
+      /* the child doesn't need cleanup handlers */
+      signal(SIGINT, SIG_DFL);
+      signal(SIGTERM, SIG_DFL);
+
+      err = assuan_init_socket_server (
+        server.assuan_ctx, sock,
+        ASSUAN_SOCKET_SERVER_FDPASSING | ASSUAN_SOCKET_SERVER_ACCEPTED);
+      if (err)
+        log_error (1, err, "can't initialize assuan server");
+    }
+  else
+    {
+      /* We use a pipe based server so that we can work from scripts.
+         assuan_init_pipe_server will automagically detect when we are
+         called with a socketpair and ignore FIELDES in this case. */
+#ifdef HAVE_W32CE_SYSTEM
+      filedes[0] = ASSUAN_STDIN;
+      filedes[1] = ASSUAN_STDOUT;
+#else
+      filedes[0] = assuan_fdopen (0);
+      filedes[1] = assuan_fdopen (1);
+#endif
+
+      err = assuan_init_pipe_server (server.assuan_ctx, filedes);
+      if (err)
+        log_error (1, err, "can't initialize assuan server");
+    }
+
   err = register_commands (server.assuan_ctx);
   if (err)
     log_error (1, err, "can't register assuan commands");
@@ -3494,7 +3613,7 @@ static char doc[] = "GPGME Tool -- Assuan server exposing GPGME operations";
 static char args_doc[] = "COMMAND [OPTIONS...]";
 
 static struct argp_option options[] = {
-  { "server", 's', 0, 0, "Server mode" },
+  { "server", 's', 0, 0, "Socket server mode (the default is a pipe server)" },
   { 0 }
 };
 
@@ -3569,9 +3688,11 @@ main (int argc, char *argv[])
 
   switch (args.cmd)
     {
-    case CMD_DEFAULT:
     case CMD_SERVER:
-      gpgme_server (&gt);
+      gpgme_server (&gt, 1);
+      break;
+    case CMD_DEFAULT:
+      gpgme_server (&gt, 0);
       break;
     }