Fix bug 901.
[gnupg.git] / tools / gpg-connect-agent.c
index 57eed64..92a6e70 100644 (file)
@@ -1,5 +1,5 @@
 /* gpg-connect-agent.c - Tool to connect to the agent.
- *     Copyright (C) 2005, 2007 Free Software Foundation, Inc.
+ *     Copyright (C) 2005, 2007, 2008 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
 #include "../common/sysutils.h"
 #include "../common/membuf.h"
 #include "../common/ttyio.h"
+#ifdef HAVE_W32_SYSTEM
+#  include "../common/exechelp.h"
+#endif
+
 
 #define CONTROL_D ('D' - 'A' + 1)
 #define octdigitp(p) (*(p) >= '0' && *(p) <= '7')
@@ -59,25 +63,28 @@ enum cmd_and_opt_values
 
 
 /* The list of commands and options. */
-static ARGPARSE_OPTS opts[] =
-  {
-    { 301, NULL, 0, N_("@\nOptions:\n ") },
+static ARGPARSE_OPTS opts[] = {
+  ARGPARSE_group (301, N_("@\nOptions:\n ")),
     
-    { oVerbose, "verbose",  0, N_("verbose") },
-    { oQuiet, "quiet",      0, N_("quiet") },
-    { oHex,   "hex",        0, N_("print data out hex encoded") },
-    { oDecode,"decode",     0, N_("decode received data lines") },
-    { oRawSocket, "raw-socket", 2, N_("|NAME|connect to Assuan socket NAME")},
-    { oExec, "exec", 0, N_("run the Assuan server given on the command line")},
-    { oNoExtConnect, "no-ext-connect",
-                            0, N_("do not use extended connect mode")},
-    { oRun,  "run", 2,         N_("|FILE|run commands from FILE on startup")},
-    { oSubst, "subst", 0,      N_("run /subst on startup")}, 
-    /* hidden options */
-    { oNoVerbose, "no-verbose",  0, "@"},
-    { oHomedir, "homedir", 2, "@" },   
-    {0}
-  };
+  ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
+  ARGPARSE_s_n (oQuiet, "quiet",     N_("quiet")),
+  ARGPARSE_s_n (oHex,   "hex",       N_("print data out hex encoded")),
+  ARGPARSE_s_n (oDecode,"decode",    N_("decode received data lines")),
+  ARGPARSE_s_s (oRawSocket, "raw-socket", 
+                N_("|NAME|connect to Assuan socket NAME")),
+  ARGPARSE_s_n (oExec, "exec", 
+                N_("run the Assuan server given on the command line")),
+  ARGPARSE_s_n (oNoExtConnect, "no-ext-connect",
+                N_("do not use extended connect mode")),
+  ARGPARSE_s_s (oRun,  "run", 
+                N_("|FILE|run commands from FILE on startup")),
+  ARGPARSE_s_n (oSubst, "subst",     N_("run /subst on startup")), 
+
+  ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
+  ARGPARSE_s_s (oHomedir, "homedir", "@" ),   
+
+  ARGPARSE_end ()
+};
 
 
 /* We keep all global options in the structure OPT.  */
@@ -137,6 +144,8 @@ typedef struct loopline_s *loopline_t;
 /* This is used to store the pid of the server.  */
 static pid_t server_pid = (pid_t)(-1);
 
+/* The current datasink file or NULL.  */
+static FILE *current_datasink;
 
 /* A list of open file descriptors. */
 static struct
@@ -416,6 +425,9 @@ arithmetic_op (int operator, const char *operands)
   result = strtol (operands, NULL, 0);
   while (*operands && !spacep (operands) )
     operands++;
