Fix a few 'gcc -fanalyzer' warnings.
[pwmd.git] / src / agent.c
blob3ab6efe58b60aa10c3ca782373df90924a68ebe4
1 /*
2 Copyright (C) 2016-2023 Ben Kibbey <bjk@luxsci.net>
4 This file is part of pwmd.
6 Pwmd is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License version 2 as
8 published by the Free Software Foundation.
10 Pwmd is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with Pwmd. If not, see <http://www.gnu.org/licenses/>.
18 #ifdef HAVE_CONFIG_H
19 #include <config.h>
20 #endif
22 #include <stdlib.h>
23 #include <errno.h>
24 #include <pwd.h>
25 #include <sys/types.h>
26 #include <signal.h>
27 #include <sys/wait.h>
28 #ifdef HAVE_FCNTL_H
29 #include <fcntl.h>
30 #endif
31 #include <dirent.h>
33 #ifdef HAVE_LIMITS_H
34 #include <limits.h>
35 #endif
37 #include "pwmd-error.h"
38 #include <gpgme.h>
39 #include "util-misc.h"
40 #include "mem.h"
41 #include "common.h"
42 #include "agent.h"
43 #include "util-string.h"
44 #include "mutex.h"
45 #include "rcfile.h"
46 #include "cache.h"
48 static gpg_error_t
49 mem_realloc_cb (void *data, const void *buffer, size_t len)
51 membuf_t *mem = (membuf_t *) data;
52 void *p;
54 if (!buffer)
55 return 0;
57 if ((p = xrealloc (mem->buf, mem->len + len)) == NULL)
58 return 1;
60 mem->buf = p;
61 memcpy ((char *) mem->buf + mem->len, buffer, len);
62 mem->len += len;
63 return 0;
66 static gpg_error_t
67 assuan_command (struct agent_s *a, char **result,
68 size_t * len, const char *cmd)
70 gpg_error_t rc;
72 a->data.len = 0;
73 a->data.buf = NULL;
74 if (result)
75 *result = NULL;
76 if (len)
77 *len = 0;
79 rc = assuan_transact (a->ctx, cmd, mem_realloc_cb, &a->data, NULL, NULL,
80 NULL, NULL);
81 if (rc)
82 xfree (a->data.buf);
83 else
85 if (a->data.buf)
87 mem_realloc_cb (&a->data, "", 1);
88 if (result)
89 *result = (char *) a->data.buf;
90 else
91 xfree (a->data.buf);
93 if (len)
94 *len = a->data.len;
98 return rc;
101 static char *
102 get_gpg_connect_agent_path (void)
104 gpgme_engine_info_t engine;
105 char *s, *p;
107 gpgme_get_engine_info (&engine);
108 while (engine)
110 if (engine->protocol == GPGME_PROTOCOL_GPGCONF)
111 break;
113 engine = engine->next;
116 if (!engine)
117 return NULL;
119 s = str_dup (engine->file_name);
120 if (!s)
121 return NULL;
123 p = strrchr (s, '/');
124 if (p)
126 *p = 0;
127 p = str_asprintf ("%s/gpg-connect-agent", s);
128 xfree (s);
129 return p;
132 xfree (s);
133 return NULL;
136 gpg_error_t
137 agent_connect (struct agent_s *agent)
139 gpg_error_t rc;
140 assuan_context_t ctx = NULL;
141 static struct assuan_malloc_hooks mhooks = { xmalloc, xrealloc, xfree };
143 agent->did_restart = 0;
144 if (agent->restart)
146 gpgme_ctx_t gctx;
148 rc = gpgme_new (&gctx);
149 if (!rc)
151 char **args = NULL;
153 pthread_cleanup_push ((void *)gpgme_release, gctx);
154 rc = gpgme_set_protocol (gctx, GPGME_PROTOCOL_SPAWN);
155 if (!rc && strv_printf (&args, ""))
157 if (!rc && strv_printf (&args, "--homedir"))
159 char *gpghome = config_get_string ("global", "gpg_homedir");
161 if (gpghome)
163 if (!strv_printf (&args, "%s", gpghome))
164 rc = GPG_ERR_ENOMEM;
166 else
168 if (!strv_printf (&args, "%s/.gnupg", homedir))
169 rc = GPG_ERR_ENOMEM;
172 xfree (gpghome);
174 else if (!rc)
175 rc = GPG_ERR_ENOMEM;
177 else if (!rc)
178 rc = GPG_ERR_ENOMEM;
180 pthread_cleanup_push ((void *)strv_free, args);
181 if (!rc)
183 gpgme_data_t idata = NULL;
184 size_t len;
185 char *s;
187 rc = gpgme_data_new (&idata);
188 if (!rc)
190 char *gca = get_gpg_connect_agent_path ();
192 pthread_cleanup_push (xfree, gca);
193 if (gca)
194 rc = gpgme_op_spawn (gctx, gca, (const char **)args, NULL,
195 idata, NULL,
196 GPGME_SPAWN_DETACHED|GPGME_SPAWN_ALLOW_SET_FG);
197 else
198 rc = GPG_ERR_ENOMEM;
200 pthread_cleanup_pop (1);
203 if (!rc)
205 ssize_t ret = gpgme_data_write (idata, "/BYE\n", 5);
207 if (ret != 5)
208 rc = gpg_error_from_syserror ();
211 if (idata)
213 s = gpgme_data_release_and_get_mem (idata, &len);
214 gpgme_free (s);
218 pthread_cleanup_pop (1);
219 pthread_cleanup_pop (1);
222 if (rc)
223 return rc;
226 rc = assuan_new_ext (&ctx, GPG_ERR_SOURCE_DEFAULT, &mhooks, assuan_log_cb,
227 NULL);
228 if (rc)
229 return rc;
231 pthread_cleanup_push ((void *)assuan_release, ctx);
232 rc = assuan_socket_connect (ctx, agent->socket, ASSUAN_INVALID_PID, 0);
233 if (!rc)
235 if (agent->ctx)
236 assuan_release (agent->ctx);
238 agent->ctx = ctx;
240 else
242 if (ctx)
243 assuan_release (ctx);
246 pthread_cleanup_pop (0);
247 return rc;
250 static gpg_error_t
251 send_to_agent (struct agent_s *agent, char **result, size_t *len,
252 const char *cmd)
254 gpg_error_t rc = 0;
256 if (agent->ctx)
257 rc = assuan_command (agent, result, len, cmd);
258 else
260 rc = agent_connect (agent);
261 if (!rc)
262 rc = assuan_command (agent, result, len, cmd);
265 if (!agent->restart && gpg_err_source (rc) == GPG_ERR_SOURCE_DEFAULT
266 && (gpg_err_code (rc) == GPG_ERR_ASS_CONNECT_FAILED
267 || gpg_err_code (rc) == GPG_ERR_EPIPE))
269 log_write (_ ("gpg-agent connection died (rc=%u), reconnecting"), rc);
270 agent->restart = 1;
271 rc = agent_connect (agent);
272 if (!rc)
274 agent->did_restart = 1;
275 rc = assuan_command (agent, result, len, cmd);
279 agent->restart = 0;
280 return rc;
283 gpg_error_t
284 agent_command (struct agent_s *agent, char **result, size_t * len,
285 const char *fmt, ...)
287 va_list ap;
288 char *cmd = NULL;
289 gpg_error_t rc;
291 va_start (ap, fmt);
293 if (str_vasprintf (&cmd, fmt, ap) > 0)
295 pthread_cleanup_push (xfree, cmd);
296 rc = send_to_agent (agent, result, len, cmd);
297 pthread_cleanup_pop (0);
299 else
300 rc = GPG_ERR_ENOMEM;
302 xfree (cmd);
303 va_end (ap);
304 return rc;
307 void
308 agent_disconnect (struct agent_s *agent)
310 if (!agent)
311 return;
313 if (agent->ctx)
314 assuan_release (agent->ctx);
316 agent->ctx = NULL;
319 void
320 agent_free (struct agent_s *agent)
322 if (!agent)
323 return;
325 agent_disconnect (agent);
326 xfree (agent->socket);
327 xfree (agent);
330 gpg_error_t
331 agent_init (struct agent_s **agent)
333 struct agent_s *new;
334 FILE *fp;
335 char *buf;
336 char line[PATH_MAX];
337 gpg_error_t rc = 0;
338 int ret;
339 char *gpghome = config_get_string ("global", "gpg_homedir");
341 if (gpghome)
342 buf = str_asprintf ("gpgconf --homedir %s --list-dirs", gpghome);
343 else
344 buf = str_asprintf ("gpgconf --homedir %s/.gnupg --list-dirs", homedir);
346 xfree (gpghome);
347 fp = popen (buf, "r");
348 if (!fp)
350 rc = gpg_error_from_syserror ();
351 xfree (buf);
352 return rc;
355 xfree (buf);
356 buf = NULL;
358 while (fgets (line, sizeof(line), fp))
360 if (line[strlen(line)-1] == '\n')
361 line[strlen(line)-1] = 0;
363 if (!strncmp (line, "agent-socket:", 13))
365 buf = str_dup (line+13);
366 break;
370 ret = pclose (fp);
371 if (ret)
373 xfree (buf);
374 return GPG_ERR_ASS_GENERAL;
377 new = xcalloc (1, sizeof (struct agent_s));
378 if (!new)
380 xfree (buf);
381 return GPG_ERR_ENOMEM;
384 new->socket = buf;
385 *agent = new;
386 return 0;
389 gpg_error_t
390 agent_set_option (struct agent_s * agent, const char *name, const char *value)
392 return agent_command (agent, NULL, NULL, "OPTION %s=%s", name, value);
395 gpg_error_t
396 agent_kill_scd (struct agent_s *agent)
398 gpg_error_t rc = 0;
400 if (config_get_boolean (NULL, "kill_scd"))
402 rc = agent_command (agent, NULL, NULL, "SCD KILLSCD");
403 if (rc && gpg_err_code (rc) != GPG_ERR_NO_SCDAEMON
404 && gpg_err_code (rc) != GPG_ERR_EPIPE
405 && gpg_err_code (rc) != GPG_ERR_EOF)
406 log_write ("%s: ERR %u: %s", __FUNCTION__, rc, pwmd_strerror (rc));
409 return rc;