Added integer casts to the use of file descriptors to fix MSVC compiling
[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       else
289         fprintf (stderr, "rvid %d, mapped to %p\n", rvid, hd);
290     }
291   else
292     fprintf (stderr, "rvid %d, CreateFile(GPG1) failed: rc=%d\n",
293              rvid, (int)GetLastError ());
294
295   return hd;
296 }
297 #endif /*HAVE_W32CE_SYSTEM*/
298
299
300
301 /* WindowsCE uses a very strange way of handling the standard streams.
302    There is a function SetStdioPath to associate a standard stream
303    with a file or a device but what we really want is to use pipes as
304    standard streams.  Despite that we implement pipes using a device,
305    we would have some limitations on the number of open pipes due to
306    the 3 character limit of device file name.  Thus we don't take this
307    path.  Another option would be to install a file system driver with
308    support for pipes; this would allow us to get rid of the device
309    name length limitation.  However, with GnuPG we can get away be
310    redefining the standard streams and passing the handles to be used
311    on the command line.  This has also the advantage that it makes
312    creating a process much easier and does not require the
313    SetStdioPath set and restore game.  The caller needs to pass the
314    rendezvous ids using up to three options:
315
316      -&S0=<rvid> -&S1=<rvid> -&S2=<rvid>
317
318    They are all optional but they must be the first arguments on the
319    command line.  Parsing stops as soon as an invalid option is found.
320    These rendezvous ids are then used to finish the pipe creation.*/
321 #ifdef HAVE_W32CE_SYSTEM
322 static void
323 parse_std_file_handles (int *argcp, char ***argvp)
324 {
325   int argc = *argcp;
326   char **argv = *argvp;
327   const char *s;
328   int fd;
329   int i;
330   int fixup = 0;
331
332   if (!argc)
333     return;
334
335   for (argc--, argv++; argc; argc--, argv++)
336     {
337       s = *argv;
338       fprintf (stderr, "parse_std_file_handles '%s'\n", s);
339       if (*s == '-' && s[1] == '&' && s[2] == 'S'
340           && (s[3] == '0' || s[3] == '1' || s[3] == '2')
341           && s[4] == '=' 
342           && (strchr ("-01234567890", s[5]) || !strcmp (s+5, "null")))
343         {
344           if (s[5] == 'n')
345             fd = (int)(-1);
346           else
347             fd = (int)w32ce_finish_pipe (atoi (s+5), s[3] != '0');
348           if (s[3] == '0' && fd != -1)
349             pinentry_set_std_fd (0, (int)fd);
350           else if (s[3] == '1' && fd != -1)
351             pinentry_set_std_fd (1, (int)fd);
352           fixup++;
353         }
354       else
355         break;
356     }
357
358   if (fixup)
359     {
360       argc = *argcp;
361       argc -= fixup;
362       *argcp = argc;
363
364       argv = *argvp;
365       for (i=1; i < argc; i++)
366         argv[i] = argv[i + fixup];
367       for (; i < argc + fixup; i++)
368         argv[i] = NULL;
369     }
370
371
372 }
373 #endif /*HAVE_W32CE_SYSTEM*/
374
375
376 int
377 main (int argc, char *argv[])
378 {
379 #ifdef HAVE_W32CE_SYSTEM
380   pinentry_set_std_fd (0, (int)fileno (stdin));
381   pinentry_set_std_fd (1, (int)fileno (stdout));
382   parse_std_file_handles (&argc, &argv);
383 #endif
384   pinentry_init ("pinentry-qt-qt4");
385
386   std::auto_ptr<QApplication> app;
387
388 #ifdef FALLBACK_CURSES
389   if (!pinentry_have_display (argc, argv))
390     pinentry_cmd_handler = curses_cmd_handler;
391   else
392 #endif
393     {
394       /* Qt does only understand -display but not --display; thus we
395          are fixing that here.  The code is pretty simply and may get
396          confused if an argument is called "--display". */
397       char **new_argv, *p;
398       size_t n;
399       int i, done;
400
401       for (n=0,i=0; i < argc; i++)
402         n += strlen (argv[i])+1;
403       n++;
404       new_argv = (char**)calloc (argc+1, sizeof *new_argv);
405       if (new_argv)
406         *new_argv = (char*)malloc (n);
407       if (!new_argv || !*new_argv)
408         {
409 #ifndef HAVE_W32CE_SYSTEM
410           fprintf (stderr, "pinentry-qt-qt4: can't fixup argument list: %s\n",
411                    strerror (errno));
412 #else
413           /* Since WinCE does not show the stderr output we leave out a 
414              GetLastError() message */
415           fprintf (stderr, "pinentry-qt-qt4: can't fixup argument list"); 
416 #endif
417           exit (EXIT_FAILURE);
418
419         }
420       for (done=0,p=*new_argv,i=0; i < argc; i++)
421         if (!done && !strcmp (argv[i], "--display"))
422           {
423             new_argv[i] = strcpy (p, argv[i]+1);
424             p += strlen (argv[i]+1) + 1;
425             done = 1;
426           }
427         else
428           {
429             new_argv[i] = strcpy (p, argv[i]);
430             p += strlen (argv[i]) + 1;
431           }
432
433       /* We use a modal dialog window, so we don't need the application
434          window anymore.  */
435       i = argc;
436       app.reset (new QApplication (i, new_argv));
437       const QIcon icon( QLatin1String( ":/document-encrypt.png" ) );
438       app->setWindowIcon( icon );
439     }
440
441
442   /* Consumes all arguments.  */
443   if (pinentry_parse_opts (argc, argv))
444     {
445       printf ("pinentry-qt-qt4 (pinentry) " /* VERSION */ "\n");
446       return EXIT_SUCCESS;
447     }
448   else
449     {
450       return pinentry_loop () ? EXIT_FAILURE : EXIT_SUCCESS ;
451     }
452
453 }