* query.c (agent_askpin): Provide the default desc text depending
[gnupg.git] / agent / simple-pwquery.c
1 /* simple-pwquery.c - A simple password query cleint for gpg-agent
2  *      Copyright (C) 2002 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 2 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, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 /* This module is intended as a standalone client implementation to
22    gpg-agent's GET_PASSPHRASE command.  In particular it does not use
23    the Assuan library and can only cope with an already running
24    gpg-agent.  Some stuff is configurable in the header file. */
25
26 #ifdef HAVE_CONFIG_H
27 #include <config.h>
28 #endif
29 #include <stdlib.h>
30 #include <stddef.h>
31 #include <string.h>
32 #include <errno.h>
33 #include <unistd.h>
34 #include <sys/socket.h>
35 #include <sys/un.h>
36 #ifdef HAVE_LOCALE_H
37 #include <locale.h>
38 #endif
39
40 #define SIMPLE_PWQUERY_IMPLEMENTATION 1
41 #include "simple-pwquery.h"
42
43 #if defined(SPWQ_USE_LOGGING) && !defined(HAVE_JNLIB_LOGGING)
44 # undef SPWQ_USE_LOGGING
45 #endif
46
47 #ifndef _
48 #define _(a) (a)
49 #endif
50
51 #if !defined (hexdigitp) && !defined (xtoi_2)
52 #define digitp(p)   (*(p) >= '0' && *(p) <= '9')
53 #define hexdigitp(a) (digitp (a)                     \
54                       || (*(a) >= 'A' && *(a) <= 'F')  \
55                       || (*(a) >= 'a' && *(a) <= 'f'))
56 #define xtoi_1(p)   (*(p) <= '9'? (*(p)- '0'): \
57                      *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
58 #define xtoi_2(p)   ((xtoi_1(p) * 16) + xtoi_1((p)+1))
59 #endif
60
61
62 /* Write NBYTES of BUF to file descriptor FD. */
63 static int
64 writen (int fd, const void *buf, size_t nbytes)
65 {
66   size_t nleft = nbytes;
67   int nwritten;
68   
69   while (nleft > 0)
70     {
71       nwritten = write( fd, buf, nleft );
72       if (nwritten < 0)
73         {
74           if (errno == EINTR)
75             nwritten = 0;
76           else {
77 #ifdef SPWQ_USE_LOGGING
78             log_error ("write failed: %s\n", strerror (errno));
79 #endif
80             return SPWQ_IO_ERROR;
81           }
82         }
83       nleft -= nwritten;
84       buf = (const char*)buf + nwritten;
85     }
86     
87   return 0;
88 }
89
90
91 /* Read an entire line and return number of bytes read. */
92 static int
93 readline (int fd, char *buf, size_t buflen)
94 {
95   size_t nleft = buflen;
96   char *p;
97   int nread = 0;
98
99   while (nleft > 0)
100     {
101       int n = read (fd, buf, nleft);
102       if (n < 0)
103         {
104           if (errno == EINTR)
105             continue;
106           return -(SPWQ_IO_ERROR);
107         }
108       else if (!n)
109         {
110           return -(SPWQ_PROTOCOL_ERROR); /* incomplete line */
111         }
112       p = buf;
113       nleft -= n;
114       buf += n;
115       nread += n;
116       
117       for (; n && *p != '\n'; n--, p++)
118         ;
119       if (n)
120         {
121           break; /* at least one full line available - that's enough.
122                     This function is just a simple implementation, so
123                     it is okay to forget about pending bytes */
124         }
125     }
126
127   return nread; 
128 }
129
130
131 /* Send an option to the agent */
132 static int
133 agent_send_option (int fd, const char *name, const char *value)
134 {
135   char buf[200];
136   int nread;
137   char *line;
138   int i; 
139   
140   line = spwq_malloc (7 + strlen (name) + 1 + strlen (value) + 2);
141   if (!line)
142     return SPWQ_OUT_OF_CORE;
143   strcpy (stpcpy (stpcpy (stpcpy (
144                      stpcpy (line, "OPTION "), name), "="), value), "\n");
145   i = writen (fd, line, strlen (line));
146   spwq_free (line);
147   if (i)
148     return i;
149   
150   /* get response */
151   nread = readline (fd, buf, DIM(buf)-1);
152   if (nread < 0)
153     return -nread;
154   if (nread < 3)
155     return SPWQ_PROTOCOL_ERROR;
156   
157   if (buf[0] == 'O' && buf[1] == 'K' && (buf[2] == ' ' || buf[2] == '\n')) 
158     return 0; /* okay */
159
160   return SPWQ_ERR_RESPONSE;
161 }
162
163
164 /* Send all available options to the agent. */
165 static int 
166 agent_send_all_options (int fd)
167 {
168   char *dft_display = NULL;
169   char *dft_ttyname = NULL;
170   char *dft_ttytype = NULL;
171   int rc = 0;
172
173   dft_display = getenv ("DISPLAY");
174   if (dft_display)
175     {
176       if ((rc = agent_send_option (fd, "display", dft_display)))
177         return rc;
178     }
179
180   if (ttyname (1))
181     dft_ttyname = ttyname (1);
182   if (dft_ttyname)
183     {
184       if ((rc=agent_send_option (fd, "ttyname", dft_ttyname)))
185         return rc;
186     }
187
188   dft_ttytype = getenv ("TERM");
189   if (dft_ttyname && dft_ttytype)
190     {
191       if ((rc = agent_send_option (fd, "ttytype", dft_ttytype)))
192         return rc;
193     }
194
195 #if defined(HAVE_SETLOCALE) 
196   {
197     char *old_lc = NULL;
198     char *dft_lc = NULL;
199
200 #if defined(LC_CTYPE)
201     old_lc = setlocale (LC_CTYPE, NULL);
202     if (old_lc)
203       {
204         char *p = spwq_malloc (strlen (old_lc)+1);
205         if (!p)
206           return SPWQ_OUT_OF_CORE;
207         strcpy (p, old_lc);
208         old_lc = p;
209       }
210     dft_lc = setlocale (LC_CTYPE, "");
211     if (dft_ttyname && dft_lc)
212       rc = agent_send_option (fd, "lc-ctype", dft_lc);
213     if (old_lc)
214       {
215         setlocale (LC_CTYPE, old_lc);
216         spwq_free (old_lc);
217       }
218     if (rc)
219       return rc;
220 #endif
221
222 #if defined(LC_MESSAGES)
223     old_lc = setlocale (LC_MESSAGES, NULL);
224     if (old_lc)
225       {
226         char *p = spwq_malloc (strlen (old_lc)+1);
227         if (!p)
228           return SPWQ_OUT_OF_CORE;
229         strcpy (p, old_lc);
230         old_lc = p;
231       }
232     dft_lc = setlocale (LC_MESSAGES, "");
233     if (dft_ttyname && dft_lc)
234       rc = agent_send_option (fd, "lc-messages", dft_lc);
235     if (old_lc)
236       {
237         setlocale (LC_MESSAGES, old_lc);
238         spwq_free (old_lc);
239       }
240     if (rc)
241       return rc;
242 #endif
243   }
244 #endif /*HAVE_SETLOCALE*/
245
246   return 0;
247 }
248
249
250
251 /* Try to open a connection to the agent, send all options and return
252    the file descriptor for the connection.  Return -1 in case of
253    error. */
254 static int
255 agent_open (int *rfd)
256 {
257   int rc;
258   int fd;
259   char *infostr, *p;
260   struct sockaddr_un client_addr;
261   size_t len;
262   int prot;
263   char line[200];
264   int nread;
265
266   *rfd = -1;
267   infostr = getenv ( "GPG_AGENT_INFO" );
268   if ( !infostr ) 
269     {
270 #ifdef SPWQ_USE_LOGGING
271       log_error (_("gpg-agent is not available in this session\n"));
272 #endif
273       return SPWQ_NO_AGENT;
274     }
275
276   if ( !(p = strchr ( infostr, ':')) || p == infostr
277        || (p-infostr)+1 >= sizeof client_addr.sun_path ) 
278     {
279 #ifdef SPWQ_USE_LOGGING
280       log_error ( _("malformed GPG_AGENT_INFO environment variable\n"));
281 #endif
282       return SPWQ_NO_AGENT;
283     }
284   *p++ = 0;
285
286   while (*p && *p != ':')
287     p++;
288   prot = *p? atoi (p+1) : 0;
289   if ( prot != 1)
290     {
291 #ifdef SPWQ_USE_LOGGING
292       log_error (_("gpg-agent protocol version %d is not supported\n"),prot);
293 #endif
294       return SPWQ_PROTOCOL_ERROR;
295     }
296        
297   if( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1 ) 
298     {
299 #ifdef SPWQ_USE_LOGGING
300       log_error ("can't create socket: %s\n", strerror(errno) );
301 #endif
302       return SPWQ_SYS_ERROR;
303     }
304     
305   memset (&client_addr, 0, sizeof client_addr);
306   client_addr.sun_family = AF_UNIX;
307   strcpy (client_addr.sun_path, infostr);
308   len = (offsetof (struct sockaddr_un, sun_path)
309          + strlen(client_addr.sun_path) + 1);
310     
311   if (connect (fd, (struct sockaddr*)&client_addr, len ) == -1)
312     {
313 #ifdef SPWQ_USE_LOGGING
314       log_error ( _("can't connect to `%s': %s\n"), infostr, strerror (errno));
315 #endif
316       close (fd );
317       return SPWQ_IO_ERROR;
318     }
319
320   nread = readline (fd, line, DIM(line));
321   if (nread < 3 || !(line[0] == 'O' && line[1] == 'K'
322                      && (line[2] == '\n' || line[2] == ' ')) ) 
323     {
324 #ifdef SPWQ_USE_LOGGING
325       log_error ( _("communication problem with gpg-agent\n"));
326 #endif
327       close (fd );
328       return SPWQ_PROTOCOL_ERROR;
329     }
330
331   rc = agent_send_all_options (fd);
332   if (rc)
333     {
334 #ifdef SPWQ_USE_LOGGING
335       log_error (_("problem setting the gpg-agent options\n"));
336 #endif
337       close (fd);
338       return rc;
339     }
340
341   *rfd = fd;
342   return 0;
343 }
344
345
346 /* Copy text to BUFFER and escape as required.  Return a poiinter to
347    the end of the new buffer.  NOte that BUFFER must be large enough
348    to keep the entire text; allocataing it 3 times the size of TEXT
349    is sufficient. */
350 static char *
351 copy_and_escape (char *buffer, const char *text)
352 {
353   int i;
354   char *p = buffer;
355
356   for (i=0; text[i]; i++)
357     {
358       if (text[i] < ' ' || text[i] == '+')
359         {
360           sprintf (p, "%%%02X", text[i]);
361           p += 3;
362         }
363       else if (text[i] == ' ')
364         *p++ = '+';
365       else
366         *p++ = text[i];
367     }
368   return p;
369 }
370
371
372 /* Ask the gpg-agent for a passphrase and present the user with a
373    DESCRIPTION, a PROMPT and optiaonlly with a TRYAGAIN extra text.
374    If a CACHEID is not NULL it is used to locate the passphrase in in
375    the cache and store it under this ID.  If ERRORCODE is not NULL it
376    should point a variable receiving an errorcode; thsi errocode might
377    be 0 if the user canceled the operation.  The function returns NULL
378    to indicate an error. */
379 char *
380 simple_pwquery (const char *cacheid, 
381                 const char *tryagain,
382                 const char *prompt,
383                 const char *description,
384                 int *errorcode)
385 {
386   int fd = -1;
387   int nread;
388   char *result = NULL;
389   char *pw = NULL;
390   char *p;
391   int rc, i; 
392
393   rc = agent_open (&fd);
394   if (rc)
395     goto leave;
396
397   if (!cacheid)
398     cacheid = "X";
399   if (!tryagain)
400     tryagain = "X";
401   if (!prompt)
402     prompt = "X";
403   if (!description)
404     description = "X";
405
406   {
407     char *line;
408     /* We allocate 3 times the needed space so that there is enough
409        space for escaping. */
410     line = spwq_malloc (15
411                         + 3*strlen (cacheid) + 1
412                         + 3*strlen (tryagain) + 1
413                         + 3*strlen (prompt) + 1
414                         + 3*strlen (description) + 1
415                         + 2);
416     if (!line)
417       {
418         rc = SPWQ_OUT_OF_CORE;
419         goto leave;
420       }
421     strcpy (line, "GET_PASSPHRASE ");
422     p = line+15;
423     p = copy_and_escape (p, cacheid);
424     *p++ = ' ';
425     p = copy_and_escape (p, tryagain);
426     *p++ = ' ';
427     p = copy_and_escape (p, prompt);
428     *p++ = ' ';
429     p = copy_and_escape (p, description);
430     *p++ = '\n';
431     rc = writen (fd, line, p - line);
432     spwq_free (line);
433     if (rc)
434       goto leave;
435   }
436
437   /* get response */
438   pw = spwq_secure_malloc (500);
439   nread = readline (fd, pw, 499);
440   if (nread < 0)
441     {
442       rc = -nread;
443       goto leave;
444     }
445   if (nread < 3)
446     {
447       rc = SPWQ_PROTOCOL_ERROR;
448       goto leave;
449     }
450       
451   if (pw[0] == 'O' && pw[1] == 'K' && pw[2] == ' ') 
452     { /* we got a passphrase - convert it back from hex */
453       size_t pwlen = 0;
454       
455       for (i=3; i < nread && hexdigitp (pw+i); i+=2)
456         pw[pwlen++] = xtoi_2 (pw+i);
457       pw[pwlen] = 0; /* make a C String */
458       result = pw;
459       pw = NULL;
460     }
461   else if (nread > 7 && !memcmp (pw, "ERR 111", 7)
462       && (pw[7] == ' ' || pw[7] == '\n') )
463     {
464 #ifdef SPWQ_USE_LOGGING
465       log_info (_("canceled by user\n") );
466 #endif
467       *errorcode = 0; /* canceled */
468     }
469   else 
470     {
471 #ifdef SPWQ_USE_LOGGING
472       log_error (_("problem with the agent\n"));
473 #endif
474       rc = SPWQ_ERR_RESPONSE;
475     }
476         
477  leave:
478   if (errorcode)
479     *errorcode = rc;
480   if (fd != -1)
481     close (fd);
482   if (pw)
483     spwq_free (pw);
484   return result;
485 }