+  if (operator == '!')
+    result = !result;
+
   while (*operands)
     {
       while ( spacep (operands) )
@@ -440,6 +452,9 @@ arithmetic_op (int operator, const char *operands)
             return NULL;
           result %= value;
           break;
+        case '!': result = !value; break;
+        case '|': result = result || value; break;
+        case '&': result = result && value; break;
         default:
           log_error ("unknown arithmetic operator `%c'\n", operator);
           return NULL;
@@ -452,7 +467,7 @@ arithmetic_op (int operator, const char *operands)
 
 
 /* Extended version of get_var.  This returns a malloced string and
-   understand the fucntion syntax: "func args". 
+   understand the function syntax: "func args". 
 
    Defined functions are
    
@@ -486,6 +501,16 @@ arithmetic_op (int operator, const char *operands)
            linefeeds and carriage returns are also escaped.
            "percent+" also maps spaces to plus characters.
 
+     errcode ARG
+           Assuming ARG is an integer, return the gpg-error code.
+
+     errsource ARG
+           Assuming ARG is an integer, return the gpg-error source.
+
+     errstring ARG
+           Assuming ARG is an integer return a formatted fpf error string.
+
+
    Example: get_var_ext ("get sysconfdir") -> "/etc/gnupg"
     
   */
@@ -497,6 +522,7 @@ get_var_ext (const char *name)
   char *result;
   char *p;
   char *free_me = NULL;
+  int intvalue;
 
   if (recursion_count > 50)
     {
@@ -538,11 +564,7 @@ get_var_ext (const char *name)
       else if (!strcmp (s, "datadir"))
         result = xstrdup (gnupg_datadir ());
       else if (!strcmp (s, "serverpid"))
-        {
-          char numbuf[30];
-          snprintf (numbuf, sizeof numbuf, "%d", (int)server_pid);
-          result = xstrdup (numbuf);
-        }
+        result = xasprintf ("%d", (int)server_pid);
       else
         {
           log_error ("invalid argument `%s' for variable function `get'\n", s);
@@ -579,7 +601,26 @@ get_var_ext (const char *name)
         if (*p == ' ')
           *p = '+';
     }
-  else if ( (s - name) == 1 && strchr ("+-*/%", *name))
+  else if ( (s - name) == 7 && !strncmp (name, "errcode", 7))
+    {
+      s++;
+      intvalue = (int)strtol (s, NULL, 0);
+      result = xasprintf ("%d", gpg_err_code (intvalue));
+    }
+  else if ( (s - name) == 9 && !strncmp (name, "errsource", 9))
+    {
+      s++;
+      intvalue = (int)strtol (s, NULL, 0);
+      result = xasprintf ("%d", gpg_err_source (intvalue));
+    }
+  else if ( (s - name) == 9 && !strncmp (name, "errstring", 9))
+    {
+      s++;
+      intvalue = (int)strtol (s, NULL, 0);
+      result = xasprintf ("%s <%s>", 
+                          gpg_strerror (intvalue), gpg_strsource (intvalue));
+    }
+  else if ( (s - name) == 1 && strchr ("+-*/%!|&", *name))
     {
       result = arithmetic_op (*name, s+1);
     }
@@ -879,6 +920,8 @@ do_sendfd (assuan_context_t ctx, char *line)
 static void
 do_recvfd (assuan_context_t ctx, char *line)
 {
+  (void)ctx;
+  (void)line;
   log_info ("This command has not yet been implemented\n");
 }
 
@@ -1098,9 +1141,11 @@ main (int argc, char **argv)
     loopline_t *tail;
     loopline_t current;
     unsigned int nestlevel; 
+    int oneshot;
     char *condition;
   } loopstack[20];
   int        loopidx;
+  char **cmdline_commands = NULL;
 
   gnupg_rl_initialize ();
   set_strusage (my_strusage);
@@ -1158,7 +1203,7 @@ main (int argc, char **argv)
         }
     }
   else if (argc)
-    usage (1);
+    cmdline_commands = argv;
 
   if (opt.exec && opt.raw_socket)
     log_info (_("option \"%s\" ignored due to \"%s\"\n"),
@@ -1246,6 +1291,16 @@ main (int argc, char **argv)
           else
             log_fatal ("/end command vanished\n");
         }
+      else if (cmdline_commands && *cmdline_commands && !script_fp)
+        {
+          keep_line = 0;
+          xfree (line);
+          line = xstrdup (*cmdline_commands);
+          cmdline_commands++;
+          n = strlen (line);
+          if (n >= maxlength)
+            maxlength = 0;
+        }
       else if (use_tty && !script_fp)
         {
           keep_line = 0;
@@ -1398,6 +1453,29 @@ main (int argc, char **argv)
               else
                 add_definq (p, 0, 1);
             }
