Set DISPLAY environment variable if --display option
[pinentry-qt.git] / main.cpp
1 /*
2    main.cpp - A (not yet) secure Qt 4 dialog for PIN entry.
3
4    Copyright (C) 2002, 2008 Klarälvdalens Datakonsult AB (KDAB)
5    Copyright (C) 2003, 2010 g10 Code GmbH
6    Copyright 2007 Ingo Klöcker
7
8    Written by Steffen Hansen <steffen@klaralvdalens-datakonsult.se>.
9    Modified by Marcus Brinkmann <marcus@g10code.de>.
10    Modified by Marc Mutz <marc@kdab.com>
11
12    This program is free software; you can redistribute it and/or
13    modify it under the terms of the GNU General Public License as
14    published by the Free Software Foundation; either version 2 of the
15    License, or (at your option) any later version.
16
17    This program is distributed in the hope that it will be useful, but
18    WITHOUT ANY WARRANTY; without even the implied warranty of
19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20    General Public License for more details.
21
22    You should have received a copy of the GNU General Public License
23    along with this program; if not, see <http://www.gnu.org/licenses/>. 
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include "pinentrydialog.h"
31 #include "pinentry.h"
32
33 #include <qapplication.h>
34 #include <QIcon>
35 #include <QString>
36 #include <qwidget.h>
37 #include <qmessagebox.h>
38 #include <QPushButton>
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #ifndef HAVE_W32CE_SYSTEM
43 # include <errno.h>
44 #endif
45
46 #ifdef HAVE_W32CE_SYSTEM
47 # include <winbase.h>
48 # include <winioctl.h>
49 #endif
50
51 #include <memory>
52 #include <stdexcept>
53
54 #ifdef FALLBACK_CURSES
55 #include <pinentry-curses.h>
56 #endif
57
58 static QString escape_accel( const QString & s ) {
59
60   QString result;
61   result.reserve( s.size() );
62
63   bool afterUnderscore = false;
64
65   for ( unsigned int i = 0, end = s.size() ; i != end ; ++i ) {
66     const QChar ch = s[i];
67     if ( ch == QLatin1Char( '_' ) )
68       {
69         if ( afterUnderscore ) // escaped _
70           {
71             result += QLatin1Char( '_' );
72             afterUnderscore = false;
73           }
74         else // accel
75           {
76             afterUnderscore = true;
77           }
78       }
79     else
80       {
81         if ( afterUnderscore || // accel
82              ch == QLatin1Char( '&' ) ) // escape & from being interpreted by Qt
83           result += QLatin1Char( '&' );
84         result += ch;
85         afterUnderscore = false;
86       }
87   }
88
89   if ( afterUnderscore )
90     // trailing single underscore: shouldn't happen, but deal with it robustly:
91     result += QLatin1Char( '_' );
92
93   return result;
94 }
95
96 /* Hack for creating a QWidget with a "foreign" window ID */
97 class ForeignWidget : public QWidget
98 {
99 public:
100   explicit ForeignWidget( WId wid ) : QWidget( 0 )
101   {
102     QWidget::destroy();
103     create( wid, false, false );
104   }
105
106   ~ForeignWidget()
107   {
108     destroy( false, false );
109   }
110 };
111
112 namespace {
113     class InvalidUtf8 : public std::invalid_argument {
114     public:
115         InvalidUtf8() : std::invalid_argument( "invalid utf8" ) {}
116         ~InvalidUtf8() throw() {}
117     };
118 }
119
120 static const bool GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8 = false;
121
122 static QString from_utf8( const char * s ) {
123     const QString result = QString::fromUtf8( s );
124     if ( result.contains( QChar::ReplacementCharacter ) )
125       {
126         if ( GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8 )
127             throw InvalidUtf8();
128         else
129             return QString::fromLocal8Bit( s );
130       }
131     
132     return result;
133 }
134
135 static int
136 qt_cmd_handler (pinentry_t pe)
137 {
138   QWidget *parent = 0;
139
140   /* FIXME: Add parent window ID to pinentry and GTK.  */
141   if (pe->parent_wid)
142     parent = new ForeignWidget ((WId) pe->parent_wid);
143
144   int want_pass = !!pe->pin;
145
146   const QString ok =
147       pe->ok             ? escape_accel( from_utf8( pe->ok ) ) :
148       pe->default_ok     ? escape_accel( from_utf8( pe->default_ok ) ) :
149       /* else */           QLatin1String( "&OK" ) ;
150   const QString cancel =
151       pe->cancel         ? escape_accel( from_utf8( pe->cancel ) ) :
152       pe->default_cancel ? escape_accel( from_utf8( pe->default_cancel ) ) :
153       /* else */           QLatin1String( "&Cancel" ) ;
154   const QString title =
155       pe->title ? from_utf8( pe->title ) :
156       /* else */  QLatin1String( "pinentry-qt4" ) ;
157       
158
159   if (want_pass)
160     {
161       PinEntryDialog pinentry (parent, 0, true, !!pe->quality_bar);
162
163       pinentry.setPinentryInfo (pe);
164       pinentry.setPrompt (escape_accel (from_utf8 (pe->prompt)) );
165       pinentry.setDescription (from_utf8 (pe->description));
166       if ( pe->title )
167           pinentry.setWindowTitle( from_utf8( pe->title ) );
168
169       /* If we reuse the same dialog window.  */
170       pinentry.setPin (secqstring());
171
172       pinentry.setOkText (ok);
173       pinentry.setCancelText (cancel);
174       if (pe->error)
175         pinentry.setError (from_utf8 (pe->error));
176       if (pe->quality_bar)
177         pinentry.setQualityBar (from_utf8 (pe->quality_bar));
178       if (pe->quality_bar_tt)
179         pinentry.setQualityBarTT (from_utf8 (pe->quality_bar_tt));
180
181       bool ret = pinentry.exec ();
182       if (!ret)
183         return -1;
184
185       const secstring pinUtf8(pinentry.pin().toUtf8());
186       const char *pin = pinUtf8.data();
187
188       int len = strlen (pin);
189       if (len >= 0)
190         {
191           pinentry_setbufferlen (pe, len + 1);
192           if (pe->pin)
193             {
194               strcpy (pe->pin, pin);
195               return len;
196             }
197         }
198       return -1;
199     }
200   else
201     {
202       const QString desc  = pe->description ? from_utf8 ( pe->description ) : QString();
203       const QString notok = pe->notok       ? escape_accel (from_utf8 ( pe->notok )) : QString();
204
205       const QMessageBox::StandardButtons buttons =
206           pe->one_button ? QMessageBox::Ok :
207           pe->notok      ? QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel :
208           /* else */       QMessageBox::Ok|QMessageBox::Cancel ;
209
210       QMessageBox box( QMessageBox::Information, title, desc, buttons, parent );
211
212       const struct {
213           QMessageBox::StandardButton button;
214           QString label;
215       } buttonLabels[] = {
216           { QMessageBox::Ok,     ok     },
217           { QMessageBox::Yes,    ok     },
218           { QMessageBox::No,     notok  },
219           { QMessageBox::Cancel, cancel },
220       };
221
222       for ( size_t i = 0 ; i < sizeof buttonLabels / sizeof *buttonLabels ; ++i )
223         if ( (buttons & buttonLabels[i].button) && !buttonLabels[i].label.isEmpty() )
224             box.button( buttonLabels[i].button )->setText( buttonLabels[i].label );
225
226       box.setIconPixmap( icon() );
227
228       if ( !pe->one_button )
229         box.setDefaultButton( QMessageBox::Cancel );
230
231       box.show();
232       raiseWindow( &box );
233
234       const int rc = box.exec();
235
236       if ( rc == QMessageBox::Cancel )
237         pe->canceled = true;
238
239       return rc == QMessageBox::Ok || rc == QMessageBox::Yes ;
240
241     }
242 }
243
244 static int
245 qt_cmd_handler_ex (pinentry_t pe)
246 {
247   try {
248     return qt_cmd_handler (pe);
249   } catch ( const InvalidUtf8 & ) {
250     pe->locale_err = true;
251     return pe->pin ? -1 : false ;
252   } catch ( ... ) {
253     pe->canceled = true;
254     return pe->pin ? -1 : false ;
255   }
256 }
257
258 pinentry_cmd_handler_t pinentry_cmd_handler = qt_cmd_handler_ex;
259
260
261 #ifdef HAVE_W32CE_SYSTEM
262 /* Create a pipe.  WRITE_END shall have the opposite value of the one
263    pssed to _assuan_w32ce_prepare_pipe; see there for more
264    details.  */
265 #define GPGCEDEV_IOCTL_MAKE_PIPE                                        \
266   CTL_CODE (FILE_DEVICE_STREAMS, 2049, METHOD_BUFFERED, FILE_ANY_ACCESS)
267 static HANDLE
268 w32ce_finish_pipe (int rvid, int write_end)
269 {
270   HANDLE hd;
271
272   hd = CreateFile (L"GPG1:", write_end? GENERIC_WRITE : GENERIC_READ,
273                    FILE_SHARE_READ | FILE_SHARE_WRITE,
274                    NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);
275   if (hd != INVALID_HANDLE_VALUE)
276     {
277       if (!DeviceIoControl (hd, GPGCEDEV_IOCTL_MAKE_PIPE,
278                             &rvid, sizeof rvid, NULL, 0, NULL, NULL))
279         {
280           DWORD lastrc = GetLastError ();
281           fprintf (stderr, "rvid %d, DeviceIoControl() failed: rc=%d\n",
282                    rvid, (int)GetLastError ());
283           CloseHandle (hd);
284           hd = INVALID_HANDLE_VALUE;
285           SetLastError (lastrc);
286         }
287     }
288   else
289     fprintf (stderr, "rvid %d, CreateFile(GPG1) failed: rc=%d\n",
290              rvid, (int)GetLastError ());
291
292   return hd;
293 }
294 #endif /*HAVE_W32CE_SYSTEM*/
295
296
297
298 /* WindowsCE uses a very strange way of handling the standard streams.
299    There is a function SetStdioPath to associate a standard stream
300    with a file or a device but what we really want is to use pipes as
301    standard streams.  Despite that we implement pipes using a device,
302    we would have some limitations on the number of open pipes due to
303    the 3 character limit of device file name.  Thus we don't take this
304    path.  Another option would be to install a file system driver with
305    support for pipes; this would allow us to get rid of the device
306    name length limitation.  However, with GnuPG we can get away be
307    redefining the standard streams and passing the handles to be used
308    on the command line.  This has also the advantage that it makes
309    creating a process much easier and does not require the
310    SetStdioPath set and restore game.  The caller needs to pass the
311    rendezvous ids using up to three options:
312
313      -&S0=<rvid> -&S1=<rvid> -&S2=<rvid>
314
315    They are all optional but they must be the first arguments on the
316    command line.  Parsing stops as soon as an invalid option is found.
317    These rendezvous ids are then used to finish the pipe creation.*/
318 #ifdef HAVE_W32CE_SYSTEM
319 static void
320 parse_std_file_handles (int *argcp, char ***argvp)
321 {
322   int argc = *argcp;
323   char **argv = *argvp;
324   const char *s;
325   int fd;
326   int i;
327   int fixup = 0;
328
329   if (!argc)
330     return;
331
332   for (argc--, argv++; argc; argc--, argv++)
333     {
334       s = *argv;
335       if (*s == '-' && s[1] == '&' && s[2] == 'S'
336           && (s[3] == '0' || s[3] == '1' || s[3] == '2')
337           && s[4] == '=' 
338           && (strchr ("-01234567890", s[5]) || !strcmp (s+5, "null")))
339         {
340           if (s[5] == 'n')
341             fd = (int)(-1);
342           else
343             fd = (int)w32ce_finish_pipe (atoi (s+5), s[3] != '0');
344           if (s[3] == '0' && fd != -1)
345             pinentry_set_std_fd (0, (int)fd);
346           else if (s[3] == '1' && fd != -1)
347             pinentry_set_std_fd (1, (int)fd);
348           fixup++;
349         }
350       else
351         break;
352     }
353
354   if (fixup)
355     {
356       argc = *argcp;
357       argc -= fixup;
358       *argcp = argc;
359
360       argv = *argvp;
361       for (i=1; i < argc; i++)
362         argv[i] = argv[i + fixup];
363       for (; i < argc + fixup; i++)
364         argv[i] = NULL;
365     }
366
367
368 }
369 #endif /*HAVE_W32CE_SYSTEM*/
370
371
372 #ifdef Q_WS_X11
373 static void
374 pinentry_set_display_env (const char *display)
375 {
376   setenv ("DISPLAY", display, true);
377 }
378 #endif
379
380
381 int
382 main (int argc, char *argv[])
383 {
384 #ifdef HAVE_W32CE_SYSTEM
385   pinentry_set_std_fd (0, (int)fileno (stdin));
386   pinentry_set_std_fd (1, (int)fileno (stdout));
387   parse_std_file_handles (&argc, &argv);
388 #endif
389   /* Print the version as early as possible.  This avoids error
390      messages printed due to pinentry_init etc.  */ 
391   for (int i=1; i < argc; i++) 
392     {
393       if (!strcmp (argv[i], "--"))
394         break;
395       else if (!strcmp (argv[i], "--version"))
396         {
397           fputs ("pinentry-qt " VERSION "\n"
398                  "Copyright (C) 2010 g10 Code GmbH\n"
399                  "License GPLv2+: GNU GPL version 2 or later "
400                  "<http://gnu.org/licenses/gpl.html>\n"
401                  "This is free software: you are free to change and "
402                  "redistribute it.\n"
403                  "There is NO WARRANTY, to the extent permitted by law.\n"
404                  "\n"
405                  "Note: This is a fork of the standard pinentry software\n"
406                  , stdout);
407           break;
408         }
409       
410     }
411   
412   pinentry_init ("pinentry-qt");
413
414   std::auto_ptr<QApplication> app;
415
416 #ifdef FALLBACK_CURSES
417   if (!pinentry_have_display (argc, argv))
418     pinentry_cmd_handler = curses_cmd_handler;
419   else
420 #endif
421     {
422       /* Qt does only understand -display but not --display; thus we
423          are fixing that here.  The code is pretty simply and may get
424          confused if an argument is called "--display". */
425       char **new_argv, *p;
426       size_t n;
427       int i, done;
428
429       for (n=0,i=0; i < argc; i++)
430         n += strlen (argv[i])+1;
431       n++;
432       new_argv = (char**)calloc (argc+1, sizeof *new_argv);
433       if (new_argv)
434         *new_argv = (char*)malloc (n);
435       if (!new_argv || !*new_argv)
436         {
437 #ifndef HAVE_W32CE_SYSTEM
438           fprintf (stderr, "pinentry-qt: can't fixup argument list: %s\n",
439                    strerror (errno));
440 #else
441           /* Since WinCE does not show the stderr output we leave out a 
442              GetLastError() message */
443           fprintf (stderr, "pinentry-qt: can't fixup argument list"); 
444 #endif
445           exit (EXIT_FAILURE);
446
447         }
448       for (done=0,p=*new_argv,i=0; i < argc; i++)
449         if (!done && !strcmp (argv[i], "--display"))
450           {
451             new_argv[i] = strcpy (p, argv[i]+1);
452             p += strlen (argv[i]+1) + 1;
453             done = 1;
454 #ifdef Q_WS_X11
455             /* Set DISPLAY environment variable if --display option is
456                passed to pinentry-qt. This solves the misbehavior of
457                the X11 server on Maemo5 if DISPLAY is reset e.g.  with
458                DISPLAY="" pinentry-qt --display :0.0  */
459             if (i+1 < argc)
460               {
461                 pinentry_set_display_env (argv[i+1]);
462               }
463             else
464               {
465                 fprintf (stderr,
466                          "pinentry-qt: --display switch set without a "
467                          "parameter\n");
468                 exit (EXIT_FAILURE);
469               }
470 #endif
471           }
472         else
473           {
474             new_argv[i] = strcpy (p, argv[i]);
475             p += strlen (argv[i]) + 1;
476           }
477
478       /* We use a modal dialog window, so we don't need the application
479          window anymore.  */
480       i = argc;
481       app.reset (new QApplication (i, new_argv));
482       const QIcon icon( QLatin1String( ":/document-encrypt.png" ) );
483       app->setWindowIcon( icon );
484     }
485
486
487   /* Consumes all arguments.  */
488   if (pinentry_parse_opts (argc, argv))
489     {
490       /* We already printed the version.  */
491       exit (EXIT_SUCCESS);
492     }
493   else
494     {
495       return pinentry_loop () ? EXIT_FAILURE : EXIT_SUCCESS ;
496     }
497
498 }