mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
2181 lines
58 KiB
C
2181 lines
58 KiB
C
/* gpg-connect-agent.c - Tool to connect to the agent.
|
||
* Copyright (C) 2005, 2007, 2008 Free Software Foundation, Inc.
|
||
*
|
||
* This file is part of GnuPG.
|
||
*
|
||
* GnuPG is free software; you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation; either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* GnuPG is distributed in the hope that it will be useful,
|
||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
* GNU General Public License for more details.
|
||
*
|
||
* You should have received a copy of the GNU General Public License
|
||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#include <config.h>
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <errno.h>
|
||
#include <ctype.h>
|
||
#include <assuan.h>
|
||
#include <unistd.h>
|
||
#include <assert.h>
|
||
|
||
#include "i18n.h"
|
||
#include "../common/util.h"
|
||
#include "../common/asshelp.h"
|
||
#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')
|
||
|
||
/* Constants to identify the commands and options. */
|
||
enum cmd_and_opt_values
|
||
{
|
||
aNull = 0,
|
||
oQuiet = 'q',
|
||
oVerbose = 'v',
|
||
oRawSocket = 'S',
|
||
oExec = 'E',
|
||
oRun = 'r',
|
||
oSubst = 's',
|
||
|
||
oNoVerbose = 500,
|
||
oHomedir,
|
||
oHex,
|
||
oDecode,
|
||
oNoExtConnect
|
||
|
||
};
|
||
|
||
|
||
/* The list of commands and options. */
|
||
static ARGPARSE_OPTS opts[] = {
|
||
ARGPARSE_group (301, N_("@\nOptions:\n ")),
|
||
|
||
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. */
|
||
struct
|
||
{
|
||
int verbose; /* Verbosity level. */
|
||
int quiet; /* Be extra quiet. */
|
||
const char *homedir; /* Configuration directory name */
|
||
int hex; /* Print data lines in hex format. */
|
||
int decode; /* Decode received data lines. */
|
||
const char *raw_socket; /* Name of socket to connect in raw mode. */
|
||
int exec; /* Run the pgm given on the command line. */
|
||
unsigned int connect_flags; /* Flags used for connecting. */
|
||
int enable_varsubst; /* Set if variable substitution is enabled. */
|
||
int trim_leading_spaces;
|
||
} opt;
|
||
|
||
|
||
|
||
/* Definitions for /definq commands and a global linked list with all
|
||
the definitions. */
|
||
struct definq_s
|
||
{
|
||
struct definq_s *next;
|
||
char *name; /* Name of inquiry or NULL for any name. */
|
||
int is_var; /* True if FILE is a variable name. */
|
||
int is_prog; /* True if FILE is a program to run. */
|
||
char file[1]; /* Name of file or program. */
|
||
};
|
||
typedef struct definq_s *definq_t;
|
||
|
||
static definq_t definq_list;
|
||
static definq_t *definq_list_tail = &definq_list;
|
||
|
||
|
||
/* Variable definitions and glovbal table. */
|
||
struct variable_s
|
||
{
|
||
struct variable_s *next;
|
||
char *value; /* Malloced value - always a string. */
|
||
char name[1]; /* Name of the variable. */
|
||
};
|
||
typedef struct variable_s *variable_t;
|
||
|
||
static variable_t variable_table;
|
||
|
||
|
||
/* To implement loops we store entire lines in a linked list. */
|
||
struct loopline_s
|
||
{
|
||
struct loopline_s *next;
|
||
char line[1];
|
||
};
|
||
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
|
||
{
|
||
int inuse;
|
||
#ifdef HAVE_W32_SYSTEM
|
||
HANDLE handle;
|
||
#endif
|
||
} open_fd_table[256];
|
||
|
||
|
||
/*-- local prototypes --*/
|
||
static char *substitute_line_copy (const char *buffer);
|
||
static int read_and_print_response (assuan_context_t ctx, int *r_goterr);
|
||
static assuan_context_t start_agent (void);
|
||
|
||
|
||
|
||
|
||
/* Print usage information and and provide strings for help. */
|
||
static const char *
|
||
my_strusage( int level )
|
||
{
|
||
const char *p;
|
||
|
||
switch (level)
|
||
{
|
||
case 11: p = "gpg-connect-agent (GnuPG)";
|
||
break;
|
||
case 13: p = VERSION; break;
|
||
case 17: p = PRINTABLE_OS_NAME; break;
|
||
case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
|
||
break;
|
||
case 1:
|
||
case 40: p = _("Usage: gpg-connect-agent [options] (-h for help)");
|
||
break;
|
||
case 41:
|
||
p = _("Syntax: gpg-connect-agent [options]\n"
|
||
"Connect to a running agent and send commands\n");
|
||
break;
|
||
case 31: p = "\nHome: "; break;
|
||
case 32: p = opt.homedir; break;
|
||
case 33: p = "\n"; break;
|
||
|
||
default: p = NULL; break;
|
||
}
|
||
return p;
|
||
}
|
||
|
||
|
||
static char *
|
||
gnu_getcwd (void)
|
||
{
|
||
char *buffer;
|
||
size_t size = 100;
|
||
|
||
for (;;)
|
||
{
|
||
buffer = xmalloc (size+1);
|
||
if (getcwd (buffer, size) == buffer)
|
||
return buffer;
|
||
xfree (buffer);
|
||
if (errno != ERANGE)
|
||
return NULL;
|
||
size *= 2;
|
||
}
|
||
}
|
||
|
||
|
||
/* Unescale STRING and returned the malloced result. The surrounding
|
||
quotes must already be removed from STRING. */
|
||
static char *
|
||
unescape_string (const char *string)
|
||
{
|
||
const unsigned char *s;
|
||
int esc;
|
||
size_t n;
|
||
char *buffer;
|
||
unsigned char *d;
|
||
|
||
n = 0;
|
||
for (s = (const unsigned char*)string, esc=0; *s; s++)
|
||
{
|
||
if (esc)
|
||
{
|
||
switch (*s)
|
||
{
|
||
case 'b':
|
||
case 't':
|
||
case 'v':
|
||
case 'n':
|
||
case 'f':
|
||
case 'r':
|
||
case '"':
|
||
case '\'':
|
||
case '\\': n++; break;
|
||
case 'x':
|
||
if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2))
|
||
n++;
|
||
break;
|
||
|
||
default:
|
||
if (s[1] && s[2]
|
||
&& octdigitp (s) && octdigitp (s+1) && octdigitp (s+2))
|
||
n++;
|
||
break;
|
||
}
|
||
esc = 0;
|
||
}
|
||
else if (*s == '\\')
|
||
esc = 1;
|
||
else
|
||
n++;
|
||
}
|
||
|
||
buffer = xmalloc (n+1);
|
||
d = (unsigned char*)buffer;
|
||
for (s = (const unsigned char*)string, esc=0; *s; s++)
|
||
{
|
||
if (esc)
|
||
{
|
||
switch (*s)
|
||
{
|
||
case 'b': *d++ = '\b'; break;
|
||
case 't': *d++ = '\t'; break;
|
||
case 'v': *d++ = '\v'; break;
|
||
case 'n': *d++ = '\n'; break;
|
||
case 'f': *d++ = '\f'; break;
|
||
case 'r': *d++ = '\r'; break;
|
||
case '"': *d++ = '\"'; break;
|
||
case '\'': *d++ = '\''; break;
|
||
case '\\': *d++ = '\\'; break;
|
||
case 'x':
|
||
if (s[1] && s[2] && hexdigitp (s+1) && hexdigitp (s+2))
|
||
{
|
||
s++;
|
||
*d++ = xtoi_2 (s);
|
||
s++;
|
||
}
|
||
break;
|
||
|
||
default:
|
||
if (s[1] && s[2]
|
||
&& octdigitp (s) && octdigitp (s+1) && octdigitp (s+2))
|
||
{
|
||
*d++ = (atoi_1 (s)*64) + (atoi_1 (s+1)*8) + atoi_1 (s+2);
|
||
s += 2;
|
||
}
|
||
break;
|
||
}
|
||
esc = 0;
|
||
}
|
||
else if (*s == '\\')
|
||
esc = 1;
|
||
else
|
||
*d++ = *s;
|
||
}
|
||
*d = 0;
|
||
return buffer;
|
||
}
|
||
|
||
|
||
/* Do the percent unescaping and return a newly malloced string.
|
||
If WITH_PLUS is set '+' characters will be changed to space. */
|
||
static char *
|
||
unpercent_string (const char *string, int with_plus)
|
||
{
|
||
const unsigned char *s;
|
||
unsigned char *buffer, *p;
|
||
size_t n;
|
||
|
||
n = 0;
|
||
for (s=(const unsigned char *)string; *s; s++)
|
||
{
|
||
if (*s == '%' && s[1] && s[2])
|
||
{
|
||
s++;
|
||
n++;
|
||
s++;
|
||
}
|
||
else if (with_plus && *s == '+')
|
||
n++;
|
||
else
|
||
n++;
|
||
}
|
||
|
||
buffer = xmalloc (n+1);
|
||
p = buffer;
|
||
for (s=(const unsigned char *)string; *s; s++)
|
||
{
|
||
if (*s == '%' && s[1] && s[2])
|
||
{
|
||
s++;
|
||
*p++ = xtoi_2 (s);
|
||
s++;
|
||
}
|
||
else if (with_plus && *s == '+')
|
||
*p++ = ' ';
|
||
else
|
||
*p++ = *s;
|
||
}
|
||
*p = 0;
|
||
return (char*)buffer;
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
static const char *
|
||
set_var (const char *name, const char *value)
|
||
{
|
||
variable_t var;
|
||
|
||
for (var = variable_table; var; var = var->next)
|
||
if (!strcmp (var->name, name))
|
||
break;
|
||
if (!var)
|
||
{
|
||
var = xmalloc (sizeof *var + strlen (name));
|
||
var->value = NULL;
|
||
strcpy (var->name, name);
|
||
var->next = variable_table;
|
||
variable_table = var;
|
||
}
|
||
xfree (var->value);
|
||
var->value = value? xstrdup (value) : NULL;
|
||
return var->value;
|
||
}
|
||
|
||
|
||
static void
|
||
set_int_var (const char *name, int value)
|
||
{
|
||
char numbuf[35];
|
||
|
||
snprintf (numbuf, sizeof numbuf, "%d", value);
|
||
set_var (name, numbuf);
|
||
}
|
||
|
||
|
||
/* Return the value of a variable. That value is valid until a
|
||
variable of the name is changed. Return NULL if not found. Note
|
||
that envvars are copied to our variable list at the first access
|
||
and not at oprogram start. */
|
||
static const char *
|
||
get_var (const char *name)
|
||
{
|
||
variable_t var;
|
||
const char *s;
|
||
|
||
if (!*name)
|
||
return "";
|
||
for (var = variable_table; var; var = var->next)
|
||
if (!strcmp (var->name, name))
|
||
break;
|
||
if (!var && (s = getenv (name)))
|
||
return set_var (name, s);
|
||
if (!var || !var->value)
|
||
return NULL;
|
||
return var->value;
|
||
}
|
||
|
||
|
||
/* Perform some simple arithmentic operations. Caller must release
|
||
the return value. On error the return value is NULL. */
|
||
static char *
|
||
arithmetic_op (int operator, const char *operands)
|
||
{
|
||
long result, value;
|
||
char numbuf[35];
|
||
|
||
while ( spacep (operands) )
|
||
operands++;
|
||
if (!*operands)
|
||
return NULL;
|
||
result = strtol (operands, NULL, 0);
|
||
while (*operands && !spacep (operands) )
|
||
operands++;
|
||
if (operator == '!')
|
||
result = !result;
|
||
|
||
while (*operands)
|
||
{
|
||
while ( spacep (operands) )
|
||
operands++;
|
||
if (!*operands)
|
||
break;
|
||
value = strtol (operands, NULL, 0);
|
||
while (*operands && !spacep (operands) )
|
||
operands++;
|
||
switch (operator)
|
||
{
|
||
case '+': result += value; break;
|
||
case '-': result -= value; break;
|
||
case '*': result *= value; break;
|
||
case '/':
|
||
if (!value)
|
||
return NULL;
|
||
result /= value;
|
||
break;
|
||
case '%':
|
||
if (!value)
|
||
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;
|
||
}
|
||
}
|
||
snprintf (numbuf, sizeof numbuf, "%ld", result);
|
||
return xstrdup (numbuf);
|
||
}
|
||
|
||
|
||
|
||
/* Extended version of get_var. This returns a malloced string and
|
||
understand the function syntax: "func args".
|
||
|
||
Defined functions are
|
||
|
||
get - Return a value described by the next argument:
|
||
cwd - The current working directory.
|
||
homedir - The gnupg homedir.
|
||
sysconfdir - GnuPG's system configuration directory.
|
||
bindir - GnuPG's binary directory.
|
||
libdir - GnuPG's library directory.
|
||
libexecdir - GnuPG's library directory for executable files.
|
||
datadir - GnuPG's data directory.
|
||
serverpid - The PID of the current server.
|
||
|
||
unescape ARGS
|
||
Remove C-style escapes from string. Note that "\0" and
|
||
"\x00" terminate the string implictly. Use "\x7d" to
|
||
represent the closing brace. The args start right after
|
||
the first space after the function name.
|
||
|
||
unpercent ARGS
|
||
unpercent+ ARGS
|
||
Remove percent style ecaping from string. Note that "%00
|
||
terminates the string implicitly. Use "%7d" to represetn
|
||
the closing brace. The args start right after the first
|
||
space after the function name. "unpercent+" also maps '+'
|
||
to space.
|
||
|
||
percent ARGS
|
||
percent+ ARGS
|
||
Escape the args using the percent style. Tabs, formfeeds,
|
||
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"
|
||
|
||
*/
|
||
static char *
|
||
get_var_ext (const char *name)
|
||
{
|
||
static int recursion_count;
|
||
const char *s;
|
||
char *result;
|
||
char *p;
|
||
char *free_me = NULL;
|
||
int intvalue;
|
||
|
||
if (recursion_count > 50)
|
||
{
|
||
log_error ("variables nested too deeply\n");
|
||
return NULL;
|
||
}
|
||
|
||
recursion_count++;
|
||
free_me = opt.enable_varsubst? substitute_line_copy (name) : NULL;
|
||
if (free_me)
|
||
name = free_me;
|
||
for (s=name; *s && !spacep (s); s++)
|
||
;
|
||
if (!*s)
|
||
{
|
||
s = get_var (name);
|
||
result = s? xstrdup (s): NULL;
|
||
}
|
||
else if ( (s - name) == 3 && !strncmp (name, "get", 3))
|
||
{
|
||
while ( spacep (s) )
|
||
s++;
|
||
if (!strcmp (s, "cwd"))
|
||
{
|
||
result = gnu_getcwd ();
|
||
if (!result)
|
||
log_error ("getcwd failed: %s\n", strerror (errno));
|
||
}
|
||
else if (!strcmp (s, "homedir"))
|
||
result = make_filename (opt.homedir, NULL);
|
||
else if (!strcmp (s, "sysconfdir"))
|
||
result = xstrdup (gnupg_sysconfdir ());
|
||
else if (!strcmp (s, "bindir"))
|
||
result = xstrdup (gnupg_bindir ());
|
||
else if (!strcmp (s, "libdir"))
|
||
result = xstrdup (gnupg_libdir ());
|
||
else if (!strcmp (s, "libexecdir"))
|
||
result = xstrdup (gnupg_libexecdir ());
|
||
else if (!strcmp (s, "datadir"))
|
||
result = xstrdup (gnupg_datadir ());
|
||
else if (!strcmp (s, "serverpid"))
|
||
result = xasprintf ("%d", (int)server_pid);
|
||
else
|
||
{
|
||
log_error ("invalid argument `%s' for variable function `get'\n", s);
|
||
log_info ("valid are: cwd, "
|
||
"{home,bin,lib,libexec,data}dir, serverpid\n");
|
||
result = NULL;
|
||
}
|
||
}
|
||
else if ( (s - name) == 8 && !strncmp (name, "unescape", 8))
|
||
{
|
||
s++;
|
||
result = unescape_string (s);
|
||
}
|
||
else if ( (s - name) == 9 && !strncmp (name, "unpercent", 9))
|
||
{
|
||
s++;
|
||
result = unpercent_string (s, 0);
|
||
}
|
||
else if ( (s - name) == 10 && !strncmp (name, "unpercent+", 10))
|
||
{
|
||
s++;
|
||
result = unpercent_string (s, 1);
|
||
}
|
||
else if ( (s - name) == 7 && !strncmp (name, "percent", 7))
|
||
{
|
||
s++;
|
||
result = percent_escape (s, "\t\r\n\f\v");
|
||
}
|
||
else if ( (s - name) == 8 && !strncmp (name, "percent+", 8))
|
||
{
|
||
s++;
|
||
result = percent_escape (s, "\t\r\n\f\v");
|
||
for (p=result; *p; p++)
|
||
if (*p == ' ')
|
||
*p = '+';
|
||
}
|
||
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);
|
||
}
|
||
else
|
||
{
|
||
log_error ("unknown variable function `%.*s'\n", (int)(s-name), name);
|
||
result = NULL;
|
||
}
|
||
|
||
xfree (free_me);
|
||
recursion_count--;
|
||
return result;
|
||
}
|
||
|
||
|
||
/* Substitute variables in LINE and return a new allocated buffer if
|
||
required. The function might modify LINE if the expanded version
|
||
fits into it. */
|
||
static char *
|
||
substitute_line (char *buffer)
|
||
{
|
||
char *line = buffer;
|
||
char *p, *pend;
|
||
const char *value;
|
||
size_t valuelen, n;
|
||
char *result = NULL;
|
||
char *freeme = NULL;
|
||
|
||
while (*line)
|
||
{
|
||
p = strchr (line, '$');
|
||
if (!p)
|
||
return result; /* No more variables. */
|
||
|
||
if (p[1] == '$') /* Escaped dollar sign. */
|
||
{
|
||
memmove (p, p+1, strlen (p+1)+1);
|
||
line = p + 1;
|
||
continue;
|
||
}
|
||
if (p[1] == '{')
|
||
{
|
||
int count = 0;
|
||
|
||
for (pend=p+2; *pend; pend++)
|
||
{
|
||
if (*pend == '{')
|
||
count++;
|
||
else if (*pend == '}')
|
||
{
|
||
if (--count < 0)
|
||
break;
|
||
}
|
||
}
|
||
if (!*pend)
|
||
return result; /* Unclosed - don't substitute. */
|
||
}
|
||
else
|
||
{
|
||
for (pend=p+1; *pend && !spacep (pend) && *pend != '$' ; pend++)
|
||
;
|
||
}
|
||
if (p[1] == '{' && *pend == '}')
|
||
{
|
||
int save = *pend;
|
||
*pend = 0;
|
||
freeme = get_var_ext (p+2);
|
||
value = freeme;
|
||
*pend++ = save;
|
||
}
|
||
else if (*pend)
|
||
{
|
||
int save = *pend;
|
||
*pend = 0;
|
||
value = get_var (p+1);
|
||
*pend = save;
|
||
}
|
||
else
|
||
value = get_var (p+1);
|
||
if (!value)
|
||
value = "";
|
||
valuelen = strlen (value);
|
||
if (valuelen <= pend - p)
|
||
{
|
||
memcpy (p, value, valuelen);
|
||
p += valuelen;
|
||
n = pend - p;
|
||
if (n)
|
||
memmove (p, p+n, strlen (p+n)+1);
|
||
line = p;
|
||
}
|
||
else
|
||
{
|
||
char *src = result? result : buffer;
|
||
char *dst;
|
||
|
||
dst = xmalloc (strlen (src) + valuelen + 1);
|
||
n = p - src;
|
||
memcpy (dst, src, n);
|
||
memcpy (dst + n, value, valuelen);
|
||
n += valuelen;
|
||
strcpy (dst + n, pend);
|
||
line = dst + n;
|
||
xfree (result);
|
||
result = dst;
|
||
}
|
||
xfree (freeme);
|
||
freeme = NULL;
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/* Same as substitute_line but do not modify BUFFER. */
|
||
static char *
|
||
substitute_line_copy (const char *buffer)
|
||
{
|
||
char *result, *p;
|
||
|
||
p = xstrdup (buffer?buffer:"");
|
||
result = substitute_line (p);
|
||
if (!result)
|
||
result = p;
|
||
else
|
||
xfree (p);
|
||
return result;
|
||
}
|
||
|
||
|
||
static void
|
||
assign_variable (char *line, int syslet)
|
||
{
|
||
char *name, *p, *tmp, *free_me, *buffer;
|
||
|
||
/* Get the name. */
|
||
name = line;
|
||
for (p=name; *p && !spacep (p); p++)
|
||
;
|
||
if (*p)
|
||
*p++ = 0;
|
||
while (spacep (p))
|
||
p++;
|
||
|
||
if (!*p)
|
||
set_var (name, NULL); /* Remove variable. */
|
||
else if (syslet)
|
||
{
|
||
free_me = opt.enable_varsubst? substitute_line_copy (p) : NULL;
|
||
if (free_me)
|
||
p = free_me;
|
||
buffer = xmalloc (4 + strlen (p) + 1);
|
||
strcpy (stpcpy (buffer, "get "), p);
|
||
tmp = get_var_ext (buffer);
|
||
xfree (buffer);
|
||
set_var (name, tmp);
|
||
xfree (tmp);
|
||
xfree (free_me);
|
||
}
|
||
else
|
||
{
|
||
tmp = opt.enable_varsubst? substitute_line_copy (p) : NULL;
|
||
if (tmp)
|
||
{
|
||
set_var (name, tmp);
|
||
xfree (tmp);
|
||
}
|
||
else
|
||
set_var (name, p);
|
||
}
|
||
}
|
||
|
||
|
||
static void
|
||
show_variables (void)
|
||
{
|
||
variable_t var;
|
||
|
||
for (var = variable_table; var; var = var->next)
|
||
if (var->value)
|
||
printf ("%-20s %s\n", var->name, var->value);
|
||
}
|
||
|
||
|
||
/* Store an inquire response pattern. Note, that this function may
|
||
change the content of LINE. We assume that leading white spaces
|
||
are already removed. */
|
||
static void
|
||
add_definq (char *line, int is_var, int is_prog)
|
||
{
|
||
definq_t d;
|
||
char *name, *p;
|
||
|
||
/* Get name. */
|
||
name = line;
|
||
for (p=name; *p && !spacep (p); p++)
|
||
;
|
||
if (*p)
|
||
*p++ = 0;
|
||
while (spacep (p))
|
||
p++;
|
||
|
||
d = xmalloc (sizeof *d + strlen (p) );
|
||
strcpy (d->file, p);
|
||
d->is_var = is_var;
|
||
d->is_prog = is_prog;
|
||
if ( !strcmp (name, "*"))
|
||
d->name = NULL;
|
||
else
|
||
d->name = xstrdup (name);
|
||
|
||
d->next = NULL;
|
||
*definq_list_tail = d;
|
||
definq_list_tail = &d->next;
|
||
}
|
||
|
||
|
||
/* Show all inquiry defintions. */
|
||
static void
|
||
show_definq (void)
|
||
{
|
||
definq_t d;
|
||
|
||
for (d=definq_list; d; d = d->next)
|
||
if (d->name)
|
||
printf ("%-20s %c %s\n",
|
||
d->name, d->is_var? 'v' : d->is_prog? 'p':'f', d->file);
|
||
for (d=definq_list; d; d = d->next)
|
||
if (!d->name)
|
||
printf ("%-20s %c %s\n", "*",
|
||
d->is_var? 'v': d->is_prog? 'p':'f', d->file);
|
||
}
|
||
|
||
|
||
/* Clear all inquiry definitions. */
|
||
static void
|
||
clear_definq (void)
|
||
{
|
||
while (definq_list)
|
||
{
|
||
definq_t tmp = definq_list->next;
|
||
xfree (definq_list->name);
|
||
xfree (definq_list);
|
||
definq_list = tmp;
|
||
}
|
||
definq_list_tail = &definq_list;
|
||
}
|
||
|
||
|
||
static void
|
||
do_sendfd (assuan_context_t ctx, char *line)
|
||
{
|
||
FILE *fp;
|
||
char *name, *mode, *p;
|
||
int rc, fd;
|
||
|
||
/* Get file name. */
|
||
name = line;
|
||
for (p=name; *p && !spacep (p); p++)
|
||
;
|
||
if (*p)
|
||
*p++ = 0;
|
||
while (spacep (p))
|
||
p++;
|
||
|
||
/* Get mode. */
|
||
mode = p;
|
||
if (!*mode)
|
||
mode = "r";
|
||
else
|
||
{
|
||
for (p=mode; *p && !spacep (p); p++)
|
||
;
|
||
if (*p)
|
||
*p++ = 0;
|
||
}
|
||
|
||
/* Open and send. */
|
||
fp = fopen (name, mode);
|
||
if (!fp)
|
||
{
|
||
log_error ("can't open `%s' in \"%s\" mode: %s\n",
|
||
name, mode, strerror (errno));
|
||
return;
|
||
}
|
||
fd = fileno (fp);
|
||
|
||
if (opt.verbose)
|
||
log_error ("file `%s' opened in \"%s\" mode, fd=%d\n",
|
||
name, mode, fd);
|
||
|
||
rc = assuan_sendfd (ctx, INT2FD (fd) );
|
||
if (rc)
|
||
log_error ("sending descriptor %d failed: %s\n", fd, gpg_strerror (rc));
|
||
fclose (fp);
|
||
}
|
||
|
||
|
||
static void
|
||
do_recvfd (assuan_context_t ctx, char *line)
|
||
{
|
||
(void)ctx;
|
||
(void)line;
|
||
log_info ("This command has not yet been implemented\n");
|
||
}
|
||
|
||
|
||
static void
|
||
do_open (char *line)
|
||
{
|
||
FILE *fp;
|
||
char *varname, *name, *mode, *p;
|
||
int fd;
|
||
|
||
#ifdef HAVE_W32_SYSTEM
|
||
if (server_pid == (pid_t)(-1))
|
||
{
|
||
log_error ("the pid of the server is unknown\n");
|
||
log_info ("use command \"/serverpid\" first\n");
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
/* Get variable name. */
|
||
varname = line;
|
||
for (p=varname; *p && !spacep (p); p++)
|
||
;
|
||
if (*p)
|
||
*p++ = 0;
|
||
while (spacep (p))
|
||
p++;
|
||
|
||
/* Get file name. */
|
||
name = p;
|
||
for (p=name; *p && !spacep (p); p++)
|
||
;
|
||
if (*p)
|
||
*p++ = 0;
|
||
while (spacep (p))
|
||
p++;
|
||
|
||
/* Get mode. */
|
||
mode = p;
|
||
if (!*mode)
|
||
mode = "r";
|
||
else
|
||
{
|
||
for (p=mode; *p && !spacep (p); p++)
|
||
;
|
||
if (*p)
|
||
*p++ = 0;
|
||
}
|
||
|
||
/* Open and send. */
|
||
fp = fopen (name, mode);
|
||
if (!fp)
|
||
{
|
||
log_error ("can't open `%s' in \"%s\" mode: %s\n",
|
||
name, mode, strerror (errno));
|
||
return;
|
||
}
|
||
fd = fileno (fp);
|
||
if (fd >= 0 && fd < DIM (open_fd_table))
|
||
{
|
||
open_fd_table[fd].inuse = 1;
|
||
#ifdef HAVE_W32_SYSTEM
|
||
{
|
||
HANDLE prochandle, handle, newhandle;
|
||
|
||
handle = (void*)_get_osfhandle (fd);
|
||
|
||
prochandle = OpenProcess (PROCESS_DUP_HANDLE, FALSE, server_pid);
|
||
if (!prochandle)
|
||
{
|
||
log_error ("failed to open the server process\n");
|
||
close (fd);
|
||
return;
|
||
}
|
||
|
||
if (!DuplicateHandle (GetCurrentProcess(), handle,
|
||
prochandle, &newhandle, 0,
|
||
TRUE, DUPLICATE_SAME_ACCESS ))
|
||
{
|
||
log_error ("failed to duplicate the handle\n");
|
||
close (fd);
|
||
CloseHandle (prochandle);
|
||
return;
|
||
}
|
||
CloseHandle (prochandle);
|
||
open_fd_table[fd].handle = newhandle;
|
||
}
|
||
if (opt.verbose)
|
||
log_info ("file `%s' opened in \"%s\" mode, fd=%d (libc=%d)\n",
|
||
name, mode, (int)open_fd_table[fd].handle, fd);
|
||
set_int_var (varname, (int)open_fd_table[fd].handle);
|
||
#else
|
||
if (opt.verbose)
|
||
log_info ("file `%s' opened in \"%s\" mode, fd=%d\n",
|
||
name, mode, fd);
|
||
set_int_var (varname, fd);
|
||
#endif
|
||
}
|
||
else
|
||
{
|
||
log_error ("can't put fd %d into table\n", fd);
|
||
close (fd);
|
||
}
|
||
}
|
||
|
||
|
||
static void
|
||
do_close (char *line)
|
||
{
|
||
int fd = atoi (line);
|
||
|
||
#ifdef HAVE_W32_SYSTEM
|
||
int i;
|
||
|
||
for (i=0; i < DIM (open_fd_table); i++)
|
||
if ( open_fd_table[i].inuse && open_fd_table[i].handle == (void*)fd)
|
||
break;
|
||
if (i < DIM (open_fd_table))
|
||
fd = i;
|
||
else
|
||
{
|
||
log_error ("given fd (system handle) has not been opened\n");
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
if (fd < 0 || fd >= DIM (open_fd_table))
|
||
{
|
||
log_error ("invalid fd\n");
|
||
return;
|
||
}
|
||
|
||
if (!open_fd_table[fd].inuse)
|
||
{
|
||
log_error ("given fd has not been opened\n");
|
||
return;
|
||
}
|
||
#ifdef HAVE_W32_SYSTEM
|
||
CloseHandle (open_fd_table[fd].handle); /* Close duped handle. */
|
||
#endif
|
||
close (fd);
|
||
open_fd_table[fd].inuse = 0;
|
||
}
|
||
|
||
|
||
static void
|
||
do_showopen (void)
|
||
{
|
||
int i;
|
||
|
||
for (i=0; i < DIM (open_fd_table); i++)
|
||
if (open_fd_table[i].inuse)
|
||
{
|
||
#ifdef HAVE_W32_SYSTEM
|
||
printf ("%-15d (libc=%d)\n", (int)open_fd_table[i].handle, i);
|
||
#else
|
||
printf ("%-15d\n", i);
|
||
#endif
|
||
}
|
||
}
|
||
|
||
|
||
|
||
static int
|
||
getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
|
||
{
|
||
membuf_t *mb = opaque;
|
||
put_membuf (mb, buffer, length);
|
||
return 0;
|
||
}
|
||
|
||
/* Get the pid of the server and store it locally. */
|
||
static void
|
||
do_serverpid (assuan_context_t ctx)
|
||
{
|
||
int rc;
|
||
membuf_t mb;
|
||
char *buffer;
|
||
|
||
init_membuf (&mb, 100);
|
||
rc = assuan_transact (ctx, "GETINFO pid", getinfo_pid_cb, &mb,
|
||
NULL, NULL, NULL, NULL);
|
||
put_membuf (&mb, "", 1);
|
||
buffer = get_membuf (&mb, NULL);
|
||
if (rc || !buffer)
|
||
log_error ("command \"%s\" failed: %s\n",
|
||
"GETINFO pid", gpg_strerror (rc));
|
||
else
|
||
{
|
||
server_pid = (pid_t)strtoul (buffer, NULL, 10);
|
||
if (opt.verbose)
|
||
log_info ("server's PID is %lu\n", (unsigned long)server_pid);
|
||
}
|
||
xfree (buffer);
|
||
}
|
||
|
||
|
||
/* gpg-connect-agent's entry point. */
|
||
int
|
||
main (int argc, char **argv)
|
||
{
|
||
ARGPARSE_ARGS pargs;
|
||
int no_more_options = 0;
|
||
assuan_context_t ctx;
|
||
char *line, *p;
|
||
char *tmpline;
|
||
size_t linesize;
|
||
int rc;
|
||
int cmderr;
|
||
const char *opt_run = NULL;
|
||
FILE *script_fp = NULL;
|
||
int use_tty, keep_line;
|
||
struct {
|
||
int collecting;
|
||
loopline_t head;
|
||
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);
|
||
log_set_prefix ("gpg-connect-agent", 1);
|
||
|
||
/* Make sure that our subsystems are ready. */
|
||
i18n_init();
|
||
init_common_subsystems ();
|
||
|
||
assuan_set_assuan_err_source (0);
|
||
|
||
|
||
opt.homedir = default_homedir ();
|
||
opt.connect_flags = 1; /* Use extended connect mode. */
|
||
|
||
/* Parse the command line. */
|
||
pargs.argc = &argc;
|
||
pargs.argv = &argv;
|
||
pargs.flags = 1; /* Do not remove the args. */
|
||
while (!no_more_options && optfile_parse (NULL, NULL, NULL, &pargs, opts))
|
||
{
|
||
switch (pargs.r_opt)
|
||
{
|
||
case oQuiet: opt.quiet = 1; break;
|
||
case oVerbose: opt.verbose++; break;
|
||
case oNoVerbose: opt.verbose = 0; break;
|
||
case oHomedir: opt.homedir = pargs.r.ret_str; break;
|
||
case oHex: opt.hex = 1; break;
|
||
case oDecode: opt.decode = 1; break;
|
||
case oRawSocket: opt.raw_socket = pargs.r.ret_str; break;
|
||
case oExec: opt.exec = 1; break;
|
||
case oNoExtConnect: opt.connect_flags &= ~(1); break;
|
||
case oRun: opt_run = pargs.r.ret_str; break;
|
||
case oSubst:
|
||
opt.enable_varsubst = 1;
|
||
opt.trim_leading_spaces = 1;
|
||
break;
|
||
|
||
default: pargs.err = 2; break;
|
||
}
|
||
}
|
||
|
||
if (log_get_errorcount (0))
|
||
exit (2);
|
||
|
||
use_tty = (isatty ( fileno (stdin)) && isatty (fileno (stdout)));
|
||
|
||
if (opt.exec)
|
||
{
|
||
if (!argc)
|
||
{
|
||
log_error (_("option \"%s\" requires a program "
|
||
"and optional arguments\n"), "--exec" );
|
||
exit (1);
|
||
}
|
||
}
|
||
else if (argc)
|
||
cmdline_commands = argv;
|
||
|
||
if (opt.exec && opt.raw_socket)
|
||
log_info (_("option \"%s\" ignored due to \"%s\"\n"),
|
||
"--raw-socket", "--exec");
|
||
|
||
if (opt_run && !(script_fp = fopen (opt_run, "r")))
|
||
{
|
||
log_error ("cannot open run file `%s': %s\n",
|
||
opt_run, strerror (errno));
|
||
exit (1);
|
||
}
|
||
|
||
|
||
if (opt.exec)
|
||
{
|
||
int no_close[3];
|
||
|
||
no_close[0] = fileno (stderr);
|
||
no_close[1] = log_get_fd ();
|
||
no_close[2] = -1;
|
||
rc = assuan_pipe_connect_ext (&ctx, *argv, (const char **)argv,
|
||
no_close, NULL, NULL,
|
||
opt.connect_flags);
|
||
if (rc)
|
||
{
|
||
log_error ("assuan_pipe_connect_ext failed: %s\n",
|
||
gpg_strerror (rc));
|
||
exit (1);
|
||
}
|
||
|
||
if (opt.verbose)
|
||
log_info ("server `%s' started\n", *argv);
|
||
|
||
}
|
||
else if (opt.raw_socket)
|
||
{
|
||
rc = assuan_socket_connect_ext (&ctx, opt.raw_socket, 0,
|
||
opt.connect_flags);
|
||
if (rc)
|
||
{
|
||
log_error ("can't connect to socket `%s': %s\n",
|
||
opt.raw_socket, gpg_strerror (rc));
|
||
exit (1);
|
||
}
|
||
|
||
if (opt.verbose)
|
||
log_info ("connection to socket `%s' established\n", opt.raw_socket);
|
||
}
|
||
else
|
||
ctx = start_agent ();
|
||
|
||
/* See whether there is a line pending from the server (in case
|
||
assuan did not run the initial handshaking). */
|
||
if (assuan_pending_line (ctx))
|
||
{
|
||
rc = read_and_print_response (ctx, &cmderr);
|
||
if (rc)
|
||
log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) );
|
||
}
|
||
|
||
|
||
for (loopidx=0; loopidx < DIM (loopstack); loopidx++)
|
||
loopstack[loopidx].collecting = 0;
|
||
loopidx = -1;
|
||
line = NULL;
|
||
linesize = 0;
|
||
keep_line = 1;
|
||
for (;;)
|
||
{
|
||
int n;
|
||
size_t maxlength = 2048;
|
||
|
||
assert (loopidx < (int)DIM (loopstack));
|
||
if (loopidx >= 0 && loopstack[loopidx].current)
|
||
{
|
||
keep_line = 0;
|
||
xfree (line);
|
||
line = xstrdup (loopstack[loopidx].current->line);
|
||
n = strlen (line);
|
||
/* Never go beyond of the final /end. */
|
||
if (loopstack[loopidx].current->next)
|
||
loopstack[loopidx].current = loopstack[loopidx].current->next;
|
||
else if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4)))
|
||
;
|
||
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;
|
||
xfree (line);
|
||
line = tty_get ("> ");
|
||
n = strlen (line);
|
||
if (n==1 && *line == CONTROL_D)
|
||
n = 0;
|
||
if (n >= maxlength)
|
||
maxlength = 0;
|
||
}
|
||
else
|
||
{
|
||
if (!keep_line)
|
||
{
|
||
xfree (line);
|
||
line = NULL;
|
||
linesize = 0;
|
||
keep_line = 1;
|
||
}
|
||
n = read_line (script_fp? script_fp:stdin,
|
||
&line, &linesize, &maxlength);
|
||
}
|
||
if (n < 0)
|
||
{
|
||
log_error (_("error reading input: %s\n"), strerror (errno));
|
||
if (script_fp)
|
||
{
|
||
fclose (script_fp);
|
||
script_fp = NULL;
|
||
log_error ("stopping script execution\n");
|
||
continue;
|
||
}
|
||
exit (1);
|
||
}
|
||
if (!n)
|
||
{
|
||
/* EOF */
|
||
if (script_fp)
|
||
{
|
||
fclose (script_fp);
|
||
script_fp = NULL;
|
||
if (opt.verbose)
|
||
log_info ("end of script\n");
|
||
continue;
|
||
}
|
||
break;
|
||
}
|
||
if (!maxlength)
|
||
{
|
||
log_error (_("line too long - skipped\n"));
|
||
continue;
|
||
}
|
||
if (memchr (line, 0, n))
|
||
log_info (_("line shortened due to embedded Nul character\n"));
|
||
if (line[n-1] == '\n')
|
||
line[n-1] = 0;
|
||
|
||
if (opt.trim_leading_spaces)
|
||
{
|
||
const char *s = line;
|
||
|
||
while (spacep (s))
|
||
s++;
|
||
if (s != line)
|
||
{
|
||
for (p=line; *s;)
|
||
*p++ = *s++;
|
||
*p = 0;
|
||
n = p - line;
|
||
}
|
||
}
|
||
|
||
if (loopidx+1 >= 0 && loopstack[loopidx+1].collecting)
|
||
{
|
||
loopline_t ll;
|
||
|
||
ll = xmalloc (sizeof *ll + strlen (line));
|
||
ll->next = NULL;
|
||
strcpy (ll->line, line);
|
||
*loopstack[loopidx+1].tail = ll;
|
||
loopstack[loopidx+1].tail = &ll->next;
|
||
|
||
if (!strncmp (line, "/end", 4) && (!line[4]||spacep(line+4)))
|
||
loopstack[loopidx+1].nestlevel--;
|
||
else if (!strncmp (line, "/while", 6) && (!line[6]||spacep(line+6)))
|
||
loopstack[loopidx+1].nestlevel++;
|
||
|
||
if (loopstack[loopidx+1].nestlevel)
|
||
continue;
|
||
/* We reached the corresponding /end. */
|
||
loopstack[loopidx+1].collecting = 0;
|
||
loopidx++;
|
||
}
|
||
|
||
if (*line == '/')
|
||
{
|
||
/* Handle control commands. */
|
||
char *cmd = line+1;
|
||
|
||
for (p=cmd; *p && !spacep (p); p++)
|
||
;
|
||
if (*p)
|
||
*p++ = 0;
|
||
while (spacep (p))
|
||
p++;
|
||
if (!strcmp (cmd, "let"))
|
||
{
|
||
assign_variable (p, 0);
|
||
}
|
||
else if (!strcmp (cmd, "slet"))
|
||
{
|
||
/* Deprecated - never used in a released version. */
|
||
assign_variable (p, 1);
|
||
}
|
||
else if (!strcmp (cmd, "showvar"))
|
||
{
|
||
show_variables ();
|
||
}
|
||
else if (!strcmp (cmd, "definq"))
|
||
{
|
||
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
|
||
if (tmpline)
|
||
{
|
||
add_definq (tmpline, 1, 0);
|
||
xfree (tmpline);
|
||
}
|
||
else
|
||
add_definq (p, 1, 0);
|
||
}
|
||
else if (!strcmp (cmd, "definqfile"))
|
||
{
|
||
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
|
||
if (tmpline)
|
||
{
|
||
add_definq (tmpline, 0, 0);
|
||
xfree (tmpline);
|
||
}
|
||
else
|
||
add_definq (p, 0, 0);
|
||
}
|
||
else if (!strcmp (cmd, "definqprog"))
|
||
{
|
||
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
|
||
if (tmpline)
|
||
{
|
||
add_definq (tmpline, 0, 1);
|
||
xfree (tmpline);
|
||
}
|
||
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 ();
|
||
}
|
||
else if (!strcmp (cmd, "cleardef"))
|
||
{
|
||
clear_definq ();
|
||
}
|
||
else if (!strcmp (cmd, "echo"))
|
||
{
|
||
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
|
||
if (tmpline)
|
||
{
|
||
puts (tmpline);
|
||
xfree (tmpline);
|
||
}
|
||
else
|
||
puts (p);
|
||
}
|
||
else if (!strcmp (cmd, "sendfd"))
|
||
{
|
||
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
|
||
if (tmpline)
|
||
{
|
||
do_sendfd (ctx, tmpline);
|
||
xfree (tmpline);
|
||
}
|
||
else
|
||
do_sendfd (ctx, p);
|
||
continue;
|
||
}
|
||
else if (!strcmp (cmd, "recvfd"))
|
||
{
|
||
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
|
||
if (tmpline)
|
||
{
|
||
do_recvfd (ctx, tmpline);
|
||
xfree (tmpline);
|
||
}
|
||
else
|
||
do_recvfd (ctx, p);
|
||
continue;
|
||
}
|
||
else if (!strcmp (cmd, "open"))
|
||
{
|
||
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
|
||
if (tmpline)
|
||
{
|
||
do_open (tmpline);
|
||
xfree (tmpline);
|
||
}
|
||
else
|
||
do_open (p);
|
||
}
|
||
else if (!strcmp (cmd, "close"))
|
||
{
|
||
tmpline = opt.enable_varsubst? substitute_line (p) : NULL;
|
||
if (tmpline)
|
||
{
|
||
do_close (tmpline);
|
||
xfree (tmpline);
|
||
}
|
||
else
|
||
do_close (p);
|
||
}
|
||
else if (!strcmp (cmd, "showopen"))
|
||
{
|
||
do_showopen ();
|
||
}
|
||
else if (!strcmp (cmd, "serverpid"))
|
||
{
|
||
do_serverpid (ctx);
|
||
}
|
||
else if (!strcmp (cmd, "hex"))
|
||
opt.hex = 1;
|
||
else if (!strcmp (cmd, "nohex"))
|
||
opt.hex = 0;
|
||
else if (!strcmp (cmd, "decode"))
|
||
opt.decode = 1;
|
||
else if (!strcmp (cmd, "nodecode"))
|
||
opt.decode = 0;
|
||
else if (!strcmp (cmd, "subst"))
|
||
{
|
||
opt.enable_varsubst = 1;
|
||
opt.trim_leading_spaces = 1;
|
||
}
|
||
else if (!strcmp (cmd, "nosubst"))
|
||
opt.enable_varsubst = 0;
|
||
else if (!strcmp (cmd, "run"))
|
||
{
|
||
char *p2;
|
||
|
||
for (p2=p; *p2 && !spacep (p2); p2++)
|
||
;
|
||
if (*p2)
|
||
*p2++ = 0;
|
||
while (spacep (p2))
|
||
p++;
|
||
if (*p2)
|
||
{
|
||
log_error ("syntax error in run command\n");
|
||
if (script_fp)
|
||
{
|
||
fclose (script_fp);
|
||
script_fp = NULL;
|
||
}
|
||
}
|
||
else if (script_fp)
|
||
{
|
||
log_error ("cannot nest run commands - stop\n");
|
||
fclose (script_fp);
|
||
script_fp = NULL;
|
||
}
|
||
else if (!(script_fp = fopen (p, "r")))
|
||
{
|
||
log_error ("cannot open run file `%s': %s\n",
|
||
p, strerror (errno));
|
||
}
|
||
else if (opt.verbose)
|
||
log_info ("running commands from `%s'\n", p);
|
||
}
|
||
else if (!strcmp (cmd, "while"))
|
||
{
|
||
if (loopidx+2 >= (int)DIM(loopstack))
|
||
{
|
||
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. */
|
||
}
|
||
else
|
||
{
|
||
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 = 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)
|
||
log_error ("stray /end command encountered - ignored\n");
|
||
else
|
||
{
|
||
char *tmpcond;
|
||
const char *value;
|
||
long condition;
|
||
|
||
/* 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);
|
||
xfree (tmpline);
|
||
xfree (tmpcond);
|
||
|
||
if (condition)
|
||
{
|
||
/* Run loop. */
|
||
loopstack[loopidx].current = loopstack[loopidx].head;
|
||
}
|
||
else
|
||
{
|
||
/* Cleanup. */
|
||
while (loopstack[loopidx].head)
|
||
{
|
||
loopline_t tmp = loopstack[loopidx].head->next;
|
||
xfree (loopstack[loopidx].head);
|
||
loopstack[loopidx].head = tmp;
|
||
}
|
||
loopstack[loopidx].tail = NULL;
|
||
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--;
|
||
}
|
||
}
|
||
}
|
||
else if (!strcmp (cmd, "bye"))
|
||
{
|
||
break;
|
||
}
|
||
else if (!strcmp (cmd, "sleep"))
|
||
{
|
||
gnupg_sleep (1);
|
||
}
|
||
else if (!strcmp (cmd, "help"))
|
||
{
|
||
puts (
|
||
"Available commands:\n"
|
||
"/echo ARGS Echo ARGS.\n"
|
||
"/let NAME VALUE Set variable NAME to VALUE.\n"
|
||
"/showvar Show all variables.\n"
|
||
"/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"
|
||
"/recvfd Receive FD from server and print.\n"
|
||
"/open VAR FILE MODE Open FILE and assign the file descriptor to VAR.\n"
|
||
"/close FD Close file with descriptor FD.\n"
|
||
"/showopen Show descriptors of all open files.\n"
|
||
"/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 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 or condition\n"
|
||
"/bye Terminate gpg-connect-agent.\n"
|
||
"/help Print this help.");
|
||
}
|
||
else
|
||
log_error (_("unknown command `%s'\n"), cmd );
|
||
|
||
continue;
|
||
}
|
||
|
||
if (opt.verbose && script_fp)
|
||
puts (line);
|
||
|
||
tmpline = opt.enable_varsubst? substitute_line (line) : NULL;
|
||
if (tmpline)
|
||
{
|
||
rc = assuan_write_line (ctx, tmpline);
|
||
xfree (tmpline);
|
||
}
|
||
else
|
||
rc = assuan_write_line (ctx, line);
|
||
if (rc)
|
||
{
|
||
log_info (_("sending line failed: %s\n"), gpg_strerror (rc) );
|
||
break;
|
||
}
|
||
if (*line == '#' || !*line)
|
||
continue; /* Don't expect a response for a comment line. */
|
||
|
||
rc = read_and_print_response (ctx, &cmderr);
|
||
if (rc)
|
||
log_info (_("receiving line failed: %s\n"), gpg_strerror (rc) );
|
||
if ((rc || cmderr) && script_fp)
|
||
{
|
||
log_error ("stopping script execution\n");
|
||
fclose (script_fp);
|
||
script_fp = NULL;
|
||
}
|
||
|
||
|
||
/* FIXME: If the last command was BYE or the server died for
|
||
some other reason, we won't notice until we get the next
|
||
input command. Probing the connection with a non-blocking
|
||
read could help to notice termination or other problems
|
||
early. */
|
||
}
|
||
|
||
if (opt.verbose)
|
||
log_info ("closing connection to agent\n");
|
||
|
||
return 0;
|
||
}
|
||
|
||
|
||
/* Handle an Inquire from the server. Return False if it could not be
|
||
handled; in this case the caller shll complete the operation. LINE
|
||
is the complete line as received from the server. This function
|
||
may change the content of LINE. */
|
||
static int
|
||
handle_inquire (assuan_context_t ctx, char *line)
|
||
{
|
||
const char *name;
|
||
definq_t d;
|
||
FILE *fp = NULL;
|
||
char buffer[1024];
|
||
int rc, n;
|
||
|
||
/* Skip the command and trailing spaces. */
|
||
for (; *line && !spacep (line); line++)
|
||
;
|
||
while (spacep (line))
|
||
line++;
|
||
/* Get the name. */
|
||
name = line;
|
||
for (; *line && !spacep (line); line++)
|
||
;
|
||
if (*line)
|
||
*line++ = 0;
|
||
|
||
/* Now match it against our list. The second loop is there to
|
||
detect the match-all entry. */
|
||
for (d=definq_list; d; d = d->next)
|
||
if (d->name && !strcmp (d->name, name))
|
||
break;
|
||
if (!d)
|
||
for (d=definq_list; d; d = d->next)
|
||
if (!d->name)
|
||
break;
|
||
if (!d)
|
||
{
|
||
if (opt.verbose)
|
||
log_info ("no handler for inquiry `%s' found\n", name);
|
||
return 0;
|
||
}
|
||
|
||
if (d->is_var)
|
||
{
|
||
char *tmpvalue = get_var_ext (d->file);
|
||
rc = assuan_send_data (ctx, tmpvalue, strlen (tmpvalue));
|
||
xfree (tmpvalue);
|
||
if (rc)
|
||
log_error ("sending data back failed: %s\n", gpg_strerror (rc) );
|
||
}
|
||
else
|
||
{
|
||
if (d->is_prog)
|
||
{
|
||
fp = popen (d->file, "r");
|
||
if (!fp)
|
||
log_error ("error executing `%s': %s\n",
|
||
d->file, strerror (errno));
|
||
else if (opt.verbose)
|
||
log_error ("handling inquiry `%s' by running `%s'\n",
|
||
name, d->file);
|
||
}
|
||
else
|
||
{
|
||
fp = fopen (d->file, "rb");
|
||
if (!fp)
|
||
log_error ("error opening `%s': %s\n", d->file, strerror (errno));
|
||
else if (opt.verbose)
|
||
log_error ("handling inquiry `%s' by returning content of `%s'\n",
|
||
name, d->file);
|
||
}
|
||
if (!fp)
|
||
return 0;
|
||
|
||
while ( (n = fread (buffer, 1, sizeof buffer, fp)) )
|
||
{
|
||
rc = assuan_send_data (ctx, buffer, n);
|
||
if (rc)
|
||
{
|
||
log_error ("sending data back failed: %s\n", gpg_strerror (rc) );
|
||
break;
|
||
}
|
||
}
|
||
if (ferror (fp))
|
||
log_error ("error reading from `%s': %s\n", d->file, strerror (errno));
|
||
}
|
||
|
||
rc = assuan_send_data (ctx, NULL, 0);
|
||
if (rc)
|
||
log_error ("sending data back failed: %s\n", gpg_strerror (rc) );
|
||
|
||
if (d->is_var)
|
||
;
|
||
else if (d->is_prog)
|
||
{
|
||
if (pclose (fp))
|
||
log_error ("error running `%s': %s\n", d->file, strerror (errno));
|
||
}
|
||
else
|
||
fclose (fp);
|
||
return 1;
|
||
}
|
||
|
||
|
||
/* Read all response lines from server and print them. Returns 0 on
|
||
success or an assuan error code. Set R_GOTERR to true if the
|
||
command did not returned OK. */
|
||
static int
|
||
read_and_print_response (assuan_context_t ctx, int *r_goterr)
|
||
{
|
||
char *line;
|
||
size_t linelen;
|
||
assuan_error_t rc;
|
||
int i, j;
|
||
int need_lf = 0;
|
||
|
||
*r_goterr = 0;
|
||
for (;;)
|
||
{
|
||
do
|
||
{
|
||
rc = assuan_read_line (ctx, &line, &linelen);
|
||
if (rc)
|
||
return rc;
|
||
|
||
if (opt.verbose > 1 && *line == '#')
|
||
{
|
||
fwrite (line, linelen, 1, stdout);
|
||
putchar ('\n');
|
||
}
|
||
}
|
||
while (*line == '#' || !linelen);
|
||
|
||
if (linelen >= 1
|
||
&& line[0] == 'D' && line[1] == ' ')
|
||
{
|
||
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; )
|
||
{
|
||
int save_i = i;
|
||
|
||
printf ("D[%04X] ", i-2);
|
||
for (j=0; j < 16 ; j++, i++)
|
||
{
|
||
if (j == 8)
|
||
putchar (' ');
|
||
if (i < linelen)
|
||
printf (" %02X", ((unsigned char*)line)[i]);
|
||
else
|
||
fputs (" ", stdout);
|
||
}
|
||
fputs (" ", stdout);
|
||
i= save_i;
|
||
for (j=0; j < 16; j++, i++)
|
||
{
|
||
unsigned int c = ((unsigned char*)line)[i];
|
||
if ( i >= linelen )
|
||
putchar (' ');
|
||
else if (isascii (c) && isprint (c) && !iscntrl (c))
|
||
putchar (c);
|
||
else
|
||
putchar ('.');
|
||
}
|
||
putchar ('\n');
|
||
}
|
||
}
|
||
else if (opt.decode)
|
||
{
|
||
const unsigned char *s;
|
||
int need_d = 1;
|
||
int c = 0;
|
||
|
||
for (j=2, s=(unsigned char*)line+2; j < linelen; j++, s++ )
|
||
{
|
||
if (need_d)
|
||
{
|
||
fputs ("D ", stdout);
|
||
need_d = 0;
|
||
}
|
||
if (*s == '%' && j+2 < linelen)
|
||
{
|
||
s++; j++;
|
||
c = xtoi_2 ( s );
|
||
s++; j++;
|
||
}
|
||
else
|
||
c = *s;
|
||
if (c == '\n')
|
||
need_d = 1;
|
||
putchar (c);
|
||
}
|
||
need_lf = (c != '\n');
|
||
}
|
||
else
|
||
{
|
||
fwrite (line, linelen, 1, stdout);
|
||
putchar ('\n');
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (need_lf)
|
||
{
|
||
if (!current_datasink || current_datasink != stdout)
|
||
putchar ('\n');
|
||
need_lf = 0;
|
||
}
|
||
|
||
if (linelen >= 1
|
||
&& line[0] == 'S'
|
||
&& (line[1] == '\0' || line[1] == ' '))
|
||
{
|
||
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] == ' '))
|
||
{
|
||
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] == ' '))
|
||
{
|
||
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;
|
||
}
|
||
else if (linelen >= 7
|
||
&& line[0] == 'I' && line[1] == 'N' && line[2] == 'Q'
|
||
&& line[3] == 'U' && line[4] == 'I' && line[5] == 'R'
|
||
&& line[6] == 'E'
|
||
&& (line[7] == '\0' || line[7] == ' '))
|
||
{
|
||
if (!current_datasink || current_datasink != stdout)
|
||
{
|
||
fwrite (line, linelen, 1, stdout);
|
||
putchar ('\n');
|
||
}
|
||
if (!handle_inquire (ctx, line))
|
||
assuan_write_line (ctx, "CANCEL");
|
||
}
|
||
else if (linelen >= 3
|
||
&& line[0] == 'E' && line[1] == 'N' && line[2] == 'D'
|
||
&& (line[3] == '\0' || line[3] == ' '))
|
||
{
|
||
if (!current_datasink || current_datasink != stdout)
|
||
{
|
||
fwrite (line, linelen, 1, stdout);
|
||
putchar ('\n');
|
||
}
|
||
/* Received from server, thus more responses are expected. */
|
||
}
|
||
else
|
||
return gpg_error (GPG_ERR_ASS_INV_RESPONSE);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
/* Connect to the agent and send the standard options. */
|
||
static assuan_context_t
|
||
start_agent (void)
|
||
{
|
||
int rc = 0;
|
||
char *infostr, *p;
|
||
assuan_context_t ctx;
|
||
|
||
infostr = getenv ("GPG_AGENT_INFO");
|
||
if (!infostr || !*infostr)
|
||
{
|
||
char *sockname;
|
||
|
||
/* 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
|
||
{
|
||
int prot;
|
||
int pid;
|
||
|
||
infostr = xstrdup (infostr);
|
||
if ( !(p = strchr (infostr, PATHSEP_C)) || p == infostr)
|
||
{
|
||
log_error (_("malformed GPG_AGENT_INFO environment variable\n"));
|
||
xfree (infostr);
|
||
exit (1);
|
||
}
|
||
*p++ = 0;
|
||
pid = atoi (p);
|
||
while (*p && *p != PATHSEP_C)
|
||
p++;
|
||
prot = *p? atoi (p+1) : 0;
|
||
if (prot != 1)
|
||
{
|
||
log_error (_("gpg-agent protocol version %d is not supported\n"),
|
||
prot);
|
||
xfree (infostr);
|
||
exit (1);
|
||
}
|
||
|
||
rc = assuan_socket_connect (&ctx, infostr, pid);
|
||
xfree (infostr);
|
||
}
|
||
|
||
if (rc)
|
||
{
|
||
log_error ("can't connect to the agent: %s\n", gpg_strerror (rc));
|
||
exit (1);
|
||
}
|
||
|
||
if (opt.verbose)
|
||
log_info ("connection to agent established\n");
|
||
|
||
rc = assuan_transact (ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
|
||
if (rc)
|
||
{
|
||
log_error (_("error sending %s command: %s\n"), "RESET",
|
||
gpg_strerror (rc));
|
||
exit (1);
|
||
}
|
||
|
||
rc = send_pinentry_environment (ctx, GPG_ERR_SOURCE_DEFAULT,
|
||
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||
if (rc)
|
||
{
|
||
log_error (_("error sending standard options: %s\n"), gpg_strerror (rc));
|
||
exit (1);
|
||
}
|
||
|
||
return ctx;
|
||
}
|