+          else if (!strcmp (cmd, "datafile"))
+            {
+              const char *fname;
+
+              if (current_datasink)
+                {
+                  if (current_datasink != stdout)
+                    fclose (current_datasink);
+                  current_datasink = NULL;
+                }
+              tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
+              fname = tmpline? tmpline : p;
+              if (fname && !strcmp (fname, "-"))
+                current_datasink = stdout;
+              else if (fname && *fname)
+                {
+                  current_datasink = fopen (fname, "wb");
+                  if (!current_datasink)
+                    log_error ("can't open `%s': %s\n", 
+                               fname, strerror (errno));
+                }
+              xfree (tmpline);
+            }
           else if (!strcmp (cmd, "showdef"))
             {
               show_definq ();
@@ -1523,7 +1601,7 @@ main (int argc, char **argv)
             {
               if (loopidx+2 >= (int)DIM(loopstack))
                 {
-                  log_error ("loops are nested too deep\n");
+                  log_error ("blocks are nested too deep\n");
                   /* We should better die or break all loop in this
                      case as recovering from this error won't be
                      easy.  */
@@ -1534,10 +1612,31 @@ main (int argc, char **argv)
                   loopstack[loopidx+1].tail = &loopstack[loopidx+1].head;
                   loopstack[loopidx+1].current = NULL;
                   loopstack[loopidx+1].nestlevel = 1;
+                  loopstack[loopidx+1].oneshot = 0;
                   loopstack[loopidx+1].condition = xstrdup (p);
                   loopstack[loopidx+1].collecting = 1;
                 }
             }
+          else if (!strcmp (cmd, "if"))
+            {
+              if (loopidx+2 >= (int)DIM(loopstack))
+                {
+                  log_error ("blocks are nested too deep\n");
+                }
+              else
+                {
+                  /* Note that we need to evaluate the condition right
+                     away and not just at the end of the block as we
+                     do with a WHILE. */
+                  loopstack[loopidx+1].head = NULL;
+                  loopstack[loopidx+1].tail = &loopstack[loopidx+1].head;
+                  loopstack[loopidx+1].current = NULL;
+                  loopstack[loopidx+1].nestlevel = 1;
+                  loopstack[loopidx+1].oneshot = 1;
+                  loopstack[loopidx+1].condition = substitute_line_copy (p);
+                  loopstack[loopidx+1].collecting = 1;
+                }
+            }
           else if (!strcmp (cmd, "end"))
             {
               if (loopidx < 0)
@@ -1550,6 +1649,11 @@ main (int argc, char **argv)
 
                   /* Evaluate the condition.  */
                   tmpcond = xstrdup (loopstack[loopidx].condition);
+                  if (loopstack[loopidx].oneshot)
+                    {
+                      xfree (loopstack[loopidx].condition);
+                      loopstack[loopidx].condition = xstrdup ("0");
+                    }
                   tmpline = substitute_line (tmpcond);
                   value = tmpline? tmpline : tmpcond;
                   condition = strtol (value, NULL, 0);
@@ -1574,6 +1678,7 @@ main (int argc, char **argv)
                       loopstack[loopidx].current = NULL;
                       loopstack[loopidx].nestlevel = 0;
                       loopstack[loopidx].collecting = 0;
+                      loopstack[loopidx].oneshot = 0;
                       xfree (loopstack[loopidx].condition);
                       loopstack[loopidx].condition = NULL;
                       loopidx--;
@@ -1598,6 +1703,7 @@ main (int argc, char **argv)
 "/definq NAME VAR       Use content of VAR for inquiries with NAME.\n"
 "/definqfile NAME FILE  Use content of FILE for inquiries with NAME.\n"
 "/definqprog NAME PGM   Run PGM for inquiries with NAME.\n"
+"/datafile [NAME]       Write all D line content to file NAME.\n"
 "/showdef               Print all definitions.\n"
 "/cleardef              Delete all definitions.\n"
 "/sendfd FILE MODE      Open FILE and pass descriptor to server.\n"
@@ -1608,10 +1714,11 @@ main (int argc, char **argv)
 "/serverpid             Retrieve the pid of the server.\n"
 "/[no]hex               Enable hex dumping of received data lines.\n"
 "/[no]decode            Enable decoding of received data lines.\n"
-"/[no]subst             Enable varibale substitution.\n"
+"/[no]subst             Enable variable substitution.\n"
 "/run FILE              Run commands from FILE.\n"
+"/if VAR                Begin conditional block controlled by VAR.\n"
 "/while VAR             Begin loop controlled by VAR.\n"
-"/end                   End loop.\n"
+"/end                   End loop or condition\n"
 "/bye                   Terminate gpg-connect-agent.\n"
 "/help                  Print this help.");
             }
