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