/* gpg-connect-agent.c - Tool to connect to the agent. * Copyright (C) 2005, 2007 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 . */ #include #include #include #include #include #include #include #include #include "i18n.h" #include "../common/util.h" #include "../common/asshelp.h" #include "../common/sysutils.h" #include "../common/membuf.h" #include "../common/ttyio.h" #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[] = { { 301, NULL, 0, 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} }; /* 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. */ } 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; /* This is used to store the pid of the server. */ static pid_t server_pid = (pid_t)(-1); /* 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; } /* Extended version of get_var. This returns a malloced string and understand the fucntion 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. 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; 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")) { char numbuf[30]; snprintf (numbuf, sizeof numbuf, "%d", (int)server_pid); result = xstrdup (numbuf); } 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 { 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) { 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, last_was_tty; gnupg_rl_initialize (); set_strusage (my_strusage); log_set_prefix ("gpg-connect-agent", 1); /* Make sure that our subsystems are ready. */ init_common_subsystems (); assuan_set_assuan_err_source (0); i18n_init(); 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; 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) usage (1); 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) ); } line = NULL; linesize = 0; last_was_tty = 0; for (;;) { int n; size_t maxlength; maxlength = 2048; if (use_tty && !script_fp) { last_was_tty = 1; line = tty_get ("> "); n = strlen (line); if (n==1 && *line == CONTROL_D) n = 0; if (n >= maxlength) maxlength = 0; } else { if (last_was_tty) { xfree (line); line = NULL; linesize = 0; last_was_tty = 0; } 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 (*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, "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; 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, "bye")) { break; } 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" "/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 varibale substitution.\n" "/run FILE Run commands from FILE.\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. he second loop is todetect 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 (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) { putchar ('\n'); need_lf = 0; } if (linelen >= 1 && line[0] == 'S' && (line[1] == '\0' || line[1] == ' ')) { 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'); 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'); *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] == ' ')) { 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] == ' ')) { 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); 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); if (rc) { log_error (_("error sending standard options: %s\n"), gpg_strerror (rc)); exit (1); } return ctx; }