1 /*
2    main.cpp - A Fltk based dialog for PIN entry.
3 
4    Copyright (C) 2016 Anatoly madRat L. Berenblit
5 
6    Written by Anatoly madRat L. Berenblit <madrat-@users.noreply.github.com>.
7 
8    This program is free software; you can redistribute it and/or
9    modify it under the terms of the GNU General Public License as
10    published by the Free Software Foundation; either version 2 of the
11    License, or (at your option) any later version.
12 
13    This program is distributed in the hope that it will be useful, but
14    WITHOUT ANY WARRANTY; without even the implied warranty of
15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16    General Public License for more details.
17 
18    You should have received a copy of the GNU General Public License
19    along with this program; if not, write to the Free Software
20    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
21    SPDX-License-Identifier: GPL-2.0+
22 */
23 
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 
28 #define PGMNAME (PACKAGE_NAME"-fltk")
29 
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <getopt.h>
35 #include <assert.h>
36 
37 #include "memory.h"
38 #include <memory>
39 
40 #include <pinentry.h>
41 #ifdef FALLBACK_CURSES
42 #include <pinentry-curses.h>
43 #endif
44 
45 #include <string>
46 #include <string.h>
47 #include <stdexcept>
48 
49 
50 #include <FL/Fl.H>
51 #include <FL/Fl_Window.H>
52 #include <FL/fl_ask.H>
53 
54 #include "pinwindow.h"
55 #include "passwindow.h"
56 #include "qualitypasswindow.h"
57 
58 #define CONFIRM_STRING "Confirm"
59 #define REPEAT_ERROR_STRING "Texts do not match"
60 #define OK_STRING "OK"
61 #define CANCEL_STRING "Cancel"
62 
63 char *application = NULL;
64 
65 static std::string escape_accel_utf8(const char *s)
66 {
67 	std::string result;
68 	if (NULL != s)
69 	{
70 		result.reserve(strlen(s));
71 		for (const char *p = s; *p; ++p)
72 		{
73 			if ('&' == *p)
74 				result.push_back(*p);
75 			result.push_back(*p);
76 		}
77 	}
78 	return result;
79 }
80 
81 // For button labels
82 // Accelerator '_' (used e.g. by GPG2) is converted to '&' (for FLTK)
83 // '&' is escaped as in escape_accel_utf8()
84 static std::string convert_accel_utf8(const char *s)
85 {
86 	static bool last_was_underscore = false;
87 	std::string result;
88 	if (NULL != s)
89 	{
90 		result.reserve(strlen(s));
91 		for (const char *p = s; *p; ++p)
92 		{
93 			// & => &&
94 			if ('&' == *p)
95 				result.push_back(*p);
96 			// _ => & (handle '__' as escaped underscore)
97 			if ('_' == *p)
98 			{
99 				if (last_was_underscore)
100 				{
101 					result.push_back(*p);
102 					last_was_underscore = false;
103 				}
104 				else
105 					last_was_underscore = true;
106 			}
107 			else
108 			{
109 				if (last_was_underscore)
110 					result.push_back('&');
111 				result.push_back(*p);
112 				last_was_underscore = false;
113 			}
114 		}
115 	}
116 	return result;
117 }
118 
119 class cancel_exception
120 {
121 
122 };
123 
124 static int get_quality(const char *passwd, void *ptr)
125 {
126 	if (NULL == passwd || 0 == *passwd)
127 		return 0;
128 
129 	pinentry_t* pe = reinterpret_cast<pinentry_t*>(ptr);
130 	return pinentry_inq_quality(*pe, passwd, strlen(passwd));
131 }
132 
133 bool is_short(const char *str)
134 {
135 	return fl_utf_nb_char(reinterpret_cast<const unsigned char*>(str), strlen(str)) < 16;
136 }
137 
138 bool is_empty(const char *str)
139 {
140 	return (NULL == str) || (0 == *str);
141 }
142 
143 static int fltk_cmd_handler(pinentry_t pe)
144 {
145 	int ret = -1;
146 
147 	try
148 	{
149 		// TODO: Add parent window to pinentry-fltk window
150 		//if (pe->parent_wid){}
151 		std::string title  = !is_empty(pe->title)?pe->title:PGMNAME;
152 		std::string ok 	   = convert_accel_utf8(pe->ok?pe->ok:(pe->default_ok?pe->default_ok:OK_STRING));
153 		std::string cancel = convert_accel_utf8(pe->cancel?pe->cancel:(pe->default_cancel?pe->default_cancel:CANCEL_STRING));
154 
155 		if (!!pe->pin) // password (or confirmation)
156 		{
157 			std::unique_ptr<PinWindow> window;
158 
159 			bool isSimple = (NULL == pe->quality_bar) &&	// pinenty.h: If this is not NULL ...
160 							is_empty(pe->error) && is_empty(pe->description) &&
161 							is_short(pe->prompt);
162 			if (isSimple)
163 			{
164 				assert(NULL == pe->description);
165 				window.reset(PinWindow::create());
166 				window->prompt(pe->prompt);
167 			}
168 			else
169 			{
170 				PassWindow *pass = NULL;
171 
172 				if (pe->quality_bar) // pinenty.h: If this is not NULL ...
173 				{
174 					QualityPassWindow *p = QualityPassWindow::create(get_quality, &pe);
175 					window.reset(p);
176 					pass = p;
177 					p->quality(pe->quality_bar);
178 				}
179 				else
180 				{
181 					pass = PassWindow::create();
182 					window.reset(pass);
183 				}
184 
185 				if (NULL == pe->description)
186 				{
187 					pass->description(pe->prompt);
188 					pass->prompt(" ");
189 				}
190 				else
191 				{
192 					pass->description(pe->description);
193 					pass->prompt(escape_accel_utf8(pe->prompt).c_str());
194 				}
195 				pass->description(pe->description);
196 				pass->prompt(escape_accel_utf8(pe->prompt).c_str());
197 
198 
199 				if (NULL != pe->error)
200 					pass->error(pe->error);
201 			}
202 
203 			window->ok(ok.c_str());
204 			window->cancel(cancel.c_str());
205 			window->title(title.c_str());
206 			window->showModal((NULL != application)?1:0, &application);
207 
208 			if (NULL == window->passwd())
209 				throw cancel_exception();
210 
211 			const std::string password = window->passwd();
212 			window.reset();
213 
214 			if (pe->repeat_passphrase)
215 			{
216 				const char *dont_match = NULL;
217 				do
218 				{
219 					if (NULL == dont_match && is_short(pe->repeat_passphrase))
220 					{
221 						window.reset(PinWindow::create());
222 						window->prompt(escape_accel_utf8(pe->repeat_passphrase).c_str());
223 					}
224 					else
225 					{
226 						PassWindow *pass = PassWindow::create();
227 						window.reset(pass);
228 						pass->description(pe->repeat_passphrase);
229 						pass->prompt(" ");
230 						pass->error(dont_match);
231 					}
232 
233 					window->ok(ok.c_str());
234 					window->cancel(cancel.c_str());
235 					window->title(title.c_str());
236 					window->showModal();
237 
238 					if (NULL == window->passwd())
239 						throw cancel_exception();
240 
241 					if (password == window->passwd())
242 					{
243 						pe->repeat_okay = 1;
244 						ret = 1;
245 						break;
246 					}
247 					else
248 					{
249 							dont_match = (NULL!=pe->repeat_error_string)? pe->repeat_error_string:REPEAT_ERROR_STRING;
250 						}
251 					} while (true);
252 				}
253 				else
254 					ret = 1;
255 
256 				pinentry_setbufferlen(pe, password.size()+1);
257 				if (pe->pin)
258 				{
259 					memcpy(pe->pin, password.c_str(), password.size()+1);
260 					pe->result = password.size();
261 					ret = password.size();
262 				}
263 			}
264 			else
265 			{
266 				// Confirmation or Message Dialog title, desc
267 				Fl_Window dummy(0,0, 1,1);
268 
269 				dummy.border(0);
270 				dummy.show((NULL != application)?1:0, &application);
271 				dummy.hide();
272 
273 				fl_message_title(title.c_str());
274 
275 				int result = -1;
276 
277 				const char *message = (NULL != pe->description)?pe->description:CONFIRM_STRING;
278 
279 				if (pe->one_button)
280 				{
281 					fl_ok = ok.c_str();
282 					fl_message("%s", message);
283 					result = 1; // OK
284 				}
285 				else if (pe->notok)
286 				{
287 					switch (fl_choice("%s", ok.c_str(), cancel.c_str(), pe->notok, message))
288 					{
289 					case 0: result = 1; break;
290 					case 2: result = 0; break;
291 					default:
292 					case 1: result = -1;break;
293 					}
294 				}
295 				else
296 				{
297 					switch (fl_choice("%s", ok.c_str(), cancel.c_str(), NULL, message))
298 					{
299 					case 0: result = 1; break;
300 					default:
301 					case 1: result = -1;break;
302 					}
303 				}
304 
305 				// cancel ->         pe->canceled = true, 0
306 				// ok/y -> 1
307 				// no -> 0
308 				if (-1 == result)
309 					pe->canceled = true;
310 				ret = (1 == result);
311 			}
312 			Fl::check();
313 		}
314 		catch (const cancel_exception&)
315 		{
316 			ret = -1;
317 		}
318 		catch (...)
319 		{
320 			ret = -1;
321 		}
322 			// do_touch_file(pe); only for NCURSES?
323 		return ret;
324 	}
325 
326 pinentry_cmd_handler_t pinentry_cmd_handler = fltk_cmd_handler;
327 
328 int main(int argc, char *argv[])
329 {
330 	application = *argv;
331 	pinentry_init(PGMNAME);
332 
333 #ifdef FALLBACK_CURSES
334 	if (!pinentry_have_display(argc, argv))
335 		pinentry_cmd_handler = curses_cmd_handler;
336 	else
337 #endif
338 	{
339 		//FLTK understood only -D (--display)
340 		// and should be converted into -di[splay]
341 		const static struct option long_options[] =
342 		{
343 			{"display", required_argument,	0, 'D' },
344 			{NULL, 		no_argument,		0, 0   }
345 		};
346 
347 		for (int i = 0; i < argc-1; ++i)
348 		{
349 			switch (getopt_long(argc-i, argv+i, "D:", long_options, NULL))
350 			{
351 				case 'D':
352 					{
353 						char* emul[] = {application, (char*)"-display", optarg};
354 						Fl::args(3, emul);
355 						i = argc;
356 						break;
357 					}
358 				default:
359 					break;
360 			}
361 		}
362 	}
363 
364 	pinentry_parse_opts(argc, argv);
365 	return pinentry_loop() ?EXIT_FAILURE:EXIT_SUCCESS;
366 }
367