/* Attach an existing connected file descriptor FD to an allocated handle CTX * and initialize the connection. */ gpg_error_t assuan_socket_connect_fd (assuan_context_t ctx, int fd, unsigned int flags) { gpg_error_t err; assuan_fd_t afd; if (!ctx || fd < 0) return GPG_ERR_INV_ARG; afd = assuan_fd_from_posix_fd (fd); if (afd == ASSUAN_INVALID_FD) return GPG_ERR_INV_ARG; err = _assuan_connect_finalize(ctx, afd, flags); if (err) _assuan_reset (ctx); return err; }
int __assuan_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name, const char **argv, assuan_fd_t fd_in, assuan_fd_t fd_out, assuan_fd_t *fd_child_list, void (*atfork) (void *opaque, int reserved), void *atforkvalue, unsigned int flags) { SECURITY_ATTRIBUTES sec_attr; PROCESS_INFORMATION pi = { NULL, /* Returns process handle. */ 0, /* Returns primary thread handle. */ 0, /* Returns pid. */ 0 /* Returns tid. */ }; STARTUPINFO si; assuan_fd_t fd; assuan_fd_t *fdp; char *cmdline; HANDLE nullfd = INVALID_HANDLE_VALUE; /* fixme: Actually we should set the "_assuan_pipe_connect_pid" env variable. However this requires us to write a full environment handler, because the strings are expected in sorted order. The suggestion given in the MS Reference Library, to save the old value, change it, create process and restore it, is not thread safe. */ /* Build the command line. */ if (build_w32_commandline (ctx, argv, &cmdline)) return -1; /* Start the process. */ memset (&sec_attr, 0, sizeof sec_attr); sec_attr.nLength = sizeof sec_attr; sec_attr.bInheritHandle = FALSE; memset (&si, 0, sizeof si); si.cb = sizeof (si); si.dwFlags = STARTF_USESTDHANDLES; /* FIXME: Dup to nul if ASSUAN_INVALID_FD. */ si.hStdInput = fd_in; si.hStdOutput = fd_out; /* Dup stderr to /dev/null unless it is in the list of FDs to be passed to the child. */ fd = assuan_fd_from_posix_fd (fileno (stderr)); fdp = fd_child_list; if (fdp) { for (; *fdp != ASSUAN_INVALID_FD && *fdp != fd; fdp++) ; } if (!fdp || *fdp == ASSUAN_INVALID_FD) { nullfd = CreateFileW (L"nul", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (nullfd == INVALID_HANDLE_VALUE) { TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx, "can't open `nul': %s", _assuan_w32_strerror (ctx, -1)); _assuan_free (ctx, cmdline); gpg_err_set_errno (EIO); return -1; } si.hStdError = nullfd; } else si.hStdError = fd; /* Note: We inherit all handles flagged as inheritable. This seems to be a security flaw but there seems to be no way of selecting handles to inherit. A fix for this would be to use a helper process like we have in gpgme. */ /* _assuan_log_printf ("CreateProcess, path=`%s' cmdline=`%s'\n", */ /* name, cmdline); */ if (!CreateProcess (name, /* Program to start. */ cmdline, /* Command line arguments. */ &sec_attr, /* Process security attributes. */ &sec_attr, /* Thread security attributes. */ TRUE, /* Inherit handles. */ (CREATE_DEFAULT_ERROR_MODE | ((flags & 128)? DETACHED_PROCESS : 0) | GetPriorityClass (GetCurrentProcess ()) | CREATE_SUSPENDED), /* Creation flags. */ NULL, /* Environment. */ NULL, /* Use current drive/directory. */ &si, /* Startup information. */ &pi /* Returns process information. */ )) { TRACE1 (ctx, ASSUAN_LOG_SYSIO, "pipe_connect_w32", ctx, "CreateProcess failed: %s", _assuan_w32_strerror (ctx, -1)); _assuan_free (ctx, cmdline); if (nullfd != INVALID_HANDLE_VALUE) CloseHandle (nullfd); gpg_err_set_errno (EIO); return -1; } _assuan_free (ctx, cmdline); if (nullfd != INVALID_HANDLE_VALUE) CloseHandle (nullfd); ResumeThread (pi.hThread); CloseHandle (pi.hThread); /* _assuan_log_printf ("CreateProcess ready: hProcess=%p hThread=%p" */ /* " dwProcessID=%d dwThreadId=%d\n", */ /* pi.hProcess, pi.hThread, */ /* (int) pi.dwProcessId, (int) pi.dwThreadId); */ *r_pid = (pid_t) pi.hProcess; /* No need to modify peer process, as we don't change the handle names. However this also means we are not safe, as we inherit too many handles. Should use approach similar to gpgme and glib using a helper process. */ return 0; }
/* Fire up a new GPG. Handle the server's initial greeting. Returns 0 on success and stores the assuan context at R_CTX. */ static gpg_error_t start_gpg (ctrl_t ctrl, const char *gpg_program, strlist_t gpg_arguments, int input_fd, int output_fd, assuan_context_t *r_ctx) { gpg_error_t err; assuan_context_t ctx = NULL; const char *pgmname; const char **argv; assuan_fd_t no_close_list[5]; int i; char line[ASSUAN_LINELENGTH]; (void)ctrl; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (err)); return err; } /* The first time we are used, intialize the gpg_program variable. */ if ( !gpg_program || !*gpg_program ) gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); /* Compute argv[0]. */ if ( !(pgmname = strrchr (gpg_program, '/'))) pgmname = gpg_program; else pgmname++; if (fflush (NULL)) { err = my_error_from_syserror (); log_error ("error flushing pending output: %s\n", gpg_strerror (err)); return err; } argv = xtrycalloc (strlist_length (gpg_arguments) + 3, sizeof *argv); if (argv == NULL) { err = my_error_from_syserror (); return err; } i = 0; argv[i++] = pgmname; argv[i++] = "--server"; for (; gpg_arguments; gpg_arguments = gpg_arguments->next) argv[i++] = gpg_arguments->d; argv[i++] = NULL; i = 0; if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); if (input_fd != -1) no_close_list[i++] = assuan_fd_from_posix_fd (input_fd); if (output_fd != -1) no_close_list[i++] = assuan_fd_from_posix_fd (output_fd); no_close_list[i] = ASSUAN_INVALID_FD; /* Connect to GPG and perform initial handshaking. */ err = assuan_pipe_connect (ctx, gpg_program, argv, no_close_list, NULL, NULL, 0); if (err) { assuan_release (ctx); log_error ("can't connect to GPG: %s\n", gpg_strerror (err)); return gpg_error (GPG_ERR_NO_ENGINE); } if (input_fd != -1) { snprintf (line, sizeof line, "INPUT FD=%d", input_fd); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { assuan_release (ctx); log_error ("error sending INPUT command: %s\n", gpg_strerror (err)); return err; } } if (output_fd != -1) { snprintf (line, sizeof line, "OUTPUT FD=%d", output_fd); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { assuan_release (ctx); log_error ("error sending OUTPUT command: %s\n", gpg_strerror (err)); return err; } } *r_ctx = ctx; return 0; }
/* Try to connect to the dirmngr via socket or fork it off and work by pipes. Handle the server's initial greeting */ static assuan_context_t start_dirmngr (int only_daemon) { int rc; char *infostr, *p; assuan_context_t ctx; int try_default = 0; infostr = opt.force_pipe_server? NULL : getenv ("DIRMNGR_INFO"); if (only_daemon && (!infostr || !*infostr)) { infostr = xstrdup (dirmngr_socket_name ()); try_default = 1; } rc = assuan_new (&ctx); if (rc) { log_error (_("failed to allocate assuan context: %s\n"), gpg_strerror (rc)); return NULL; } if (!infostr || !*infostr) { const char *pgmname; const char *argv[3]; int no_close_list[3]; int i; if (only_daemon) { log_error (_("apparently no running dirmngr\n")); return NULL; } if (opt.verbose) log_info (_("no running dirmngr - starting one\n")); if (!opt.dirmngr_program || !*opt.dirmngr_program) opt.dirmngr_program = "./dirmngr"; if ( !(pgmname = strrchr (opt.dirmngr_program, '/'))) pgmname = opt.dirmngr_program; else pgmname++; argv[0] = pgmname; argv[1] = "--server"; argv[2] = NULL; i=0; if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (es_fileno (es_stderr)); no_close_list[i] = -1; /* Connect to the agent and perform initial handshaking. */ rc = assuan_pipe_connect (ctx, opt.dirmngr_program, argv, no_close_list, NULL, NULL, 0); } else /* Connect to a daemon. */ { int prot; int pid; infostr = xstrdup (infostr); if (!try_default && *infostr) { if ( !(p = strchr (infostr, ':')) || p == infostr) { log_error (_("malformed DIRMNGR_INFO environment variable\n")); xfree (infostr); if (only_daemon) return NULL; /* Try again by starting a new instance. */ opt.force_pipe_server = 1; return start_dirmngr (0); } *p++ = 0; pid = atoi (p); while (*p && *p != ':') p++; prot = *p? atoi (p+1) : 0; if (prot != 1) { log_error (_("dirmngr protocol version %d is not supported\n"), prot); xfree (infostr); if (only_daemon) return NULL; opt.force_pipe_server = 1; return start_dirmngr (0); } } else pid = -1; rc = assuan_socket_connect (ctx, infostr, pid, 0); xfree (infostr); if (gpg_err_code(rc) == GPG_ERR_ASS_CONNECT_FAILED && !only_daemon) { log_error (_("can't connect to the dirmngr - trying fall back\n")); opt.force_pipe_server = 1; return start_dirmngr (0); } } if (rc) { assuan_release (ctx); log_error (_("can't connect to the dirmngr: %s\n"), gpg_strerror (rc)); return NULL; } return ctx; }
/* Try to connect to the agent via socket or fork it off and work by pipes. Handle the server's initial greeting. Returns a new assuan context at R_CTX or an error code. */ gpg_error_t start_new_gpg_agent (assuan_context_t *r_ctx, gpg_err_source_t errsource, const char *homedir, const char *agent_program, const char *opt_lc_ctype, const char *opt_lc_messages, session_env_t session_env, int verbose, int debug, gpg_error_t (*status_cb)(ctrl_t, int, ...), ctrl_t status_cb_arg) { /* If we ever failed to connect via a socket we will force the use of the pipe based server for the lifetime of the process. */ static int force_pipe_server = 0; gpg_error_t err = 0; char *infostr, *p; assuan_context_t ctx; int did_success_msg = 0; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("error allocating assuan context: %s\n", gpg_strerror (err)); return err; } restart: infostr = force_pipe_server? NULL : getenv ("GPG_AGENT_INFO"); if (!infostr || !*infostr) { char *sockname; const char *argv[3]; pid_t pid; int excode; /* First check whether we can connect at the standard socket. */ sockname = make_filename (homedir, "S.gpg-agent", NULL); err = assuan_socket_connect (ctx, sockname, 0, 0); if (err) { /* With no success start a new server. */ if (!agent_program || !*agent_program) agent_program = gnupg_module_name (GNUPG_MODULE_NAME_AGENT); if (verbose) log_info (_("no running gpg-agent - starting '%s'\n"), agent_program); if (status_cb) status_cb (status_cb_arg, STATUS_PROGRESS, "starting_agent ? 0 0", NULL); if (fflush (NULL)) { gpg_error_t tmperr = gpg_err_make (errsource, gpg_err_code_from_syserror ()); log_error ("error flushing pending output: %s\n", strerror (errno)); xfree (sockname); assuan_release (ctx); return tmperr; } argv[0] = "--use-standard-socket-p"; argv[1] = NULL; err = gnupg_spawn_process_fd (agent_program, argv, -1, -1, -1, &pid); if (err) log_debug ("starting '%s' for testing failed: %s\n", agent_program, gpg_strerror (err)); else if ((err = gnupg_wait_process (agent_program, pid, 1, &excode))) { if (excode == -1) log_debug ("running '%s' for testing failed (wait): %s\n", agent_program, gpg_strerror (err)); } gnupg_release_process (pid); if (!err && !excode) { /* If the agent has been configured for use with a standard socket, an environment variable is not required and thus we we can savely start the agent here. */ lock_spawn_t lock; argv[0] = "--daemon"; argv[1] = "--use-standard-socket"; argv[2] = NULL; if (!(err = lock_spawning (&lock, homedir, "agent", verbose)) && assuan_socket_connect (ctx, sockname, 0, 0)) { err = gnupg_spawn_process_detached (agent_program, argv,NULL); if (err) log_error ("failed to start agent '%s': %s\n", agent_program, gpg_strerror (err)); else { int i; for (i=0; i < SECS_TO_WAIT_FOR_AGENT; i++) { if (verbose) log_info (_("waiting for the agent " "to come up ... (%ds)\n"), SECS_TO_WAIT_FOR_AGENT - i); gnupg_sleep (1); err = assuan_socket_connect (ctx, sockname, 0, 0); if (!err) { if (verbose) { log_info (_("connection to agent " "established\n")); did_success_msg = 1; } break; } } } } unlock_spawning (&lock, "agent"); } else { /* If using the standard socket is not the default we start the agent as a pipe server which gives us most of the required features except for passphrase caching etc. */ const char *pgmname; int no_close_list[3]; int i; if ( !(pgmname = strrchr (agent_program, '/'))) pgmname = agent_program; else pgmname++; argv[0] = pgmname; argv[1] = "--server"; argv[2] = NULL; i=0; if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); no_close_list[i] = -1; /* Connect to the agent and perform initial handshaking. */ err = assuan_pipe_connect (ctx, agent_program, argv, no_close_list, NULL, NULL, 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); force_pipe_server = 1; goto restart; } *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); force_pipe_server = 1; goto restart; } err = assuan_socket_connect (ctx, infostr, pid, 0); xfree (infostr); if (gpg_err_code (err) == GPG_ERR_ASS_CONNECT_FAILED) { log_info (_("can't connect to the agent - trying fall back\n")); force_pipe_server = 1; goto restart; } } if (err) { log_error ("can't connect to the agent: %s\n", gpg_strerror (err)); assuan_release (ctx); return gpg_err_make (errsource, GPG_ERR_NO_AGENT); } if (debug && !did_success_msg) log_debug (_("connection to agent established\n")); err = assuan_transact (ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL); if (!err) err = send_pinentry_environment (ctx, errsource, opt_lc_ctype, opt_lc_messages, session_env); if (err) { assuan_release (ctx); return err; } *r_ctx = ctx; return 0; }
/* This is a compatibility function for GPGSM 2.0.0, which does not support the --data option with the EXPORT command. */ static gpg_error_t export_cert_compat (char *fpr, struct cert *cert) { gpg_error_t err; assuan_context_t ctx; const char *argv[] = { "gpgsm", "--server", NULL }; int got; #define COMMANDLINELEN 80 char cmd[COMMANDLINELEN]; int output_fds[2]; int child_fds[2]; #define MAX_CERT_SIZE 4096 cert->cert_der = malloc (MAX_CERT_SIZE); if (!cert->cert_der) return gpg_error_from_syserror (); if(pipe (output_fds) < 0) return gpg_error_from_syserror (); child_fds[0] = assuan_fd_from_posix_fd (output_fds[1]); child_fds[1] = -1; err = assuan_new (&ctx); if (err) { close (output_fds[0]); close (output_fds[1]); DEBUG (DBG_CRIT, "failed to allocate assuan context: %s\n", gpg_strerror (err)); return err; } err = assuan_pipe_connect (ctx, get_gpgsm_path (), argv, child_fds, NULL, NULL, 128); close (output_fds[1]); if (err) { close (output_fds[0]); assuan_release (ctx); DEBUG (DBG_CRIT, "failed to spawn %s\n", get_gpgsm_path ()); return err; } snprintf (cmd, sizeof (cmd), "OUTPUT FD=%i", output_fds[1]); err = assuan_transact (ctx, cmd, NULL, NULL, NULL, NULL, NULL, NULL); if (err) goto export_out; /* FIXME: This will only work if the certificate is small and fits into the pipe buffer completely!!! */ snprintf (cmd, sizeof (cmd), "EXPORT %s\n", cert->fpr); err = assuan_transact (ctx, cmd, NULL, NULL, NULL, NULL, NULL, NULL); if (err) goto export_out; do { got = read (output_fds[0], cert->cert_der + cert->cert_der_len, MAX_CERT_SIZE - cert->cert_der_len); if (got > 0) cert->cert_der_len += got; } while (!err && got > 0 && cert->cert_der_len < MAX_CERT_SIZE); if (got < 0 || cert->cert_der_len == MAX_CERT_SIZE) err = gpg_error (GPG_ERR_GENERAL); export_out: assuan_release (ctx); close (output_fds[0]); return err; }
/* Try to connect to the agent via socket or fork it off and work by pipes. Handle the server's initial greeting */ static int start_dirmngr_ext (ctrl_t ctrl, assuan_context_t *ctx_r) { int rc; char *infostr, *p; assuan_context_t ctx = NULL; int try_default = 0; if (opt.disable_dirmngr) return gpg_error (GPG_ERR_NO_DIRMNGR); if (*ctx_r) return 0; /* Note: if you change this to multiple connections, you also need to take care of the implicit option sending caching. */ #ifdef HAVE_W32_SYSTEM infostr = NULL; opt.prefer_system_dirmngr = 1; #else infostr = force_pipe_server? NULL : getenv ("DIRMNGR_INFO"); #endif /*HAVE_W32_SYSTEM*/ if (infostr && !*infostr) infostr = NULL; else if (infostr) infostr = xstrdup (infostr); if (opt.prefer_system_dirmngr && !force_pipe_server && !infostr) { infostr = xstrdup (dirmngr_socket_name ()); try_default = 1; } rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); return rc; } if (!infostr) { const char *pgmname; const char *argv[3]; int no_close_list[3]; int i; if (!opt.dirmngr_program || !*opt.dirmngr_program) opt.dirmngr_program = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR); if ( !(pgmname = strrchr (opt.dirmngr_program, '/'))) pgmname = opt.dirmngr_program; else pgmname++; if (opt.verbose) log_info (_("no running dirmngr - starting `%s'\n"), opt.dirmngr_program); if (fflush (NULL)) { gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); log_error ("error flushing pending output: %s\n", strerror (errno)); return tmperr; } argv[0] = pgmname; argv[1] = "--server"; argv[2] = NULL; i=0; if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); no_close_list[i] = -1; /* connect to the agent and perform initial handshaking */ rc = assuan_pipe_connect (ctx, opt.dirmngr_program, argv, no_close_list, NULL, NULL, 0); } else { int prot; int pid; if (!try_default) { if ( !(p = strchr (infostr, PATHSEP_C)) || p == infostr) { log_error (_("malformed DIRMNGR_INFO environment variable\n")); xfree (infostr); force_pipe_server = 1; return start_dirmngr_ext (ctrl, ctx_r); } *p++ = 0; pid = atoi (p); while (*p && *p != PATHSEP_C) p++; prot = *p? atoi (p+1) : 0; if (prot != 1) { log_error (_("dirmngr protocol version %d is not supported\n"), prot); xfree (infostr); force_pipe_server = 1; return start_dirmngr_ext (ctrl, ctx_r); } } else pid = -1; rc = assuan_socket_connect (ctx, infostr, pid, 0); #ifdef HAVE_W32_SYSTEM if (rc) log_debug ("connecting dirmngr at `%s' failed\n", infostr); #endif xfree (infostr); #ifndef HAVE_W32_SYSTEM if (gpg_err_code (rc) == GPG_ERR_ASS_CONNECT_FAILED) { log_info (_("can't connect to the dirmngr - trying fall back\n")); force_pipe_server = 1; return start_dirmngr_ext (ctrl, ctx_r); } #endif /*!HAVE_W32_SYSTEM*/ } prepare_dirmngr (ctrl, ctx, rc); if (rc) { assuan_release (ctx); log_error ("can't connect to the dirmngr: %s\n", gpg_strerror (rc)); return gpg_error (GPG_ERR_NO_DIRMNGR); } *ctx_r = ctx; if (DBG_ASSUAN) log_debug ("connection to dirmngr established\n"); return 0; }
/* Fire up a new GPG. Handle the server's initial greeting. Returns 0 on success and stores the assuan context at R_CTX. */ static gpg_error_t start_gpg (ctrl_t ctrl, int input_fd, int output_fd, assuan_context_t *r_ctx) { gpg_error_t err; assuan_context_t ctx = NULL; const char *pgmname; const char *argv[10]; int no_close_list[5]; int i; char line[ASSUAN_LINELENGTH]; (void)ctrl; *r_ctx = NULL; err = assuan_new (&ctx); if (err) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (err)); return err; } /* The first time we are used, intialize the gpg_program variable. */ if ( !opt.gpg_program || !*opt.gpg_program ) opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG); if (opt.verbose) log_info (_("no running gpg - starting '%s'\n"), opt.gpg_program); /* Compute argv[0]. */ if ( !(pgmname = strrchr (opt.gpg_program, '/'))) pgmname = opt.gpg_program; else pgmname++; if (fflush (NULL)) { err = gpg_error_from_syserror (); log_error ("error flushing pending output: %s\n", gpg_strerror (err)); return err; } i = 0; argv[i++] = pgmname; argv[i++] = "--server"; if ((opt.debug & 1024)) argv[i++] = "--debug=1024"; argv[i++] = "-z"; argv[i++] = "0"; argv[i++] = "--trust-model"; argv[i++] = "always"; argv[i++] = NULL; i = 0; if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); if (input_fd != -1) no_close_list[i++] = assuan_fd_from_posix_fd (input_fd); if (output_fd != -1) no_close_list[i++] = assuan_fd_from_posix_fd (output_fd); no_close_list[i] = -1; /* Connect to GPG and perform initial handshaking. */ err = assuan_pipe_connect (ctx, opt.gpg_program, argv, no_close_list, NULL, NULL, 0); if (err) { assuan_release (ctx); log_error ("can't connect to GPG: %s\n", gpg_strerror (err)); return gpg_error (GPG_ERR_NO_ENGINE); } if (input_fd != -1) { snprintf (line, sizeof line, "INPUT FD=%d", input_fd); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { assuan_release (ctx); log_error ("error sending INPUT command: %s\n", gpg_strerror (err)); return err; } } if (output_fd != -1) { snprintf (line, sizeof line, "OUTPUT FD=%d", output_fd); err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); if (err) { assuan_release (ctx); log_error ("error sending OUTPUT command: %s\n", gpg_strerror (err)); return err; } } *r_ctx = ctx; if (DBG_ASSUAN) log_debug ("connection to GPG established\n"); return 0; }
/* Fork off the pin entry if this has not already been done. Note, that this function must always be used to aquire the lock for the pinentry - we will serialize _all_ pinentry calls. */ static int start_pinentry (ctrl_t ctrl) { int rc = 0; const char *pgmname; assuan_context_t ctx; const char *argv[5]; int no_close_list[3]; int i; const char *tmpstr; unsigned long pinentry_pid; const char *value; struct timespec abstime; int err; npth_clock_gettime (&abstime); abstime.tv_sec += LOCK_TIMEOUT; err = npth_mutex_timedlock (&entry_lock, &abstime); if (err) { if (err == ETIMEDOUT) rc = gpg_error (GPG_ERR_TIMEOUT); else rc = gpg_error_from_errno (rc); log_error (_("failed to acquire the pinentry lock: %s\n"), gpg_strerror (rc)); return rc; } entry_owner = ctrl; if (entry_ctx) return 0; if (opt.verbose) log_info ("starting a new PIN Entry\n"); #ifdef HAVE_W32_SYSTEM fflush (stdout); fflush (stderr); #endif if (fflush (NULL)) { #ifndef HAVE_W32_SYSTEM gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); #endif log_error ("error flushing pending output: %s\n", strerror (errno)); /* At least Windows XP fails here with EBADF. According to docs and Wine an fflush(NULL) is the same as _flushall. However the Wime implementaion does not flush stdin,stdout and stderr - see above. Lets try to ignore the error. */ #ifndef HAVE_W32_SYSTEM return unlock_pinentry (tmperr); #endif } if (!opt.pinentry_program || !*opt.pinentry_program) opt.pinentry_program = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY); pgmname = opt.pinentry_program; if ( !(pgmname = strrchr (opt.pinentry_program, '/'))) pgmname = opt.pinentry_program; else pgmname++; /* OS X needs the entire file name in argv[0], so that it can locate the resource bundle. For other systems we stick to the usual convention of supplying only the name of the program. */ #ifdef __APPLE__ argv[0] = opt.pinentry_program; #else /*!__APPLE__*/ argv[0] = pgmname; #endif /*__APPLE__*/ if (!opt.keep_display && (value = session_env_getenv (ctrl->session_env, "DISPLAY"))) { argv[1] = "--display"; argv[2] = value; argv[3] = NULL; } else argv[1] = NULL; i=0; if (!opt.running_detached) { if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); } no_close_list[i] = -1; rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); return rc; } /* We don't want to log the pinentry communication to make the logs easier to read. We might want to add a new debug option to enable pinentry logging. */ #ifdef ASSUAN_NO_LOGGING assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1); #endif /* Connect to the pinentry and perform initial handshaking. Note that atfork is used to change the environment for pinentry. We start the server in detached mode to suppress the console window under Windows. */ rc = assuan_pipe_connect (ctx, opt.pinentry_program, argv, no_close_list, atfork_cb, ctrl, ASSUAN_PIPE_CONNECT_DETACHED); if (rc) { log_error ("can't connect to the PIN entry module '%s': %s\n", opt.pinentry_program, gpg_strerror (rc)); assuan_release (ctx); return unlock_pinentry (gpg_error (GPG_ERR_NO_PIN_ENTRY)); } entry_ctx = ctx; if (DBG_ASSUAN) log_debug ("connection to PIN entry established\n"); rc = assuan_transact (entry_ctx, opt.no_grab? "OPTION no-grab":"OPTION grab", NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (rc); value = session_env_getenv (ctrl->session_env, "GPG_TTY"); if (value) { char *optstr; if (asprintf (&optstr, "OPTION ttyname=%s", value) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } value = session_env_getenv (ctrl->session_env, "TERM"); if (value) { char *optstr; if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } if (ctrl->lc_ctype) { char *optstr; if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } if (ctrl->lc_messages) { char *optstr; if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 ) return unlock_pinentry (out_of_core ()); rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); if (rc) return unlock_pinentry (rc); } { /* Provide a few default strings for use by the pinentries. This may help a pinentry to avoid implementing localization code. */ static struct { const char *key, *value; } tbl[] = { /* TRANSLATORS: These are labels for buttons etc used in Pinentries. An underscore indicates that the next letter should be used as an accelerator. Double the underscore for a literal one. The actual to be translated text starts after the second vertical bar. */ { "ok", N_("|pinentry-label|_OK") }, { "cancel", N_("|pinentry-label|_Cancel") }, { "prompt", N_("|pinentry-label|PIN:") }, { NULL, NULL} }; char *optstr; int idx; const char *s, *s2; for (idx=0; tbl[idx].key; idx++) { s = _(tbl[idx].value); if (*s == '|' && (s2=strchr (s+1,'|'))) s = s2+1; if (asprintf (&optstr, "OPTION default-%s=%s", tbl[idx].key, s) < 0 ) return unlock_pinentry (out_of_core ()); assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); } } /* Tell the pinentry the name of a file it shall touch after having messed with the tty. This is optional and only supported by newer pinentries and thus we do no error checking. */ tmpstr = opt.pinentry_touch_file; if (tmpstr && !strcmp (tmpstr, "/dev/null")) tmpstr = NULL; else if (!tmpstr) tmpstr = get_agent_socket_name (); if (tmpstr) { char *optstr; if (asprintf (&optstr, "OPTION touch-file=%s", tmpstr ) < 0 ) ; else { assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL); xfree (optstr); } } /* Now ask the Pinentry for its PID. If the Pinentry is new enough it will send the pid back and we will use an inquire to notify our client. The client may answer the inquiry either with END or with CAN to cancel the pinentry. */ rc = assuan_transact (entry_ctx, "GETINFO pid", getinfo_pid_cb, &pinentry_pid, NULL, NULL, NULL, NULL); if (rc) { log_info ("You may want to update to a newer pinentry\n"); rc = 0; } else if (!rc && (pid_t)pinentry_pid == (pid_t)(-1)) log_error ("pinentry did not return a PID\n"); else { rc = agent_inq_pinentry_launched (ctrl, pinentry_pid); if (gpg_err_code (rc) == GPG_ERR_CANCELED || gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED) return unlock_pinentry (gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (rc))); rc = 0; } return 0; }
int __assuan_spawn (assuan_context_t ctx, pid_t *r_pid, const char *name, const char **argv, assuan_fd_t fd_in, assuan_fd_t fd_out, assuan_fd_t *fd_child_list, void (*atfork) (void *opaque, int reserved), void *atforkvalue, unsigned int flags) { PROCESS_INFORMATION pi = { NULL, /* Returns process handle. */ 0, /* Returns primary thread handle. */ 0, /* Returns pid. */ 0 /* Returns tid. */ }; assuan_fd_t fd; assuan_fd_t *fdp; assuan_fd_t fd_err; int fd_err_isnull = 0; char *cmdline; /* Dup stderr to /dev/null unless it is in the list of FDs to be passed to the child. Well we don't actually open nul because that is not available on Windows, but use our hack for it. Because an RVID of 0 is an invalid value and HANDLES will never have this value either, we test for this as well. */ /* FIXME: As long as we can't decide whether a handle is a real handler or an rendezvous id we can't do anything with the FD_CHILD_LIST. We can't do much with stderr either, thus we better don't pass stderr to the child at all. If we would do so and it is not a rendezvous id the client would run into problems. */ fd = assuan_fd_from_posix_fd (fileno (stderr)); fdp = fd_child_list; if (fdp) { for (; *fdp != ASSUAN_INVALID_FD && *fdp != 0 && *fdp != fd; fdp++) ; } if (!fdp || *fdp == ASSUAN_INVALID_FD) fd_err_isnull = 1; fd_err = ASSUAN_INVALID_FD; if (build_w32_commandline (ctx, argv, fd_in, fd_out, fd_err, fd_err_isnull, &cmdline)) { return -1; } TRACE2 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx, "path=`%s' cmdline=`%s'", name, cmdline); { wchar_t *wcmdline, *wname; wcmdline = utf8_to_wchar (cmdline); _assuan_free (ctx, cmdline); if (!wcmdline) return -1; wname = utf8_to_wchar (name); if (!wname) { free_wchar (wcmdline); return -1; } if (!CreateProcess (wname, /* Program to start. */ wcmdline, /* Command line arguments. */ NULL, /* (not supported) */ NULL, /* (not supported) */ FALSE, /* (not supported) */ (CREATE_SUSPENDED), /* Creation flags. */ NULL, /* (not supported) */ NULL, /* (not supported) */ NULL, /* (not supported) */ &pi /* Returns process information.*/ )) { TRACE1 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx, "CreateProcess failed: %s", _assuan_w32_strerror (ctx, -1)); free_wchar (wname); free_wchar (wcmdline); gpg_err_set_errno (EIO); return -1; } free_wchar (wname); free_wchar (wcmdline); } ResumeThread (pi.hThread); TRACE4 (ctx, ASSUAN_LOG_SYSIO, "__assuan_spawn", ctx, "CreateProcess ready: hProcess=%p hThread=%p" " dwProcessID=%d dwThreadId=%d\n", pi.hProcess, pi.hThread, (int) pi.dwProcessId, (int) pi.dwThreadId); CloseHandle (pi.hThread); *r_pid = (pid_t) pi.hProcess; return 0; }
/* Fork off the SCdaemon if this has not already been done. Lock the daemon and make sure that a proper context has been setup in CTRL. This function might also lock the daemon, which means that the caller must call unlock_scd after this fucntion has returned success and the actual Assuan transaction been done. */ static int start_scd (ctrl_t ctrl) { gpg_error_t err = 0; const char *pgmname; assuan_context_t ctx = NULL; const char *argv[3]; assuan_fd_t no_close_list[3]; int i; int rc; if (opt.disable_scdaemon) return gpg_error (GPG_ERR_NOT_SUPPORTED); /* If this is the first call for this session, setup the local data structure. */ if (!ctrl->scd_local) { ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); if (!ctrl->scd_local) return gpg_error_from_syserror (); ctrl->scd_local->ctrl_backlink = ctrl; ctrl->scd_local->next_local = scd_local_list; scd_local_list = ctrl->scd_local; } /* Assert that the lock count is as expected. */ if (ctrl->scd_local->locked) { log_error ("start_scd: invalid lock count (%d)\n", ctrl->scd_local->locked); return gpg_error (GPG_ERR_INTERNAL); } ctrl->scd_local->locked++; if (ctrl->scd_local->ctx) return 0; /* Okay, the context is fine. We used to test for an alive context here and do an disconnect. Now that we have a ticker function to check for it, it is easier not to check here but to let the connection run on an error instead. */ /* We need to protect the following code. */ rc = npth_mutex_lock (&start_scd_lock); if (rc) { log_error ("failed to acquire the start_scd lock: %s\n", strerror (rc)); return gpg_error (GPG_ERR_INTERNAL); } /* Check whether the pipe server has already been started and in this case either reuse a lingering pipe connection or establish a new socket based one. */ if (primary_scd_ctx && primary_scd_ctx_reusable) { ctx = primary_scd_ctx; primary_scd_ctx_reusable = 0; if (opt.verbose) log_info ("new connection to SCdaemon established (reusing)\n"); goto leave; } rc = assuan_new (&ctx); if (rc) { log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); err = rc; goto leave; } if (socket_name) { rc = assuan_socket_connect (ctx, socket_name, 0, 0); if (rc) { log_error ("can't connect to socket '%s': %s\n", socket_name, gpg_strerror (rc)); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } if (opt.verbose) log_info ("new connection to SCdaemon established\n"); goto leave; } if (primary_scd_ctx) { log_info ("SCdaemon is running but won't accept further connections\n"); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } /* Nope, it has not been started. Fire it up now. */ if (opt.verbose) log_info ("no running SCdaemon - starting it\n"); if (fflush (NULL)) { #ifndef HAVE_W32_SYSTEM err = gpg_error_from_syserror (); #endif log_error ("error flushing pending output: %s\n", strerror (errno)); /* At least Windows XP fails here with EBADF. According to docs and Wine an fflush(NULL) is the same as _flushall. However the Wime implementaion does not flush stdin,stdout and stderr - see above. Lets try to ignore the error. */ #ifndef HAVE_W32_SYSTEM goto leave; #endif } if (!opt.scdaemon_program || !*opt.scdaemon_program) opt.scdaemon_program = gnupg_module_name (GNUPG_MODULE_NAME_SCDAEMON); if ( !(pgmname = strrchr (opt.scdaemon_program, '/'))) pgmname = opt.scdaemon_program; else pgmname++; argv[0] = pgmname; argv[1] = "--multi-server"; argv[2] = NULL; i=0; if (!opt.running_detached) { if (log_get_fd () != -1) no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); } no_close_list[i] = ASSUAN_INVALID_FD; /* Connect to the scdaemon and perform initial handshaking. Use detached flag so that under Windows SCDAEMON does not show up a new window. */ rc = assuan_pipe_connect (ctx, opt.scdaemon_program, argv, no_close_list, atfork_cb, NULL, ASSUAN_PIPE_CONNECT_DETACHED); if (rc) { log_error ("can't connect to the SCdaemon: %s\n", gpg_strerror (rc)); err = gpg_error (GPG_ERR_NO_SCDAEMON); goto leave; } if (opt.verbose) log_debug ("first connection to SCdaemon established\n"); /* Get the name of the additional socket opened by scdaemon. */ { membuf_t data; unsigned char *databuf; size_t datalen; xfree (socket_name); socket_name = NULL; init_membuf (&data, 256); assuan_transact (ctx, "GETINFO socket_name", membuf_data_cb, &data, NULL, NULL, NULL, NULL); databuf = get_membuf (&data, &datalen); if (databuf && datalen) { socket_name = xtrymalloc (datalen + 1); if (!socket_name) log_error ("warning: can't store socket name: %s\n", strerror (errno)); else { memcpy (socket_name, databuf, datalen); socket_name[datalen] = 0; if (DBG_IPC) log_debug ("additional connections at '%s'\n", socket_name); } } xfree (databuf); } /* Tell the scdaemon we want him to send us an event signal. We don't support this for W32CE. */ #ifndef HAVE_W32CE_SYSTEM if (opt.sigusr2_enabled) { char buf[100]; #ifdef HAVE_W32_SYSTEM snprintf (buf, sizeof buf, "OPTION event-signal=%lx", (unsigned long)get_agent_scd_notify_event ()); #else snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2); #endif assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); } #endif /*HAVE_W32CE_SYSTEM*/ primary_scd_ctx = ctx; primary_scd_ctx_reusable = 0; leave: if (err) { unlock_scd (ctrl, err); if (ctx) assuan_release (ctx); } else { ctrl->scd_local->ctx = ctx; } rc = npth_mutex_unlock (&start_scd_lock); if (rc) log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); return err; }