Merge branch 'STABLE-BRANCH-2-2' into master
[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 <https://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 static FILE *log_stream;
29
30
31 static int
32 reply (const char *fmt, ...)
33 {
34   int result;
35   va_list ap;
36
37   if (log_stream)
38     {
39       fprintf (log_stream, "> ");
40       va_start (ap, fmt);
41       vfprintf (log_stream, fmt, ap);
42       va_end (ap);
43     }
44   va_start (ap, fmt);
45   result = vprintf (fmt, ap);
46   va_end (ap);
47
48   fflush (stdout);
49   return result;
50 }
51
52
53 /* Return the first line from FNAME, removing it from the file.  */
54 static char *
55 get_passphrase (const char *fname)
56 {
57   char *passphrase = NULL;
58   size_t fname_len;
59   char *fname_new;
60   FILE *source, *sink;
61   char linebuf[80];
62
63   fname_len = strlen (fname);
64   fname_new = malloc (fname_len + 5);
65   if (fname_new == NULL)
66     {
67       perror ("malloc");
68       exit (1);
69     }
70   snprintf (fname_new, fname_len + 5, "%s.new", fname);
71
72   source = fopen (fname, "r");
73   if (! source)
74     {
75       perror (fname);
76       exit (1);
77     }
78
79   sink = fopen (fname_new, "w");
80   if (! sink)
81     {
82       perror (fname_new);
83       exit (1);
84     }
85
86   while (fgets (linebuf, sizeof linebuf, source))
87     {
88       linebuf[sizeof linebuf - 1] = 0;
89       if (passphrase == NULL)
90         {
91           passphrase = strdup (linebuf);
92           if (passphrase == NULL)
93             {
94               perror ("strdup");
95               exit (1);
96             }
97         }
98       else
99         fputs (linebuf, sink);
100     }
101
102   if (ferror (source))
103     {
104       perror (fname);
105       exit (1);
106     }
107
108   if (ferror (sink))
109     {
110       perror (fname_new);
111       exit (1);
112     }
113
114   fclose (source);
115   fclose (sink);
116   if (remove (fname))
117     {
118       fprintf (stderr, "Failed to remove %s: %s",
119                fname, strerror (errno));
120       exit (1);
121     }
122
123   if (rename (fname_new, fname))
124     {
125       fprintf (stderr, "Failed to rename %s to %s: %s",
126                fname, fname_new, strerror (errno));
127       exit (1);
128     }
129
130   free (fname_new);
131   return passphrase;
132 }
133
134 \f
135 #define whitespacep(p)   (*(p) == ' ' || *(p) == '\t' \
136                           || *(p) == '\r' || *(p) == '\n')
137
138 /* rstrip line.  */
139 static void
140 rstrip (char *buffer)
141 {
142   char *p;
143   if (!*buffer)
144     return; /* This is to avoid p = buffer - 1 */
145   for (p = buffer + strlen (buffer) - 1; p >= buffer; p--)
146     {
147       if (! whitespacep (p))
148         break;
149       *p = 0;
150     }
151 }
152
153
154 /* Skip over options in LINE.
155
156    Blanks after the options are also removed.  Options are indicated
157    by two leading dashes followed by a string consisting of non-space
158    characters.  The special option "--" indicates an explicit end of
159    options; all what follows will not be considered an option.  The
160    first no-option string also indicates the end of option parsing. */
161 char *
162 skip_options (const char *line)
163 {
164   while (whitespacep (line))
165     line++;
166   while (*line == '-' && line[1] == '-')
167     {
168       while (*line && !whitespacep (line))
169         line++;
170       while (whitespacep (line))
171         line++;
172     }
173   return (char*) line;
174 }
175
176
177 /* Return a pointer to the argument of the option with NAME.  If such
178    an option is not given, NULL is returned. */
179 char *
180 option_value (const char *line, const char *name)
181 {
182   char *s;
183   int n = strlen (name);
184
185   s = strstr (line, name);
186   if (s && s >= skip_options (line))
187     return NULL;
188   if (s && (s == line || whitespacep (s-1))
189       && s[n] && (whitespacep (s+n) || s[n] == '='))
190     {
191       s += n + 1;
192       s += strspn (s, " ");
193       if (*s && !whitespacep(s))
194         return s;
195     }
196   return NULL;
197 }
198
199 int
200 main (int argc, char **argv)
201 {
202   char *args;
203   char *option_user_data = NULL;
204   int got_environment_user_data;
205   char *logfile;
206   char *passphrasefile;
207   char *passphrase;
208
209   /* We get our options via PINENTRY_USER_DATA.  */
210   (void) argc, (void) argv;
211
212   setvbuf (stdin, NULL, _IOLBF, BUFSIZ);
213   setvbuf (stdout, NULL, _IOLBF, BUFSIZ);
214
215   args = getenv ("PINENTRY_USER_DATA");
216   got_environment_user_data = !!args;
217   if (! args)
218     args = "";
219
220  restart:
221   logfile = option_value (args, "--logfile");
222   if (logfile)
223     {
224       char *p = logfile, more;
225       while (*p && ! whitespacep (p))
226         p++;
227       more = !! *p;
228       *p = 0;
229       args = more ? p+1 : p;
230
231       log_stream = fopen (logfile, "a");
232       if (! log_stream)
233         {
234           perror (logfile);
235           return 1;
236         }
237     }
238
239   passphrasefile = option_value (args, "--passphrasefile");
240   if (passphrasefile)
241     {
242       char *p = passphrasefile, more;
243       while (*p && ! whitespacep (p))
244         p++;
245       more = !! *p;
246       *p = 0;
247       args = more ? p+1 : p;
248
249       passphrase = get_passphrase (passphrasefile);
250       if (! passphrase)
251         {
252           reply ("# Passphrasefile '%s' is empty.  Terminating.\n",
253                  passphrasefile);
254           return 1;
255         }
256
257       rstrip (passphrase);
258     }
259   else
260     {
261       passphrase = skip_options (args);
262       if (*passphrase == 0)
263         passphrase = "no PINENTRY_USER_DATA -- using default passphrase";
264     }
265
266   reply ("# fake-pinentry(%u) started.  Passphrase='%s'.\n",
267          (unsigned int)getpid (), passphrase);
268   reply ("OK - what's up?\n");
269
270   while (! feof (stdin))
271     {
272       char buffer[1024];
273
274       if (fgets (buffer, sizeof buffer, stdin) == NULL)
275         break;
276
277       if (log_stream)
278         fprintf (log_stream, "< %s", buffer);
279
280       rstrip (buffer);
281
282 #define OPT_USER_DATA   "OPTION pinentry-user-data="
283
284       if (strncmp (buffer, "GETPIN", 6) == 0)
285         reply ("D %s\n", passphrase);
286       else if (strncmp (buffer, "BYE", 3) == 0)
287         {
288           reply ("OK\n");
289           break;
290         }
291       else if (strncmp (buffer, OPT_USER_DATA, strlen (OPT_USER_DATA)) == 0)
292         {
293           if (got_environment_user_data)
294             {
295               reply ("OK - I already got the data from the environment.\n");
296               continue;
297             }
298
299           if (log_stream)
300             fclose (log_stream);
301           log_stream = NULL;
302           free (option_user_data);
303           option_user_data = args = strdup (buffer + strlen (OPT_USER_DATA));
304           goto restart;
305         }
306
307       reply ("OK\n");
308     }
309
310 #undef OPT_USER_DATA
311
312   reply ("# Connection terminated.\n");
313   if (log_stream)
314     fclose (log_stream);
315
316   free (option_user_data);
317   return 0;
318 }