agent,tests,w32: Fix relaying pinentry user data, fix fake-pinentry.
[gnupg.git] / tests / openpgp / fake-pinentry.c
1 /* Fake pinentry program for the OpenPGP test suite.
2  *
3  * Copyright (C) 2016 g10 code GmbH
4  *
5  * This file is part of GnuPG.
6  *
7  * GnuPG is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * GnuPG is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include <errno.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <stdarg.h>
26 #include <unistd.h>
27
28 FILE *log_stream;
29
30 int
31 reply (const char *fmt, ...)
32 {
33   int result;
34   va_list ap;
35
36   if (log_stream)
37     {
38       fprintf (log_stream, "> ");
39       va_start (ap, fmt);
40       vfprintf (log_stream, fmt, ap);
41       va_end (ap);
42     }
43   va_start (ap, fmt);
44   result = vprintf (fmt, ap);
45   va_end (ap);
46
47   fflush (stdout);
48   return result;
49 }
50
51 /* Return the first line from FNAME, removing it from the file.  */
52 char *
53 get_passphrase (const char *fname)
54 {
55   char *passphrase = NULL;
56   size_t fname_len;
57   char *fname_new;
58   FILE *source, *sink;
59   char linebuf[80];
60
61   fname_len = strlen (fname);
62   fname_new = malloc (fname_len + 5);
63   if (fname_new == NULL)
64     {
65       perror ("malloc");
66       exit (1);
67     }
68   snprintf (fname_new, fname_len + 5, "%s.new", fname);
69
70   source = fopen (fname, "r");
71   if (! source)
72     {
73       perror (fname);
74       exit (1);
75     }
76
77   sink = fopen (fname_new, "w");
78   if (! sink)
79     {
80       perror (fname_new);
81       exit (1);
82     }
83
84   while (fgets (linebuf, sizeof linebuf, source))
85     {
86       linebuf[sizeof linebuf - 1] = 0;
87       if (passphrase == NULL)
88         {
89           passphrase = strdup (linebuf);
90           if (passphrase == NULL)
91             {
92               perror ("strdup");
93               exit (1);
94             }
95         }
96       else
97         fputs (linebuf, sink);
98     }
99
100   if (ferror (source))
101     {
102       perror (fname);
103       exit (1);
104     }
105
106   if (ferror (sink))
107     {
108       perror (fname_new);
109       exit (1);
110     }
111
112   fclose (source);
113   fclose (sink);
114   if (unlink (fname))
115     {
116       fprintf (stderr, "Failed to remove %s: %s",
117                fname, strerror (errno));
118       exit (1);
119     }
120
121   if (rename (fname_new, fname))
122     {
123       fprintf (stderr, "Failed to rename %s to %s: %s",
124                fname, fname_new, strerror (errno));
125       exit (1);
126     }
127   return passphrase;
128 }
129
130 \f
131 #define spacep(p)   (*(p) == ' ' || *(p) == '\t' \
132                      || *(p) == '\r' || *(p) == '\n')
133
134 /* rstrip line.  */
135 void
136 rstrip (char *buffer)
137 {
138   char *p;
139   for (p = buffer + strlen (buffer) - 1; p >= buffer; p--)
140     {
141       if (! spacep (p))
142         break;
143       *p = 0;
144     }
145 }
146
147
148 /* Skip over options in LINE.
149
150    Blanks after the options are also removed.  Options are indicated
151    by two leading dashes followed by a string consisting of non-space
152    characters.  The special option "--" indicates an explicit end of
153    options; all what follows will not be considered an option.  The
154    first no-option string also indicates the end of option parsing. */
155 char *
156 skip_options (const char *line)
157 {
158   while (spacep (line))
159     line++;
160   while (*line == '-' && line[1] == '-')
161     {
162       while (*line && !spacep (line))
163         line++;
164       while (spacep (line))
165         line++;
166     }
167   return (char*) line;
168 }
169
170
171 /* Return a pointer to the argument of the option with NAME.  If such
172    an option is not given, NULL is returned. */
173 char *
174 option_value (const char *line, const char *name)
175 {
176   char *s;
177   int n = strlen (name);
178
179   s = strstr (line, name);
180   if (s && s >= skip_options (line))
181     return NULL;
182   if (s && (s == line || spacep (s-1))
183       && s[n] && (spacep (s+n) || s[n] == '='))
184     {
185       s += n + 1;
186       s += strspn (s, " ");
187       if (*s && !spacep(s))
188         return s;
189     }
190   return NULL;
191 }
192
193 int
194 main (int argc, char **argv)
195 {
196   char *args;
197   char *option_user_data = NULL;
198   int got_environment_user_data;
199   char *logfile;
200   char *passphrasefile;
201   char *passphrase;
202
203   /* We get our options via PINENTRY_USER_DATA.  */
204   (void) argc, (void) argv;
205
206   setvbuf (stdin, NULL, _IOLBF, BUFSIZ);
207   setvbuf (stdout, NULL, _IOLBF, BUFSIZ);
208
209   args = getenv ("PINENTRY_USER_DATA");
210   got_environment_user_data = args != NULL;
211   if (! args)
212     args = "";
213
214  restart:
215   logfile = option_value (args, "--logfile");
216   if (logfile)
217     {
218       char *p = logfile, more;
219       while (*p && ! spacep (p))
220         p++;
221       more = !! *p;
222       *p = 0;
223       args = more ? p+1 : p;
224
225       log_stream = fopen (logfile, "a");
226       if (! log_stream)
227         {
228           perror (logfile);
229           return 1;
230         }
231     }
232
233   passphrasefile = option_value (args, "--passphrasefile");
234   if (passphrasefile)
235     {
236       char *p = passphrasefile, more;
237       while (*p && ! spacep (p))
238         p++;
239       more = !! *p;
240       *p = 0;
241       args = more ? p+1 : p;
242
243       passphrase = get_passphrase (passphrasefile);
244       if (! passphrase)
245         {
246           reply ("# Passphrasefile '%s' is empty.  Terminating.\n",
247                  passphrasefile);
248           return 1;
249         }
250
251       rstrip (passphrase);
252     }
253   else
254     {
255       passphrase = skip_options (args);
256       if (*passphrase == 0)
257         passphrase = "no PINENTRY_USER_DATA -- using default passphrase";
258     }
259
260   reply ("# fake-pinentry(%d) started.  Passphrase='%s'.\n",
261          getpid (), passphrase);
262   reply ("OK - what's up?\n");
263
264   while (! feof (stdin))
265     {
266       char buffer[1024], *p;
267
268       if (fgets (buffer, sizeof buffer, stdin) == NULL)
269         break;
270
271       if (log_stream)
272         fprintf (log_stream, "< %s", buffer);
273
274       rstrip (buffer);
275
276       if (strncmp (buffer, "GETPIN", 6) == 0)
277         reply ("D %s\n", passphrase);
278       else if (strncmp (buffer, "BYE", 3) == 0)
279         {
280           reply ("OK\n");
281           break;
282         }
283 #define OPT_USER_DATA   "OPTION pinentry-user-data="
284       else if (strncmp (buffer, OPT_USER_DATA, strlen (OPT_USER_DATA)) == 0)
285         {
286           if (got_environment_user_data)
287             {
288               reply ("OK - I already got the data from the environment.\n");
289               continue;
290             }
291
292           if (log_stream)
293             fclose (log_stream);
294           log_stream = NULL;
295           free (option_user_data);
296           option_user_data = args = strdup (buffer + strlen (OPT_USER_DATA));
297           goto restart;
298         }
299
300       reply ("OK\n");
301     }
302
303   reply ("# Connection terminated.\n");
304   if (log_stream)
305     fclose (log_stream);
306
307   free (option_user_data);
308   return 0;
309 }