Add debug code for W32CE
[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 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, write to the Free Software
24    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include "pinentrydialog.h"
32 #include "pinentry.h"
33
34 #include <qapplication.h>
35 #include <QIcon>
36 #include <QString>
37 #include <qwidget.h>
38 #include <qmessagebox.h>
39 #include <QPushButton>
40
41 #include <stdio.h>
42 #include <stdlib.h>
43 #ifndef HAVE_W32CE_SYSTEM
44 # include <errno.h>
45 #endif
46
47 #ifdef HAVE_W32CE_SYSTEM
48 # include <winbase.h>
49 # include <winioctl.h>
50 #endif
51
52 #include <memory>
53 #include <stdexcept>
54
55 #ifdef FALLBACK_CURSES
56 #include <pinentry-curses.h>
57 #endif
58
59 static QString escape_accel( const QString & s ) {
60
61   QString result;
62   result.reserve( s.size() );
63
64   bool afterUnderscore = false;
65
66   for ( unsigned int i = 0, end = s.size() ; i != end ; ++i ) {
67     const QChar ch = s[i];
68     if ( ch == QLatin1Char( '_' ) )
69       {
70         if ( afterUnderscore ) // escaped _
71           {
72             result += QLatin1Char( '_' );
73             afterUnderscore = false;
74           }
75         else // accel
76           {
77             afterUnderscore = true;
78           }
79       }
80     else
81       {
82         if ( afterUnderscore || // accel
83              ch == QLatin1Char( '&' ) ) // escape & from being interpreted by Qt
84           result += QLatin1Char( '&' );
85         result += ch;
86         afterUnderscore = false;
87       }
88   }
89
90   if ( afterUnderscore )
91     // trailing single underscore: shouldn't happen, but deal with it robustly:
92     result += QLatin1Char( '_' );
93
94   return result;
95 }
96
97 /* Hack for creating a QWidget with a "foreign" window ID */
98 class ForeignWidget : public QWidget
99 {
100 public:
101   explicit ForeignWidget( WId wid ) : QWidget( 0 )
102   {
103     QWidget::destroy();
104     create( wid, false, false );
105   }
106
107   ~ForeignWidget()
108   {
109     destroy( false, false );
110   }
111 };
112
113 namespace {
114     class InvalidUtf8 : public std::invalid_argument {
115     public:
116         InvalidUtf8() : std::invalid_argument( "invalid utf8" ) {}
117         ~InvalidUtf8() throw() {}
118     };
119 }
120
121 static const bool GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8 = false;
122
123 static QString from_utf8( const char * s ) {
124     const QString result = QString::fromUtf8( s );
125     if ( result.contains( QChar::ReplacementCharacter ) )
126       {
127         if ( GPG_AGENT_IS_PORTED_TO_ONLY_SEND_UTF8 )
128             throw InvalidUtf8();
129         else
130             return QString::fromLocal8Bit( s );
131       }
132     
133     return result;
134 }
135
136 static int
137 qt_cmd_handler (pinentry_t pe)
138 {
139   QWidget *parent = 0;
140
141   /* FIXME: Add parent window ID to pinentry and GTK.  */
142   if (pe->parent_wid)
143     parent = new ForeignWidget ((WId) pe->parent_wid);
144
145   int want_pass = !!pe->pin;
146
147   const QString ok =
148       pe->ok             ? escape_accel( from_utf8( pe->ok ) ) :
149       pe->default_ok     ? escape_accel( from_utf8( pe->default_ok ) ) :
150       /* else */           QLatin1String( "&OK" ) ;
151   const QString cancel =
152       pe->cancel         ? escape_accel( from_utf8( pe->cancel ) ) :
153       pe->default_cancel ? escape_accel( from_utf8( pe->default_cancel ) ) :
154       /* else */           QLatin1String( "&Cancel" ) ;
155   const QString title =
156       pe->title ? from_utf8( pe->title ) :
157       /* else */  QLatin1String( "pinentry-qt4" ) ;
158       
159
160   if (want_pass)
161     {
162       PinEntryDialog pinentry (parent, 0, true, !!pe->quality_bar);
163
164       pinentry.setPinentryInfo (pe);
165       pinentry.setPrompt (escape_accel (from_utf8 (pe->prompt)) );
166       pinentry.setDescription (from_utf8 (pe->description));
167       if ( pe->title )
168           pinentry.setWindowTitle( from_utf8( pe->title ) );
169
170       /* If we reuse the same dialog window.  */
171       pinentry.setPin (secqstring());
172
173       pinentry.setOkText (ok);
174       pinentry.setCancelText (cancel);
175       if (pe->error)
176         pinentry.setError (from_utf8 (pe->error));
177       if (pe->quality_bar)
178         pinentry.setQualityBar (from_utf8 (pe->quality_bar));
179       if (pe->quality_bar_tt)
180         pinentry.setQualityBarTT (from_utf8 (pe->quality_bar_tt));
181
182       bool ret = pinentry.exec ();
183       if (!ret)
184         return -1;
185
186       const secstring pinUtf8(pinentry.pin().toUtf8());
187       const char *pin = pinUtf8.data();
188
189       int len = strlen (pin);
190       if (len >= 0)
191         {
192           pinentry_setbufferlen (pe, len + 1);
193           if (pe->pin)
194             {
195               strcpy (pe->pin, pin);
196               return len;
197             }
198         }
199       return -1;
200     }
201   else
202     {
203       const QString desc  = pe->description ? from_utf8 ( pe->description ) : QString();
204       const QString notok = pe->notok       ? escape_accel (from_utf8 ( pe->notok )) : QString();
205
206       const QMessageBox::StandardButtons buttons =
207           pe->one_button ? QMessageBox::Ok :
208           pe->notok      ? QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel :
209           /* else */       QMessageBox::Ok|QMessageBox::Cancel ;
210
211       QMessageBox box( QMessageBox::Information, title, desc, buttons, parent );
212
213       const struct {
214           QMessageBox::StandardButton button;
215           QString label;
216       } buttonLabels[] = {
217           { QMessageBox::Ok,     ok     },
218           { QMessageBox::Yes,    ok     },
219           { QMessageBox::No,     notok  },
220           { QMessageBox::Cancel, cancel },
221       };
222
223       for ( size_t i = 0 ; i < sizeof buttonLabels / sizeof *buttonLabels ; ++i )
224         if ( (buttons & buttonLabels[i].button) && !buttonLabels[i].label.isEmpty() )
225             box.button( buttonLabels[i].button )->setText( buttonLabels[i].label );
226
227       box.setIconPixmap( icon() );
228
229       if ( !pe->one_button )
230         box.setDefaultButton( QMessageBox::Cancel );
231
232       box.show();
233       raiseWindow( &box );
234
235       const int rc = box.exec();
236
237       if ( rc == QMessageBox::Cancel )
238         pe->canceled = true;
239
240       return rc == QMessageBox::Ok || rc == QMessageBox::Yes ;
241
242     }
243 }
244
245 static int
246 qt_cmd_handler_ex (pinentry_t pe)
247 {
248   try {
249     return qt_cmd_handler (pe);
250   } catch ( const InvalidUtf8 & ) {
251     pe->locale_err = true;
252     return pe->pin ? -1 : false ;
253   } catch ( ... ) {
254     pe->canceled = true;
255     return pe->pin ? -1 : false ;
256   }
257 }
258
259 pinentry_cmd_handler_t pinentry_cmd_handler = qt_cmd_handler_ex;
260
261
262 #ifdef HAVE_W32CE_SYSTEM
263 /* Create a pipe.  WRITE_END shall have the opposite value of the one
264    pssed to _assuan_w32ce_prepare_pipe; see there for more
265    details.  */
266 #define GPGCEDEV_IOCTL_MAKE_PIPE                                        \
267   CTL_CODE (FILE_DEVICE_STREAMS, 2049, METHOD_BUFFERED, FILE_ANY_ACCESS)
268 static HANDLE
269 w32ce_finish_pipe (int rvid, int write_end)
270 {
271   HANDLE hd;
272
273   hd = CreateFile (L"GPG1:", write_end? GENERIC_WRITE : GENERIC_READ,
274                    FILE_SHARE_READ | FILE_SHARE_WRITE,
275                    NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,NULL);
276   if (hd != INVALID_HANDLE_VALUE)
277     {
278       if (!DeviceIoControl (hd, GPGCEDEV_IOCTL_MAKE_PIPE,
279                             &rvid, sizeof rvid, NULL, 0, NULL, NULL))
280         {
281           DWORD lastrc = GetLastError ();
282           fprintf (stderr, "rvid %d, DeviceIoControl() failed: rc=%d\n",
283                    rvid, (int)GetLastError ());
284           CloseHandle (hd);
285           hd = INVALID_HANDLE_VALUE;
286           SetLastError (lastrc);
287         }
288     }
289   else
290     fprintf (stderr, "rvid %d, CreateFile(GPG1) failed: rc=%d\n",
291              rvid, (int)GetLastError ());
292
293   return hd;
294 }
295 #endif /*HAVE_W32CE_SYSTEM*/
296
297
298
299 /* WindowsCE uses a very strange way of handling the standard streams.
300    There is a function SetStdioPath to associate a standard stream
301    with a file or a device but what we really want is to use pipes as
302    standard streams.  Despite that we implement pipes using a device,
303    we would have some limitations on the number of open pipes due to
304    the 3 character limit of device file name.  Thus we don't take this
305    path.  Another option would be to install a file system driver with
306    support for pipes; this would allow us to get rid of the device
307    name length limitation.  However, with GnuPG we can get away be
308    redefining the standard streams and passing the handles to be used
309    on the command line.  This has also the advantage that it makes
310    creating a process much easier and does not require the
311    SetStdioPath set and restore game.  The caller needs to pass the
312    rendezvous ids using up to three options:
313
314      -&S0=<rvid> -&S1=<rvid> -&S2=<rvid>
315
316    They are all optional but they must be the first arguments on the
317    command line.  Parsing stops as soon as an invalid option is found.
318    These rendezvous ids are then used to finish the pipe creation.*/
319 #ifdef HAVE_W32CE_SYSTEM
320 static void
321 parse_std_file_handles (int *argcp, char ***argvp)
322 {
323   int argc = *argcp;
324   char **argv = *argvp;
325   const char *s;
326   int fd;
327   int i;
328   int fixup = 0;
329
330   if (!argc)
331     return;
332
333   for (argc--, argv++; argc; argc--, argv++)
334     {
335       s = *argv;
336       if (*s == '-' && s[1] == '&' && s[2] == 'S'
337           && (s[3] == '0' || s[3] == '1' || s[3] == '2')
338           && s[4] == '=' 
339           && (strchr ("-01234567890", s[5]) || !strcmp (s+5, "null")))
340         {
341           if (s[5] == 'n')
342             fd = (int)(-1);
343           else
344             fd = (int)w32ce_finish_pipe (atoi (s+5), s[3] != '0');
345           if (s[3] == '0' && fd != -1)
346             pinentry_set_std_fd (0, fd);
347           else if (s[3] == '1' && fd != -1)
348             pinentry_set_std_fd (1, fd);
349           fixup++;
350         }
351       else
352         break;
353     }
354
355   if (fixup)
356     {
357       argc = *argcp;
358       argc -= fixup;
359       *argcp = argc;
360
361       argv = *argvp;
362       for (i=1; i < argc; i++)
363         argv[i] = argv[i + fixup];
364       for (; i < argc + fixup; i++)
365         argv[i] = NULL;
366     }
367
368
369 }
370 #endif /*HAVE_W32CE_SYSTEM*/
371
372
373 int
374 main (int argc, char *argv[])
375 {
376 #ifdef HAVE_W32CE_SYSTEM
377   parse_std_file_handles (&argc, &argv);
378 #endif
379   pinentry_init ("pinentry-qt-qt4");
380
381   std::auto_ptr<QApplication> app;
382
383 #ifdef FALLBACK_CURSES
384   if (!pinentry_have_display (argc, argv))
385     pinentry_cmd_handler = curses_cmd_handler;
386   else
387 #endif
388     {
389       /* Qt does only understand -display but not --display; thus we
390          are fixing that here.  The code is pretty simply and may get
391          confused if an argument is called "--display". */
392       char **new_argv, *p;
393       size_t n;
394       int i, done;
395
396       for (n=0,i=0; i < argc; i++)
397         n += strlen (argv[i])+1;
398       n++;
399       new_argv = (char**)calloc (argc+1, sizeof *new_argv);
400       if (new_argv)
401         *new_argv = (char*)malloc (n);
402       if (!new_argv || !*new_argv)
403         {
404 #ifndef HAVE_W32CE_SYSTEM
405           fprintf (stderr, "pinentry-qt-qt4: can't fixup argument list: %s\n",
406                    strerror (errno));
407 #else
408           /* Since WinCE does not show the stderr output we leave out a 
409              GetLastError() message */
410           fprintf (stderr, "pinentry-qt-qt4: can't fixup argument list"); 
411 #endif
412           exit (EXIT_FAILURE);
413
414         }
415       for (done=0,p=*new_argv,i=0; i < argc; i++)
416         if (!done && !strcmp (argv[i], "--display"))
417           {
418             new_argv[i] = strcpy (p, argv[i]+1);
419             p += strlen (argv[i]+1) + 1;
420             done = 1;
421           }
422         else
423           {
424             new_argv[i] = strcpy (p, argv[i]);
425             p += strlen (argv[i]) + 1;
426           }
427
428       /* We use a modal dialog window, so we don't need the application
429          window anymore.  */
430       i = argc;
431       app.reset (new QApplication (i, new_argv));
432       const QIcon icon( QLatin1String( ":/document-encrypt.png" ) );
433       app->setWindowIcon( icon );
434     }
435
436
437   /* Consumes all arguments.  */
438   if (pinentry_parse_opts (argc, argv))
439     {
440       printf ("pinentry-qt-qt4 (pinentry) " /* VERSION */ "\n");
441       return EXIT_SUCCESS;
442     }
443   else
444     {
445       return pinentry_loop () ? EXIT_FAILURE : EXIT_SUCCESS ;
446     }
447
448 }