chiark / gitweb /
Replace use of variable-length-arrays.
[gnupg2.git] / common / get-passphrase.c
1 /* get-passphrase.c - Ask for a passphrase via the agent
2  * Copyright (C) 2009 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * This file is free software; you can redistribute it and/or modify
7  * it under the terms of either
8  *
9  *   - the GNU Lesser General Public License as published by the Free
10  *     Software Foundation; either version 3 of the License, or (at
11  *     your option) any later version.
12  *
13  * or
14  *
15  *   - the GNU General Public License as published by the Free
16  *     Software Foundation; either version 2 of the License, or (at
17  *     your option) any later version.
18  *
19  * or both in parallel, as here.
20  *
21  * This file is distributed in the hope that it will be useful,
22  * but WITHOUT ANY WARRANTY; without even the implied warranty of
23  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24  * GNU General Public License for more details.
25  *
26  * You should have received a copy of the GNU General Public License
27  * along with this program; if not, see <https://www.gnu.org/licenses/>.
28  */
29
30 #include <config.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <assert.h>
35 #include <assuan.h>
36
37 #include "util.h"
38 #include "i18n.h"
39 #include "asshelp.h"
40 #include "membuf.h"
41 #include "sysutils.h"
42 #include "get-passphrase.h"
43
44 /* The context used by this process to ask for the passphrase.  */
45 static assuan_context_t agent_ctx;
46 static struct
47 {
48   gpg_err_source_t errsource;
49   int verbosity;
50   const char *agent_program;
51   const char *lc_ctype;
52   const char *lc_messages;
53   session_env_t session_env;
54   const char *pinentry_user_data;
55 } agentargs;
56
57
58 /* Set local variable to be used for a possible agent startup.  Note
59    that the strings are just pointers and should not anymore be
60    modified by the caller. */
61 void
62 gnupg_prepare_get_passphrase (gpg_err_source_t errsource,
63                               int verbosity,
64                               const char *agent_program,
65                               const char *opt_lc_ctype,
66                               const char *opt_lc_messages,
67                               session_env_t session_env)
68 {
69   agentargs.errsource          = errsource;
70   agentargs.verbosity          = verbosity;
71   agentargs.agent_program      = agent_program;
72   agentargs.lc_ctype           = opt_lc_ctype;
73   agentargs.lc_messages        = opt_lc_messages;
74   agentargs.session_env        = session_env;
75 }
76
77
78 /* Try to connect to the agent via socket or fork it off and work by
79    pipes.  Handle the server's initial greeting.  */
80 static gpg_error_t
81 start_agent (void)
82 {
83   gpg_error_t err;
84
85   /* Fixme: This code is not thread safe, thus we don't build it with
86      pth.  We will need a context for each thread or serialize the
87      access to the agent.  */
88   if (agent_ctx)
89     return 0;
90
91   err = start_new_gpg_agent (&agent_ctx,
92                              agentargs.errsource,
93                              agentargs.agent_program,
94                              agentargs.lc_ctype,
95                              agentargs.lc_messages,
96                              agentargs.session_env,
97                              1, agentargs.verbosity, 0, NULL, NULL);
98   if (!err)
99     {
100       /* Tell the agent that we support Pinentry notifications.  No
101          error checking so that it will work with older agents.  */
102       assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
103                        NULL, NULL, NULL, NULL, NULL, NULL);
104     }
105
106   return err;
107 }
108
109
110 /* This is the default inquiry callback.  It merely handles the
111    Pinentry notification.  */
112 static gpg_error_t
113 default_inq_cb (void *opaque, const char *line)
114 {
115   (void)opaque;
116
117   if (!strncmp (line, "PINENTRY_LAUNCHED", 17) && (line[17]==' '||!line[17]))
118     {
119       gnupg_allow_set_foregound_window ((pid_t)strtoul (line+17, NULL, 10));
120       /* We do not return errors to avoid breaking other code.  */
121     }
122   else
123     log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
124
125   return 0;
126 }
127
128
129 /* Ask for a passphrase via gpg-agent.  On success the caller needs to
130    free the string stored at R_PASSPHRASE.  On error NULL will be
131    stored at R_PASSPHRASE and an appropriate gpg error code is
132    returned.  With REPEAT set to 1, gpg-agent will ask the user to
133    repeat the just entered passphrase.  CACHE_ID is a gpg-agent style
134    passphrase cache id or NULL.  ERR_MSG is a error message to be
135    presented to the user (e.g. "bad passphrase - try again") or NULL.
136    PROMPT is the prompt string to label the entry box, it may be NULL
137    for a default one.  DESC_MSG is a longer description to be
138    displayed above the entry box, if may be NULL for a default one.
139    If USE_SECMEM is true, the returned passphrase is returned in
140    secure memory.  The length of all these strings is limited; they
141    need to fit in their encoded form into a standard Assuan line (i.e
142    less then about 950 characters).  All strings shall be UTF-8.  */
143 gpg_error_t
144 gnupg_get_passphrase (const char *cache_id,
145                       const char *err_msg,
146                       const char *prompt,
147                       const char *desc_msg,
148                       int repeat,
149                       int check_quality,
150                       int use_secmem,
151                       char **r_passphrase)
152 {
153   gpg_error_t err;
154   char line[ASSUAN_LINELENGTH];
155   const char *arg1 = NULL;
156   char *arg2 = NULL;
157   char *arg3 = NULL;
158   char *arg4 = NULL;
159   membuf_t data;
160
161   *r_passphrase = NULL;
162
163   err = start_agent ();
164   if (err)
165     return err;
166
167   /* Check that the gpg-agent understands the repeat option.  */
168   if (assuan_transact (agent_ctx,
169                        "GETINFO cmd_has_option GET_PASSPHRASE repeat",
170                        NULL, NULL, NULL, NULL, NULL, NULL))
171     return gpg_error (GPG_ERR_NOT_SUPPORTED);
172
173   arg1 = cache_id && *cache_id? cache_id:NULL;
174   if (err_msg && *err_msg)
175     if (!(arg2 = percent_plus_escape (err_msg)))
176       goto no_mem;
177   if (prompt && *prompt)
178     if (!(arg3 = percent_plus_escape (prompt)))
179       goto no_mem;
180   if (desc_msg && *desc_msg)
181     if (!(arg4 = percent_plus_escape (desc_msg)))
182       goto no_mem;
183
184   snprintf (line, DIM(line),
185             "GET_PASSPHRASE --data %s--repeat=%d -- %s %s %s %s",
186             check_quality? "--check ":"",
187             repeat,
188             arg1? arg1:"X",
189             arg2? arg2:"X",
190             arg3? arg3:"X",
191             arg4? arg4:"X");
192   xfree (arg2);
193   xfree (arg3);
194   xfree (arg4);
195
196   if (use_secmem)
197     init_membuf_secure (&data, 64);
198   else
199     init_membuf (&data, 64);
200   err = assuan_transact (agent_ctx, line,
201                          put_membuf_cb, &data,
202                          default_inq_cb, NULL, NULL, NULL);
203
204   /* Older Pinentries return the old assuan error code for canceled
205      which gets translated by libassuan to GPG_ERR_ASS_CANCELED and
206      not to the code for a user cancel.  Fix this here. */
207   if (err && gpg_err_source (err)
208       && gpg_err_code (err) == GPG_ERR_ASS_CANCELED)
209     err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED);
210
211   if (err)
212     {
213       void *p;
214       size_t n;
215
216       p = get_membuf (&data, &n);
217       if (p)
218         wipememory (p, n);
219       xfree (p);
220     }
221   else
222     {
223       put_membuf (&data, "", 1);
224       *r_passphrase = get_membuf (&data, NULL);
225       if (!*r_passphrase)
226         err = gpg_error_from_syserror ();
227     }
228   return err;
229  no_mem:
230   err = gpg_error_from_syserror ();
231   xfree (arg2);
232   xfree (arg3);
233   xfree (arg4);
234   return err;
235 }
236
237
238 /* Flush the passphrase cache with Id CACHE_ID.  */
239 gpg_error_t
240 gnupg_clear_passphrase (const char *cache_id)
241 {
242   gpg_error_t err;
243   char line[ASSUAN_LINELENGTH];
244
245   if (!cache_id || !*cache_id)
246     return 0;
247
248   err = start_agent ();
249   if (err)
250     return err;
251
252   snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id);
253   return assuan_transact (agent_ctx, line, NULL, NULL,
254                           default_inq_cb, NULL, NULL, NULL);
255 }