@@ -1800,7 +1907,25 @@ read_and_print_response (assuan_context_t ctx, int *r_goterr)
       if (linelen >= 1
           && line[0] == 'D' && line[1] == ' ')
         {
-          if (opt.hex)
+          if (current_datasink)
+            {
+              const unsigned char *s;
+              int c = 0;
+
+              for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ )
+                {
+                  if (*s == '%' && j+2 < linelen)
+                    { 
+                      s++; j++;
+                      c = xtoi_2 ( s );
+                      s++; j++;
+                    }
+                  else
+                    c = *s;
+                  putc (c, current_datasink);
+                }
+            }
+          else if (opt.hex)
             {
               for (i=2; i < linelen; )
                 {
@@ -1868,7 +1993,8 @@ read_and_print_response (assuan_context_t ctx, int *r_goterr)
         {
           if (need_lf)
             {
-              putchar ('\n');
+              if (!current_datasink || current_datasink != stdout)
+                putchar ('\n');
               need_lf = 0;
             }
 
@@ -1876,23 +2002,39 @@ read_and_print_response (assuan_context_t ctx, int *r_goterr)
               && line[0] == 'S' 
               && (line[1] == '\0' || line[1] == ' '))
             {
-              fwrite (line, linelen, 1, stdout);
-              putchar ('\n');
+              if (!current_datasink || current_datasink != stdout)
+                {
+                  fwrite (line, linelen, 1, stdout);
+                  putchar ('\n');
+                }
             }  
           else if (linelen >= 2
                    && line[0] == 'O' && line[1] == 'K'
                    && (line[2] == '\0' || line[2] == ' '))
             {
-              fwrite (line, linelen, 1, stdout);
-              putchar ('\n');
+              if (!current_datasink || current_datasink != stdout)
+                {
+                  fwrite (line, linelen, 1, stdout);
+                  putchar ('\n');
+                }
+              set_int_var ("?", 0);
               return 0;
             }
           else if (linelen >= 3
                    && line[0] == 'E' && line[1] == 'R' && line[2] == 'R'
                    && (line[3] == '\0' || line[3] == ' '))
             {
-              fwrite (line, linelen, 1, stdout);
-              putchar ('\n');
+              int errval;
+
+              errval = strtol (line+3, NULL, 10);
+              if (!errval)
+                errval = -1;
+              set_int_var ("?", errval);
+              if (!current_datasink || current_datasink != stdout)
+                {
+                  fwrite (line, linelen, 1, stdout);
+                  putchar ('\n');
+                }
               *r_goterr = 1;
               return 0;
             }  
@@ -1902,8 +2044,11 @@ read_and_print_response (assuan_context_t ctx, int *r_goterr)
                    && line[6] == 'E' 
                    && (line[7] == '\0' || line[7] == ' '))
             {
-              fwrite (line, linelen, 1, stdout);
-              putchar ('\n');
+              if (!current_datasink || current_datasink != stdout)
+                {
+                  fwrite (line, linelen, 1, stdout);
+                  putchar ('\n');
+                }
               if (!handle_inquire (ctx, line))
                 assuan_write_line (ctx, "CANCEL");
             }
@@ -1911,8 +2056,11 @@ read_and_print_response (assuan_context_t ctx, int *r_goterr)
                    && line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
                    && (line[3] == '\0' || line[3] == ' '))
             {
-              fwrite (line, linelen, 1, stdout);
-              putchar ('\n');
+              if (!current_datasink || current_datasink != stdout)
+                {
+                  fwrite (line, linelen, 1, stdout);
+                  putchar ('\n');
+                }
               /* Received from server, thus more responses are expected.  */
             }
           else
@@ -1940,6 +2088,38 @@ start_agent (void)
       /* Check whether we can connect at the standard socket.  */
       sockname = make_filename (opt.homedir, "S.gpg-agent", NULL);
       rc = assuan_socket_connect (&ctx, sockname, 0);
+
+#ifdef HAVE_W32_SYSTEM
+      /* If we failed to connect under Windows, we fire up the agent.  */
+      if (gpg_err_code (rc) == GPG_ERR_ASS_CONNECT_FAILED)
+        {
+          const char *agent_program;
+          const char *argv[3];
+          int save_rc = rc;
+          
+          if (opt.verbose)
+            log_info (_("no running gpg-agent - starting one\n"));
+          agent_program = gnupg_module_name (GNUPG_MODULE_NAME_AGENT);
+          
+          argv[0] = "--daemon";
+          argv[1] = "--use-standard-socket"; 
+          argv[2] = NULL;  
+
+          rc = gnupg_spawn_process_detached (agent_program, argv, NULL);
+          if (rc)
+            log_debug ("failed to start agent `%s': %s\n",
+                       agent_program, gpg_strerror (rc));
+          else
+            {
+              /* Give the agent some time to prepare itself. */
+              gnupg_sleep (3);
+              /* Now try again to connect the agent.  */
+              rc = assuan_socket_connect (&ctx, sockname, 0);
+            }
+          if (rc)
+            rc = save_rc;
+        }
+#endif /*HAVE_W32_SYSTEM*/
       xfree (sockname);
     }
   else