Show the passphrase quality meter on mismatch.
[libpwmd.git] / src / pinentry.c
blobe0d65085c3abc3dff8a12af039844594c0e9c5a2
1 /* vim:tw=78:ts=8:sw=4:set ft=c: */
2 /*
3 Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012
4 Ben Kibbey <bjk@luxsci.net>
6 This file is part of libpwmd.
8 Libpwmd is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 2 of the License, or
11 (at your option) any later version.
13 Libpwmd is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with Libpwmd. If not, see <http://www.gnu.org/licenses/>.
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
25 #include <sys/types.h>
26 #include <signal.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <limits.h>
31 #ifdef HAVE_STRING_H
32 #include <string.h>
33 #endif
35 #include "pinentry.h"
36 #include "misc.h"
38 #ifdef WITH_QUALITY
39 #include <crack.h>
40 #endif
42 static pwm_t *gpwm;
43 static int gelapsed, gtimeout;
44 static gpg_error_t grc;
46 static gpg_error_t set_pinentry_strings(pwm_t *pwm, pwmd_pinentry_t which);
48 static gpg_error_t launch_pinentry(pwm_t *pwm)
50 int rc;
51 assuan_context_t ctx;
52 int child_list[] = {-1};
53 const char *argv[10];
54 const char **p = argv;
55 static struct assuan_malloc_hooks mhooks = {
56 pwmd_malloc, pwmd_realloc, pwmd_free
59 update_pinentry_settings(pwm);
60 if (!pwm->pinentry_display && !pwm->pinentry_tty)
61 return GPG_ERR_ENOTTY;
63 *p++ = "pinentry";
64 *p++ = pwm->pinentry_display ? "--display" : "--ttyname";
65 *p++ = pwm->pinentry_display ? pwm->pinentry_display : pwm->pinentry_tty;
67 if (pwm->pinentry_lcctype) {
68 *p++ = "--lc-ctype";
69 *p++ = pwm->pinentry_lcctype;
72 if (pwm->pinentry_lcmessages) {
73 *p++ = "--lc-messages";
74 *p++ = pwm->pinentry_lcmessages;
77 *p = NULL;
79 if (!pwm->pinentry_display) {
80 *p++ = "--ttytype";
81 *p++ = pwm->pinentry_term ? pwm->pinentry_term : getenv("TERM");
82 *p = NULL;
85 rc = assuan_new_ext(&ctx, GPG_ERR_SOURCE_PINENTRY, &mhooks, NULL, NULL);
86 if (rc)
87 return rc;
89 rc = assuan_pipe_connect(ctx,
90 pwm->pinentry_path ? pwm->pinentry_path : PINENTRY_PATH, argv,
91 child_list, NULL, NULL, 0);
92 if (rc)
93 return rc;
95 pwm->pinentry_pid = assuan_get_pid(ctx);
96 pwm->pctx = ctx;
97 return set_pinentry_strings(pwm, 0);
100 static gpg_error_t pinentry_command(pwm_t *pwm, char **result, size_t *len,
101 const char *cmd)
103 if (!pwm->pctx) {
104 gpg_error_t rc = launch_pinentry(pwm);
106 if (rc)
107 return rc;
110 return _assuan_command(pwm, pwm->pctx, result, len, cmd);
113 #ifdef WITH_QUALITY
114 static gpg_error_t quality_cb(void *data, const char *line)
116 pwm_t *pwm = data;
117 int score = 0;
118 char buf[5];
119 const char *tmp;
121 if (strncmp(line, "QUALITY ", 8))
122 return GPG_ERR_INV_ARG;
124 if (!(tmp = FascistCheck(line+8, CRACKLIB_DICT)))
125 return assuan_send_data(pwm->pctx, "100", 3);
127 if (!strcmp(tmp, N_("it is WAY too short")))
128 score = 10;
129 else if (!strcmp(tmp, N_("it is too short")))
130 score = 20;
131 else if (!strcmp(tmp, N_("it is all whitespace")))
132 score = 25;
133 else if (!strcmp(tmp, N_("it is based on your username")))
134 score = 30;
135 else if (!strcmp(tmp, N_("it does not contain enough DIFFERENT characters")))
136 score = 35;
137 else if (!strcmp(tmp, N_("it is based on a dictionary word")))
138 score = 40;
139 else if (!strcmp(tmp, N_("it is based upon your password entry")))
140 score = 50;
141 else if (!strcmp(tmp, N_("it's derived from your password entry")))
142 score = 50;
143 else if (!strcmp(tmp, N_("it is derived from your password entry")))
144 score = 50;
145 else if (!strcmp(tmp, N_("it is based on a (reversed) dictionary word")))
146 score = 60;
147 else if (!strcmp(tmp, N_("it is derivable from your password entry")))
148 score = 70;
149 else if (!strcmp(tmp, N_("it's derivable from your password entry")))
150 score = 70;
151 else if (!strcmp(tmp, N_("it looks like a National Insurance number.")))
152 score = 80;
153 else if (!strcmp(tmp, N_("it is too simplistic/systematic")))
154 score = 90;
155 else
156 score = 0;
158 snprintf(buf, sizeof(buf), "%i", score);
159 return assuan_send_data(pwm->pctx, buf, strlen(buf));
161 #endif
163 static gpg_error_t set_pinentry_strings(pwm_t *pwm, pwmd_pinentry_t which)
165 char *tmp, *desc = NULL;
166 gpg_error_t rc;
168 if (which != PWMD_PINENTRY_USER) {
169 rc = pinentry_command(pwm, NULL, NULL, "SETERROR ");
170 if (rc)
171 return rc;
174 tmp = pwmd_malloc(ASSUAN_LINELENGTH+1);
175 if (!tmp)
176 return gpg_error_from_errno(ENOMEM);
178 if (!pwm->pinentry_prompt) {
179 pwm->pinentry_prompt = pwmd_strdup(N_("Passphrase:"));
180 if (!pwm->pinentry_prompt) {
181 pwmd_free(tmp);
182 return GPG_ERR_ENOMEM;
186 if (!pwm->pinentry_desc) {
187 if (which == PWMD_PINENTRY_OPEN || which == PWMD_PINENTRY_OPEN_FAILED)
188 desc = pwmd_strdup_printf(N_("A passphrase is required to unlock the secret key for the encrypted data file \"%s\". Please enter the passphrase below."), pwm->filename);
189 else if (which == PWMD_PINENTRY_SAVE
190 || which == PWMD_PINENTRY_SAVE_FAILED)
191 desc = pwmd_strdup_printf(N_("Please enter the passphrase to use to encrypt the data file \"%s\"."), pwm->filename);
192 else if (which == PWMD_PINENTRY_SAVE_CONFIRM)
193 desc = pwmd_strdup_printf(N_("Please re-enter the passphrase for confirmation."), pwm->filename);
195 if (!desc) {
196 pwmd_free(tmp);
197 return GPG_ERR_ENOMEM;
201 if (pwm->pinentry_desc)
202 desc = pwm->pinentry_desc;
204 if (which == PWMD_PINENTRY_USER) {
205 snprintf(tmp, ASSUAN_LINELENGTH, "SETERROR %s",
206 pwm->pinentry_error ? pwm->pinentry_error : "");
207 rc = pinentry_command(pwm, NULL, NULL, tmp);
208 if (rc) {
209 pwmd_free(tmp);
210 return rc;
213 snprintf(tmp, ASSUAN_LINELENGTH, "SETDESC %s",
214 pwm->pinentry_desc ? pwm->pinentry_desc : "");
216 else {
217 if (which == PWMD_PINENTRY_SAVE_FAILED
218 || which == PWMD_PINENTRY_OPEN_FAILED) {
219 if (which == PWMD_PINENTRY_SAVE_FAILED)
220 snprintf(tmp, ASSUAN_LINELENGTH, "SETERROR %s",
221 N_("Passphrases do not match, try again."));
222 else {
223 if (pwm->pinentry_tries) {
224 strcpy(tmp, "SETERROR ");
225 snprintf(tmp+strlen("SETERROR "), ASSUAN_LINELENGTH,
226 N_("Bad passphrase (try %i of %i)"),
227 pwm->pinentry_try+1, pwm->pinentry_tries);
229 else
230 snprintf(tmp, ASSUAN_LINELENGTH, "SETERROR %s",
231 N_("Bad passphrase, try again"));
234 rc = pinentry_command(pwm, NULL, NULL, tmp);
235 if (rc) {
236 pwmd_free(tmp);
237 return rc;
241 snprintf(tmp, ASSUAN_LINELENGTH, "SETDESC %s", desc);
243 if (pwm->pinentry_desc != desc)
244 pwmd_free(desc);
247 rc = pinentry_command(pwm, NULL, NULL, tmp);
248 if (rc) {
249 pwmd_free(tmp);
250 return rc;
253 snprintf(tmp, ASSUAN_LINELENGTH, "SETPROMPT %s", pwm->pinentry_prompt);
254 rc = pinentry_command(pwm, NULL, NULL, tmp);
255 pwmd_free(tmp);
257 #ifdef WITH_QUALITY
258 if (!rc && (which == PWMD_PINENTRY_SAVE ||
259 which == PWMD_PINENTRY_SAVE_FAILED)) {
260 rc = pinentry_command(pwm, NULL, NULL, "SETQUALITYBAR");
261 if (!rc) {
262 pwm->_inquire_func = quality_cb;
263 pwm->_inquire_data = pwm;
266 #endif
268 return rc;
271 static gpg_error_t terminate_pinentry(pwm_t *pwm)
273 pid_t pid = pwm->pinentry_pid;
275 pwm->pinentry_pid = -1;
277 if (!pwm || pid == -1)
278 return GPG_ERR_INV_ARG;
280 if (kill(pid, 0) == 0) {
281 if (kill(pid, SIGTERM) == -1) {
282 if (kill(pid, SIGKILL) == -1)
283 return gpg_error_from_errno(errno);
286 else
287 return gpg_error_from_errno(errno);
289 return 0;
292 void _pinentry_disconnect(pwm_t *pwm)
294 if (pwm->pctx)
295 assuan_release(pwm->pctx);
297 pwm->pctx = NULL;
298 pwm->pinentry_pid = -1;
302 * Only called from a child process.
304 static void catchsig(int sig)
306 switch (sig) {
307 case SIGALRM:
308 if (++gelapsed >= gtimeout) {
309 terminate_pinentry(gpwm);
310 grc = gpg_err_make(GPG_ERR_SOURCE_PINENTRY, GPG_ERR_CANCELED);
312 else
313 alarm(1);
314 break;
315 default:
316 break;
320 gpg_error_t _getpin(pwm_t *pwm, char **result, size_t *len,
321 pwmd_pinentry_t which)
323 grc = 0;
324 gpg_error_t rc = set_pinentry_strings(pwm, which);
326 if (rc) {
327 _pinentry_disconnect(pwm);
328 return rc;
331 if (result)
332 *result = NULL;
334 if (gtimeout) {
335 signal(SIGALRM, catchsig);
336 alarm(1);
339 if (result)
340 *result = NULL;
342 rc = pinentry_command(pwm, result, len,
343 which == PWMD_PINENTRY_CONFIRM ? "CONFIRM" : "GETPIN");
346 * Since there was input cancel any timeout setting.
348 alarm(0);
349 signal(SIGALRM, SIG_DFL);
351 if (rc) {
352 _pinentry_disconnect(pwm);
354 /* This lets PWMD_OPTION_PINENTRY_TIMEOUT work. Note that it is not
355 * thread safe do to the global variables. */
356 if (gpg_err_code(rc) == GPG_ERR_EOF)
357 rc = GPG_ERR_CANCELED;
360 return rc;
363 gpg_error_t _pwmd_getpin(pwm_t *pwm, const char *filename, char **result,
364 size_t *len, pwmd_pinentry_t which)
366 gpg_error_t rc;
367 char *p = pwm->filename;
369 if (!pwm)
370 return GPG_ERR_INV_ARG;
372 if (which == PWMD_PINENTRY_CLOSE) {
373 _pinentry_disconnect(pwm);
374 return 0;
377 if (!result && which != PWMD_PINENTRY_CONFIRM)
378 return GPG_ERR_INV_ARG;
380 /* Another handle is using this pinentry method. Allowing this instance
381 * would reset the timeout and global handle which wouldn't be good. */
382 if (gpwm)
383 return GPG_ERR_PIN_BLOCKED;
385 if (pwm->pinentry_timeout != 0) {
386 gpwm = pwm;
387 gtimeout = abs(pwm->pinentry_timeout);
388 gelapsed = 0;
391 pwm->filename = (char *)filename;
392 rc = _getpin(pwm, result, len, which);
393 pwm->filename = p;
395 /* Don't timeout when an invalid passphrase was entered. */
396 gtimeout = 0;
397 gpwm = NULL;
398 return rc;