Line data Source code
1 : /* simple-pwquery.c - A simple password query client for gpg-agent
2 : * Copyright (C) 2002, 2004, 2007 Free Software Foundation, Inc.
3 : *
4 : * This file is part of GnuPG.
5 : *
6 : * GnuPG is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU General Public License as published by
8 : * the Free Software Foundation; either version 3 of the License, or
9 : * (at your option) any later version.
10 : *
11 : * GnuPG is distributed in the hope that it will be useful,
12 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : * GNU General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU General Public License
17 : * along with this program; if not, see <https://www.gnu.org/licenses/>.
18 : */
19 :
20 : /* This module is intended as a simple client implementation to
21 : gpg-agent's GET_PASSPHRASE command. It can only cope with an
22 : already running gpg-agent. Some stuff is configurable in the
23 : header file. */
24 :
25 : #ifdef HAVE_CONFIG_H
26 : #include <config.h>
27 : #endif
28 : #include <stdlib.h>
29 : #include <stddef.h>
30 : #include <string.h>
31 : #include <errno.h>
32 : #include <unistd.h>
33 : #include <assuan.h>
34 : #ifdef HAVE_W32_SYSTEM
35 : #include <winsock2.h>
36 : #else
37 : #include <sys/socket.h>
38 : #include <sys/un.h>
39 : #endif
40 : #ifdef HAVE_LOCALE_H
41 : #include <locale.h>
42 : #endif
43 :
44 : #define GNUPG_COMMON_NEED_AFLOCAL
45 : #include "../common/mischelp.h"
46 : #include "sysutils.h"
47 : #include "membuf.h"
48 :
49 :
50 : #define SIMPLE_PWQUERY_IMPLEMENTATION 1
51 : #include "simple-pwquery.h"
52 :
53 : #define SPWQ_OUT_OF_CORE gpg_error_from_errno (ENOMEM)
54 : #define SPWQ_IO_ERROR gpg_error_from_errno (EIO)
55 : #define SPWQ_PROTOCOL_ERROR gpg_error (GPG_ERR_PROTOCOL_VIOLATION)
56 : #define SPWQ_ERR_RESPONSE gpg_error (GPG_ERR_INV_RESPONSE)
57 : #define SPWQ_NO_AGENT gpg_error (GPG_ERR_NO_AGENT)
58 : #define SPWQ_SYS_ERROR gpg_error_from_syserror ()
59 : #define SPWQ_GENERAL_ERROR gpg_error (GPG_ERR_GENERAL)
60 : #define SPWQ_NO_PIN_ENTRY gpg_error (GPG_ERR_NO_PIN_ENTRY)
61 :
62 : #ifndef _
63 : #define _(a) (a)
64 : #endif
65 :
66 : #if !defined (hexdigitp) && !defined (xtoi_2)
67 : #define digitp(p) (*(p) >= '0' && *(p) <= '9')
68 : #define hexdigitp(a) (digitp (a) \
69 : || (*(a) >= 'A' && *(a) <= 'F') \
70 : || (*(a) >= 'a' && *(a) <= 'f'))
71 : #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
72 : *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
73 : #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
74 : #endif
75 :
76 :
77 : /* Name of the socket to be used. This is a kludge to keep on using
78 : the existsing code despite that we only support a standard socket. */
79 : static char *default_gpg_agent_info;
80 :
81 :
82 :
83 :
84 :
85 : #ifndef HAVE_STPCPY
86 : static char *
87 : my_stpcpy(char *a,const char *b)
88 : {
89 : while( *b )
90 : *a++ = *b++;
91 : *a = 0;
92 :
93 : return (char*)a;
94 : }
95 : #define stpcpy(a,b) my_stpcpy((a), (b))
96 : #endif
97 :
98 :
99 : /* Send an option to the agent */
100 : static int
101 135 : agent_send_option (assuan_context_t ctx, const char *name, const char *value)
102 : {
103 : int err;
104 : char *line;
105 :
106 135 : line = spwq_malloc (7 + strlen (name) + 1 + strlen (value) + 2);
107 135 : if (!line)
108 0 : return SPWQ_OUT_OF_CORE;
109 135 : strcpy (stpcpy (stpcpy (stpcpy (
110 135 : stpcpy (line, "OPTION "), name), "="), value), "\n");
111 :
112 135 : err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
113 :
114 135 : spwq_free (line);
115 135 : return err;
116 : }
117 :
118 :
119 : /* Send all available options to the agent. */
120 : static int
121 135 : agent_send_all_options (assuan_context_t ctx)
122 : {
123 135 : char *dft_display = NULL;
124 135 : char *dft_ttyname = NULL;
125 135 : char *dft_ttytype = NULL;
126 135 : char *dft_xauthority = NULL;
127 135 : char *dft_pinentry_user_data = NULL;
128 135 : int rc = 0;
129 :
130 135 : dft_display = getenv ("DISPLAY");
131 135 : if (dft_display)
132 : {
133 135 : if ((rc = agent_send_option (ctx, "display", dft_display)))
134 0 : return rc;
135 : }
136 :
137 135 : dft_ttyname = getenv ("GPG_TTY");
138 : #if !defined(HAVE_W32_SYSTEM) && !defined(HAVE_BROKEN_TTYNAME)
139 135 : if ((!dft_ttyname || !*dft_ttyname) && ttyname (0))
140 0 : dft_ttyname = ttyname (0);
141 : #endif
142 135 : if (dft_ttyname && *dft_ttyname)
143 : {
144 0 : if ((rc=agent_send_option (ctx, "ttyname", dft_ttyname)))
145 0 : return rc;
146 : }
147 :
148 135 : dft_ttytype = getenv ("TERM");
149 135 : if (dft_ttyname && dft_ttytype)
150 : {
151 0 : if ((rc = agent_send_option (ctx, "ttytype", dft_ttytype)))
152 0 : return rc;
153 : }
154 :
155 : #if defined(HAVE_SETLOCALE)
156 : {
157 135 : char *old_lc = NULL;
158 135 : char *dft_lc = NULL;
159 :
160 : #if defined(LC_CTYPE)
161 135 : old_lc = setlocale (LC_CTYPE, NULL);
162 135 : if (old_lc)
163 : {
164 135 : char *p = spwq_malloc (strlen (old_lc)+1);
165 135 : if (!p)
166 0 : return SPWQ_OUT_OF_CORE;
167 135 : strcpy (p, old_lc);
168 135 : old_lc = p;
169 : }
170 135 : dft_lc = setlocale (LC_CTYPE, "");
171 135 : if (dft_ttyname && dft_lc)
172 0 : rc = agent_send_option (ctx, "lc-ctype", dft_lc);
173 135 : if (old_lc)
174 : {
175 135 : setlocale (LC_CTYPE, old_lc);
176 135 : spwq_free (old_lc);
177 : }
178 135 : if (rc)
179 0 : return rc;
180 : #endif
181 :
182 : #if defined(LC_MESSAGES)
183 135 : old_lc = setlocale (LC_MESSAGES, NULL);
184 135 : if (old_lc)
185 : {
186 135 : char *p = spwq_malloc (strlen (old_lc)+1);
187 135 : if (!p)
188 0 : return SPWQ_OUT_OF_CORE;
189 135 : strcpy (p, old_lc);
190 135 : old_lc = p;
191 : }
192 135 : dft_lc = setlocale (LC_MESSAGES, "");
193 135 : if (dft_ttyname && dft_lc)
194 0 : rc = agent_send_option (ctx, "lc-messages", dft_lc);
195 135 : if (old_lc)
196 : {
197 135 : setlocale (LC_MESSAGES, old_lc);
198 135 : spwq_free (old_lc);
199 : }
200 135 : if (rc)
201 0 : return rc;
202 : #endif
203 : }
204 : #endif /*HAVE_SETLOCALE*/
205 :
206 : /* Send the XAUTHORITY variable. */
207 135 : dft_xauthority = getenv ("XAUTHORITY");
208 135 : if (dft_xauthority)
209 : {
210 : /* We ignore errors here because older gpg-agents don't support
211 : this option. */
212 0 : agent_send_option (ctx, "xauthority", dft_xauthority);
213 : }
214 :
215 : /* Send the PINENTRY_USER_DATA variable. */
216 135 : dft_pinentry_user_data = getenv ("PINENTRY_USER_DATA");
217 135 : if (dft_pinentry_user_data)
218 : {
219 : /* We ignore errors here because older gpg-agents don't support
220 : this option. */
221 0 : agent_send_option (ctx, "pinentry-user-data", dft_pinentry_user_data);
222 : }
223 :
224 : /* Tell the agent that we support Pinentry notifications. No
225 : error checking so that it will work with older agents. */
226 135 : assuan_transact (ctx, "OPTION allow-pinentry-notify",
227 : NULL, NULL, NULL, NULL, NULL, NULL);
228 :
229 135 : return 0;
230 : }
231 :
232 :
233 :
234 : /* Try to open a connection to the agent, send all options and return
235 : the file descriptor for the connection. Return -1 in case of
236 : error. */
237 : static int
238 135 : agent_open (assuan_context_t *ctx)
239 : {
240 : int rc;
241 : char *infostr;
242 :
243 135 : infostr = default_gpg_agent_info;
244 135 : if ( !infostr || !*infostr )
245 : {
246 : #ifdef SPWQ_USE_LOGGING
247 0 : log_error (_("no gpg-agent running in this session\n"));
248 : #endif
249 0 : return SPWQ_NO_AGENT;
250 : }
251 :
252 135 : rc = assuan_new (ctx);
253 135 : if (rc)
254 0 : return rc;
255 :
256 135 : rc = assuan_socket_connect (*ctx, infostr, 0, 0);
257 135 : if (rc)
258 : {
259 : #ifdef SPWQ_USE_LOGGING
260 0 : log_error (_("can't connect to '%s': %s\n"),
261 : infostr, gpg_strerror (rc));
262 : #endif
263 0 : goto errout;
264 : }
265 :
266 135 : rc = agent_send_all_options (*ctx);
267 135 : if (rc)
268 : {
269 : #ifdef SPWQ_USE_LOGGING
270 0 : log_error (_("problem setting the gpg-agent options\n"));
271 : #endif
272 0 : goto errout;
273 : }
274 :
275 135 : return 0;
276 :
277 : errout:
278 0 : assuan_release (*ctx);
279 0 : *ctx = NULL;
280 0 : return rc;
281 : }
282 :
283 :
284 : /* Copy text to BUFFER and escape as required. Return a pointer to
285 : the end of the new buffer. Note that BUFFER must be large enough
286 : to keep the entire text; allocataing it 3 times the size of TEXT
287 : is sufficient. */
288 : static char *
289 0 : copy_and_escape (char *buffer, const char *text)
290 : {
291 : int i;
292 0 : const unsigned char *s = (unsigned char *)text;
293 0 : char *p = buffer;
294 :
295 :
296 0 : for (i=0; s[i]; i++)
297 : {
298 0 : if (s[i] < ' ' || s[i] == '+')
299 : {
300 0 : sprintf (p, "%%%02X", s[i]);
301 0 : p += 3;
302 : }
303 0 : else if (s[i] == ' ')
304 0 : *p++ = '+';
305 : else
306 0 : *p++ = s[i];
307 : }
308 0 : return p;
309 : }
310 :
311 :
312 : /* Set the name of the default socket to NAME. */
313 : int
314 135 : simple_pw_set_socket (const char *name)
315 : {
316 135 : spwq_free (default_gpg_agent_info);
317 135 : default_gpg_agent_info = NULL;
318 135 : if (name)
319 : {
320 135 : default_gpg_agent_info = spwq_malloc (strlen (name) + 1);
321 135 : if (!default_gpg_agent_info)
322 0 : return SPWQ_OUT_OF_CORE;
323 135 : strcpy (default_gpg_agent_info, name);
324 : }
325 :
326 135 : return 0;
327 : }
328 :
329 :
330 : /* This is the default inquiry callback. It merely handles the
331 : Pinentry notification. */
332 : static gpg_error_t
333 0 : default_inq_cb (void *opaque, const char *line)
334 : {
335 : (void)opaque;
336 :
337 0 : if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17]))
338 : {
339 0 : gnupg_allow_set_foregound_window ((pid_t)strtoul (line+17, NULL, 10));
340 : /* We do not return errors to avoid breaking other code. */
341 : }
342 : else
343 : {
344 : #ifdef SPWQ_USE_LOGGING
345 0 : log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
346 : #endif
347 : }
348 :
349 0 : return 0;
350 : }
351 :
352 :
353 : /* Ask the gpg-agent for a passphrase and present the user with a
354 : DESCRIPTION, a PROMPT and optionally with a TRYAGAIN extra text.
355 : If a CACHEID is not NULL it is used to locate the passphrase in in
356 : the cache and store it under this ID. If OPT_CHECK is true
357 : gpg-agent is asked to apply some checks on the passphrase security.
358 : If ERRORCODE is not NULL it should point a variable receiving an
359 : errorcode; this error code might be 0 if the user canceled the
360 : operation. The function returns NULL to indicate an error. */
361 : char *
362 0 : simple_pwquery (const char *cacheid,
363 : const char *tryagain,
364 : const char *prompt,
365 : const char *description,
366 : int opt_check,
367 : int *errorcode)
368 : {
369 : int rc;
370 : assuan_context_t ctx;
371 : membuf_t data;
372 0 : char *result = NULL;
373 0 : char *pw = NULL;
374 : char *p;
375 : size_t n;
376 :
377 :
378 0 : rc = agent_open (&ctx);
379 0 : if (rc)
380 0 : goto leave;
381 :
382 0 : if (!cacheid)
383 0 : cacheid = "X";
384 0 : if (!tryagain)
385 0 : tryagain = "X";
386 0 : if (!prompt)
387 0 : prompt = "X";
388 0 : if (!description)
389 0 : description = "X";
390 :
391 : {
392 : char *line;
393 : /* We allocate 3 times the needed space so that there is enough
394 : space for escaping. */
395 0 : line = spwq_malloc (15 + 10
396 : + 3*strlen (cacheid) + 1
397 : + 3*strlen (tryagain) + 1
398 : + 3*strlen (prompt) + 1
399 : + 3*strlen (description) + 1
400 : + 2);
401 0 : if (!line)
402 : {
403 0 : rc = SPWQ_OUT_OF_CORE;
404 0 : goto leave;
405 : }
406 0 : strcpy (line, "GET_PASSPHRASE ");
407 0 : p = line+15;
408 0 : if (opt_check)
409 0 : p = stpcpy (p, "--check ");
410 0 : p = copy_and_escape (p, cacheid);
411 0 : *p++ = ' ';
412 0 : p = copy_and_escape (p, tryagain);
413 0 : *p++ = ' ';
414 0 : p = copy_and_escape (p, prompt);
415 0 : *p++ = ' ';
416 0 : p = copy_and_escape (p, description);
417 0 : *p++ = '\n';
418 :
419 0 : init_membuf_secure (&data, 64);
420 :
421 0 : rc = assuan_transact (ctx, line, put_membuf_cb, &data,
422 : default_inq_cb, NULL, NULL, NULL);
423 0 : spwq_free (line);
424 :
425 : /* Older Pinentries return the old assuan error code for canceled
426 : which gets translated by libassuan to GPG_ERR_ASS_CANCELED and
427 : not to the code for a user cancel. Fix this here. */
428 0 : if (rc && gpg_err_source (rc)
429 0 : && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
430 0 : rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
431 :
432 0 : if (rc)
433 : {
434 0 : p = get_membuf (&data, &n);
435 0 : if (p)
436 0 : wipememory (p, n);
437 0 : spwq_free (p);
438 : }
439 : else
440 : {
441 0 : put_membuf (&data, "", 1);
442 0 : result = get_membuf (&data, NULL);
443 0 : if (pw == NULL)
444 0 : rc = gpg_error_from_syserror ();
445 : }
446 : }
447 :
448 : leave:
449 0 : if (errorcode)
450 0 : *errorcode = rc;
451 0 : assuan_release (ctx);
452 0 : return result;
453 : }
454 :
455 :
456 : /* Ask the gpg-agent to clear the passphrase for the cache ID CACHEID. */
457 : int
458 0 : simple_pwclear (const char *cacheid)
459 : {
460 : char line[500];
461 : char *p;
462 :
463 : /* We need not more than 50 characters for the command and the
464 : terminating nul. */
465 0 : if (strlen (cacheid) * 3 > sizeof (line) - 50)
466 0 : return SPWQ_PROTOCOL_ERROR;
467 :
468 0 : strcpy (line, "CLEAR_PASSPHRASE ");
469 0 : p = line + 17;
470 0 : p = copy_and_escape (p, cacheid);
471 0 : *p++ = '\n';
472 0 : *p++ = '\0';
473 :
474 0 : return simple_query (line);
475 : }
476 :
477 :
478 : /* Perform the simple query QUERY (which must be new-line and 0
479 : terminated) and return the error code. */
480 : int
481 135 : simple_query (const char *query)
482 : {
483 : assuan_context_t ctx;
484 : int rc;
485 :
486 135 : rc = agent_open (&ctx);
487 135 : if (rc)
488 0 : return rc;
489 :
490 135 : rc = assuan_transact (ctx, query, NULL, NULL, NULL, NULL, NULL, NULL);
491 :
492 135 : assuan_release (ctx);
493 135 : return rc;
494 : }
|