1 /* pinentry-efl.c
2    Copyright (C) 2017 Obsidian-Studios, Inc.
3      Author William L. Thomson Jr. <wlt@o-sinc.com>
4 
5    Based on pinentry-gtk2.c
6    Copyright (C) 1999 Robert Bihlmeyer <robbe@orcus.priv.at>
7    Copyright (C) 2001, 2002, 2007, 2015 g10 Code GmbH
8    Copyright (C) 2004 by Albrecht Dreß <albrecht.dress@arcor.de>
9 
10    pinentry-efl is a pinentry application for the EFL widget set.
11    It tries to follow the Gnome Human Interface Guide as close as
12    possible.
13 
14    This program is free software; you can redistribute it and/or modify
15    it under the terms of the GNU General Public License as published by
16    the Free Software Foundation; either version 2 of the License, or
17    (at your option) any later version.
18 
19    This program is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22    GNU General Public License for more details.
23 
24    You should have received a copy of the GNU General Public License
25    along with this program; if not, write to the Free Software
26    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27  */
28 
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32 #include <Elementary.h>
33 #include <Ecore_X.h>
34 #include <gpg-error.h>
35 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
36 #pragma GCC diagnostic push
37 #pragma GCC diagnostic ignored "-Wstrict-prototypes"
38 #endif
39 #if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
40 #pragma GCC diagnostic pop
41 #endif
42 
43 #ifdef HAVE_GETOPT_H
44 #include <getopt.h>
45 #else
46 #include "getopt.h"
47 #endif /* HAVE_GETOPT_H */
48 
49 #include "pinentry.h"
50 
51 #ifdef FALLBACK_CURSES
52 #include "pinentry-curses.h"
53 #endif
54 
55 #define PGMNAME "pinentry-efl"
56 
57 #ifndef VERSION
58 #define VERSION
59 #endif
60 
61 #define ENTRY_HIDE "Hide entry"
62 #define ENTRY_SHOW "Show entry"
63 
64 typedef enum { CONFIRM_CANCEL, CONFIRM_OK, CONFIRM_NOTOK } confirm_value_t;
65 
66 static const int WIDTH = 480;
67 static const int BUTTON_HEIGHT = 27;
68 static const int BUTTON_WIDTH = 70;
69 static const int BUTTON_ICON_SIZE = 13;
70 static const int PADDING = 5;
71 
72 static Eina_Bool got_input;
73 static Ecore_Timer *timer;
74 static Evas_Object *check_label;
75 static Evas_Object *error_label;
76 static Evas_Object *entry;
77 static Evas_Object *repeat_entry;
78 static Evas_Object *qualitybar;
79 static Evas_Object *win;
80 static char **pargv;
81 static int grab_failed;
82 static int passphrase_ok;
83 static int confirm_mode;
84 static int pargc;
85 static confirm_value_t confirm_value;
86 static pinentry_t pinentry;
87 
88 pinentry_cmd_handler_t pinentry_cmd_handler;
89 
90 static void
91 quit (void)
92 {
93   evas_object_del(win);
94   elm_exit();
95   ecore_main_loop_quit ();
96 }
97 
98 static void
99 delete_event (void *data EINA_UNUSED,
100               Evas_Object *obj EINA_UNUSED,
101               void *event EINA_UNUSED)
102 {
103   pinentry->close_button = 1;
104   quit ();
105 }
106 
107 static void
108 changed_text_handler (void *data EINA_UNUSED,
109                       Evas_Object *obj,
110                       void *event EINA_UNUSED)
111 {
112   const char *s;
113   int length;
114   int percent;
115 
116   got_input = EINA_TRUE;
117 
118   if (pinentry->repeat_passphrase && repeat_entry)
119     {
120       elm_object_text_set (repeat_entry, "");
121       elm_object_text_set (error_label, "");
122     }
123 
124   if (!qualitybar || !pinentry->quality_bar)
125     return;
126 
127   s = elm_object_text_get (obj);
128   if (!s)
129     s = "";
130   length = strlen (s);
131   percent = length? pinentry_inq_quality (pinentry, s, length) : 0;
132   evas_object_color_set(qualitybar,
133                         255 - ( 2.55 * percent ),
134                         2.55 * percent, 0, 255);
135   elm_progressbar_value_set (qualitybar, (double) percent / 100.0);
136 }
137 
138 static void
139 on_check (void *data EINA_UNUSED, Evas_Object *obj, void *event EINA_UNUSED)
140 {
141   if(elm_check_state_get(obj))
142     {
143         elm_entry_password_set(entry, EINA_FALSE);
144         elm_object_text_set(check_label,ENTRY_HIDE);
145     }
146   else
147     {
148         elm_entry_password_set(entry, EINA_TRUE);
149         elm_object_text_set(check_label,ENTRY_SHOW);
150     }
151   evas_object_size_hint_min_set(check_label,
152                                 ELM_SCALE_SIZE(BUTTON_WIDTH),
153                                 ELM_SCALE_SIZE(BUTTON_HEIGHT));
154   evas_object_size_hint_align_set(check_label, 0, 1);
155 }
156 
157 static void
158 on_click (void *data, Evas_Object *obj EINA_UNUSED, void *event EINA_UNUSED)
159 {
160   if (confirm_mode)
161     {
162       confirm_value = (confirm_value_t) data;
163       quit ();
164       return;
165     }
166 
167   if (data)
168     {
169       const char *s;
170       const char *s2;
171 
172       s = elm_entry_entry_get (entry);
173       if (!s)
174 	s = "";
175 
176       if (pinentry->repeat_passphrase && repeat_entry)
177 	{
178 	  s2 = elm_entry_entry_get (repeat_entry);
179 	  if (!s2)
180 	    s2 = "";
181 	  if (strcmp (s, s2))
182 	    {
183               elm_object_text_set(error_label,
184                                   pinentry->repeat_error_string?
185                                   pinentry->repeat_error_string:
186 				   "not correctly repeated");
187               elm_object_focus_set(entry,EINA_TRUE);
188               return;
189 	    }
190 	  pinentry->repeat_okay = 1;
191 	}
192 
193       passphrase_ok = 1;
194       pinentry_setbufferlen (pinentry, strlen (s) + 1);
195       if (pinentry->pin)
196 	strncpy (pinentry->pin, s, strlen(s) + 1);
197     }
198   quit ();
199 }
200 
201 static void
202 enter_callback (void *data, Evas_Object * obj, void *event_info EINA_UNUSED)
203 {
204   if (data)
205     elm_object_focus_set (data, 1);
206   else
207     on_click ((void *) CONFIRM_OK, obj, NULL);
208 }
209 
210 static Eina_Bool
211 timeout_cb (const void * data)
212 {
213   pinentry_t pe = (pinentry_t)data;
214   if (!got_input)
215     {
216       ecore_main_loop_quit();
217       if (pe)
218         pe->specific_err = gpg_error (GPG_ERR_TIMEOUT);
219     }
220 
221   timer = NULL;
222   return ECORE_CALLBACK_DONE;
223 }
224 
225 static void
226 create_window (void)
227 {
228   char *txt;
229   Evas_Object *icon;
230   Evas_Object *obj;
231   Evas_Object *table;
232   int btn_txt_len = 0;
233   int row = 0;
234   int ok_len = 0;
235 
236   win = elm_win_util_dialog_add(NULL,"pinentry","enter pin");
237   elm_win_autodel_set(win, EINA_TRUE);
238   elm_win_center(win,EINA_TRUE,EINA_TRUE);
239   evas_object_smart_callback_add(win, "delete,request", delete_event, NULL);
240 
241   table = elm_table_add(win);
242   elm_table_padding_set(table,ELM_SCALE_SIZE(PADDING),0);
243   evas_object_size_hint_padding_set (table,
244                                      ELM_SCALE_SIZE(PADDING),
245                                      ELM_SCALE_SIZE(PADDING),
246                                      ELM_SCALE_SIZE(PADDING),
247                                      ELM_SCALE_SIZE(PADDING));
248   evas_object_show(table);
249 
250   if (pinentry->title)
251     {
252       txt = pinentry_utf8_to_local (pinentry->lc_ctype,
253                                     pinentry->title);
254       elm_win_title_set ( win, txt );
255       free (txt);
256     }
257 
258   /* Description Label */
259   if (pinentry->description)
260     {
261       char* aligned;
262       int len;
263 
264       obj = elm_label_add(table);
265       elm_label_line_wrap_set (obj, ELM_WRAP_WORD);
266       txt = pinentry_utf8_to_local (pinentry->lc_ctype, pinentry->description);
267       len = strlen(txt)+20; // 20 chars for align tag
268       aligned = calloc(len+1,sizeof(char));
269       if(aligned)
270         {
271           snprintf(aligned,len, "<align=left>%s</align>",txt);
272           elm_object_text_set(obj,aligned);
273           free (aligned);
274         } else
275           elm_object_text_set(obj,txt);
276       free (txt);
277       evas_object_size_hint_weight_set(obj, EVAS_HINT_EXPAND, 0);
278       evas_object_size_hint_align_set(obj, EVAS_HINT_FILL, 0);
279       elm_table_pack(table, obj, 1, row, 5, 1);
280       evas_object_show(obj);
281       row++;
282     }
283   if (!confirm_mode && (pinentry->error || pinentry->repeat_passphrase))
284     {
285     /* Error Label */
286     if (pinentry->error)
287         txt = pinentry_utf8_to_local (pinentry->lc_ctype, pinentry->error);
288       else
289         txt = "";
290       obj = elm_label_add(table);
291       evas_object_color_set(obj, 255, 0, 0, 255);
292       elm_object_text_set(obj,txt);
293       elm_object_style_set(obj,"slide_bounce");
294       elm_label_slide_duration_set(obj, 10);
295       elm_label_slide_mode_set(obj, ELM_LABEL_SLIDE_MODE_ALWAYS);
296       elm_label_slide_go(obj);
297       evas_object_size_hint_weight_set(obj, EVAS_HINT_EXPAND, 0);
298       evas_object_size_hint_align_set(obj, EVAS_HINT_FILL, 0);
299       elm_table_pack(table, obj, 1, row, 5, 1);
300       evas_object_show(obj);
301       if (pinentry->error)
302         free (txt);
303       row++;
304     }
305 
306   qualitybar = NULL;
307 
308   if (!confirm_mode)
309     {
310 
311     if (pinentry->prompt)
312       {
313         /* Entry/Prompt Label */
314         obj = elm_label_add(table);
315         txt = pinentry_utf8_to_local (pinentry->lc_ctype, pinentry->prompt);
316         elm_object_text_set(obj,txt);
317         free (txt);
318         evas_object_size_hint_weight_set(obj, 0, EVAS_HINT_EXPAND);
319         evas_object_size_hint_align_set(obj, 1, EVAS_HINT_FILL);
320         elm_table_pack(table, obj, 1, row, 1, 1);
321         evas_object_show(obj);
322       }
323 
324       entry = elm_entry_add(table);
325       elm_entry_scrollable_set(entry, EINA_TRUE);
326       elm_scroller_policy_set(entry,
327                               ELM_SCROLLER_POLICY_OFF,
328                               ELM_SCROLLER_POLICY_OFF);
329       elm_entry_password_set(entry, EINA_TRUE);
330       elm_entry_single_line_set(entry, EINA_TRUE);
331       evas_object_size_hint_weight_set(entry, 0, 0);
332       evas_object_size_hint_align_set(entry, EVAS_HINT_FILL, 0);
333       elm_table_pack(table, entry, 2, row, 4, 1);
334       evas_object_smart_callback_add(entry,
335                                      "changed",
336                                      changed_text_handler,
337                                      NULL);
338       evas_object_show(entry);
339       row++;
340 
341       /* Check box */
342       obj = elm_check_add(table);
343       evas_object_size_hint_align_set(obj, 1, EVAS_HINT_FILL);
344       elm_table_pack(table, obj, 1, row, 1, 1);
345       evas_object_smart_callback_add(obj, "changed", on_check, NULL);
346       evas_object_show(obj);
347 
348       /* Check Label */
349       check_label = elm_label_add(table);
350       on_check((void *)NULL, obj, (void *)NULL);
351       elm_table_pack(table, check_label, 2, row, 4, 1);
352       evas_object_show(check_label);
353       row++;
354 
355       if (pinentry->quality_bar)
356 	{
357           /* Quality Bar Label */
358 	  obj = elm_label_add(table);
359           txt = pinentry_utf8_to_local (pinentry->lc_ctype,
360                                         pinentry->quality_bar);
361           elm_object_text_set(obj,txt);
362           free (txt);
363           evas_object_size_hint_weight_set(obj, 0, EVAS_HINT_EXPAND);
364           evas_object_size_hint_align_set(obj, 1, EVAS_HINT_FILL);
365           elm_table_pack(table, obj, 1, row, 1, 1);
366           evas_object_show(obj);
367 
368 	  qualitybar = elm_progressbar_add(table);
369           evas_object_color_set(qualitybar, 255, 0, 0, 255);
370           evas_object_show(qualitybar);
371           if (pinentry->quality_bar_tt)
372 	    elm_object_tooltip_text_set (qualitybar,
373 					 pinentry->quality_bar_tt);
374           evas_object_size_hint_weight_set(qualitybar, EVAS_HINT_EXPAND, 0);
375           evas_object_size_hint_align_set(qualitybar, EVAS_HINT_FILL, 0);
376           elm_table_pack(table, qualitybar, 2, row, 4, 1);
377           row++;
378 	}
379 
380       if (pinentry->repeat_passphrase)
381         {
382           /* Repeat Label */
383 	  obj = elm_label_add(table);
384           txt = pinentry_utf8_to_local (pinentry->lc_ctype,
385                                         pinentry->repeat_passphrase);
386           elm_object_text_set(obj,txt);
387           free (txt);
388           evas_object_size_hint_weight_set(obj, 0, EVAS_HINT_EXPAND);
389           evas_object_size_hint_align_set(obj, 1, EVAS_HINT_FILL);
390           elm_table_pack(table, obj, 1, row, 1, 1);
391           evas_object_show(obj);
392 
393           repeat_entry = elm_entry_add(table);
394           elm_entry_scrollable_set(repeat_entry, EINA_TRUE);
395           elm_scroller_policy_set(repeat_entry,
396                                   ELM_SCROLLER_POLICY_OFF,
397                                   ELM_SCROLLER_POLICY_OFF);
398           elm_entry_password_set(repeat_entry, EINA_TRUE);
399           elm_entry_single_line_set(repeat_entry, EINA_TRUE);
400           evas_object_size_hint_weight_set(repeat_entry, 0, 0);
401           evas_object_size_hint_align_set(repeat_entry, EVAS_HINT_FILL, 0);
402           elm_table_pack(table, repeat_entry, 2, row, 4, 1);
403 	  evas_object_smart_callback_add (repeat_entry, "activated",
404 					  enter_callback, NULL);
405           evas_object_show(repeat_entry);
406           evas_object_smart_callback_add (entry,
407                                           "activated",
408                                           enter_callback,
409                                           repeat_entry);
410           evas_object_smart_callback_add(repeat_entry,
411                                          "activated",
412                                          on_click,
413                                          (void *) CONFIRM_OK);
414           row++;
415         }
416       else
417         evas_object_smart_callback_add(entry,
418                                        "activated",
419                                        on_click,
420                                        (void *) CONFIRM_OK);
421   }
422 
423   /* Cancel Button */
424   if (!pinentry->one_button)
425     {
426       obj = elm_button_add(table);
427       icon = elm_icon_add (table);
428       evas_object_size_hint_aspect_set (icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
429       if (elm_icon_standard_set (icon, "dialog-cancel") ||
430           elm_icon_standard_set (icon, "window-close"))
431         {
432           evas_object_size_hint_min_set(icon,
433                                         ELM_SCALE_SIZE(BUTTON_ICON_SIZE),
434                                         ELM_SCALE_SIZE(BUTTON_ICON_SIZE));
435           elm_object_part_content_set(obj, "icon", icon);
436           evas_object_show (icon);
437         }
438       else
439         evas_object_del(icon);
440       if (pinentry->cancel || pinentry->default_cancel)
441         {
442           if(pinentry->cancel)
443             txt = pinentry_utf8_to_local (pinentry->lc_ctype, pinentry->cancel);
444           else
445             txt = pinentry_utf8_to_local (pinentry->lc_ctype,
446                                           pinentry->default_cancel);
447           if(txt[0]=='_')
448             elm_object_text_set(obj,txt+1);
449           else
450             elm_object_text_set(obj,txt);
451           btn_txt_len = ELM_SCALE_SIZE(strlen(txt) * (PADDING * 1.5));
452           free (txt);
453         }
454       else
455         elm_object_text_set(obj, "Cancel"); //STOCK_CANCEL
456       evas_object_size_hint_align_set(obj, 0, 0);
457       if(btn_txt_len>ELM_SCALE_SIZE(BUTTON_WIDTH))
458         evas_object_size_hint_min_set(obj,
459                                       btn_txt_len,
460                                       ELM_SCALE_SIZE(BUTTON_HEIGHT));
461       else
462         evas_object_size_hint_min_set(obj,
463                                       ELM_SCALE_SIZE(BUTTON_WIDTH),
464                                       ELM_SCALE_SIZE(BUTTON_HEIGHT));
465       elm_table_pack(table, obj, 4, row, 1, 1);
466       evas_object_smart_callback_add(obj,
467                                      "clicked",
468                                      on_click,
469                                      (void *) CONFIRM_CANCEL);
470       evas_object_show(obj);
471     }
472 
473   /* OK Button */
474   obj = elm_button_add(table);
475   icon = elm_icon_add (table);
476   evas_object_size_hint_aspect_set (icon, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
477   if (elm_icon_standard_set (icon, "dialog-ok") ||
478       elm_icon_standard_set (icon, "list-add"))
479     {
480       evas_object_size_hint_min_set(icon,
481                                     ELM_SCALE_SIZE(BUTTON_ICON_SIZE),
482                                     ELM_SCALE_SIZE(BUTTON_ICON_SIZE));
483       elm_object_part_content_set(obj, "icon", icon);
484       evas_object_show (icon);
485     }
486   else
487     evas_object_del(icon);
488   if (pinentry->ok || pinentry->default_ok)
489     {
490       if(pinentry->ok)
491         txt = pinentry_utf8_to_local (pinentry->lc_ctype, pinentry->ok);
492       else
493         txt = pinentry_utf8_to_local (pinentry->lc_ctype, pinentry->default_ok);
494       if(txt[0]=='_')
495         elm_object_text_set(obj,txt+1);
496       else
497         elm_object_text_set(obj,txt);
498       ok_len = ELM_SCALE_SIZE(strlen(txt) * (PADDING * 1.5));
499       if(ok_len>btn_txt_len)
500         btn_txt_len = ok_len;
501       free (txt);
502     }
503   else
504     elm_object_text_set(obj,"OK"); //STOCK_OK
505   evas_object_size_hint_align_set(obj, 0, 0);
506   if(btn_txt_len>ELM_SCALE_SIZE(BUTTON_WIDTH))
507     evas_object_size_hint_min_set(obj,
508                                   btn_txt_len,
509                                   ELM_SCALE_SIZE(BUTTON_HEIGHT));
510   else
511     evas_object_size_hint_min_set(obj,
512                                   ELM_SCALE_SIZE(BUTTON_WIDTH),
513                                   ELM_SCALE_SIZE(BUTTON_HEIGHT));
514   elm_table_pack(table, obj, 5, row, 1, 1);
515   evas_object_smart_callback_add(obj, "clicked", on_click, (void *) CONFIRM_OK);
516   evas_object_show(obj);
517 
518   /* Key/Lock Icon */
519   obj = elm_icon_add (win);
520   evas_object_size_hint_aspect_set (obj, EVAS_ASPECT_CONTROL_BOTH, 1, 1);
521   if (elm_icon_standard_set (obj, "dialog-password"))
522     {
523       double ic_size = WIDTH/5;
524       if(row==0)
525         ic_size = ic_size/3.5;
526       else if(row<4)
527         ic_size = ic_size - ic_size/row;
528       evas_object_size_hint_min_set(obj,
529                                     ELM_SCALE_SIZE(ic_size),
530                                     ELM_SCALE_SIZE(ic_size));
531       evas_object_size_hint_weight_set(obj, EVAS_HINT_EXPAND, EVAS_HINT_EXPAND);
532       evas_object_size_hint_align_set(obj, EVAS_HINT_FILL, 0.5);
533       elm_table_pack(table, obj, 0, 0, 1, row? row:1);
534       evas_object_show (obj);
535     }
536   else
537       evas_object_del(obj);
538 
539   /* Box for padding */
540   obj = elm_box_add (win);
541   elm_box_pack_end (obj, table);
542   evas_object_show (obj);
543 
544   elm_win_resize_object_add(win,obj);
545   evas_object_show(win);
546 
547   if(entry)
548     elm_object_focus_set (entry, EINA_TRUE);
549 
550   if (pinentry->timeout > 0)
551     timer = ecore_timer_add (pinentry->timeout,
552                              (Ecore_Task_Cb)timeout_cb,
553                              pinentry);
554 }
555 
556 static int
557 efl_cmd_handler (pinentry_t pe)
558 {
559   int want_pass = !!pe->pin;
560 
561   got_input = EINA_FALSE;
562   pinentry = pe;
563   confirm_value = CONFIRM_CANCEL;
564   passphrase_ok = 0;
565   confirm_mode = want_pass ? 0 : 1;
566   /* init ecore-x explicitly using DISPLAY since this can launch
567    * from console
568    */
569   if (pe->display)
570     ecore_x_init (pe->display);
571   elm_init (pargc, pargv);
572   create_window ();
573   ecore_main_loop_begin ();
574 
575   if (timer)
576     {
577       ecore_timer_del (timer);
578       timer = NULL;
579     }
580 
581   if (confirm_value == CONFIRM_CANCEL || grab_failed)
582     pe->canceled = 1;
583 
584   pinentry = NULL;
585   if (want_pass)
586     {
587       if (passphrase_ok && pe->pin)
588 	return strlen (pe->pin);
589       else
590 	return -1;
591     }
592   else
593     return (confirm_value == CONFIRM_OK) ? 1 : 0;
594 }
595 
596 int
597 main (int argc, char *argv[])
598 {
599   pinentry_init (PGMNAME);
600 
601 #ifdef FALLBACK_CURSES
602   if (pinentry_have_display (argc, argv))
603     {
604 #endif
605 
606   pinentry_cmd_handler = efl_cmd_handler;
607   pargc = argc;
608   pargv = argv;
609 
610 #ifdef FALLBACK_CURSES
611     }
612   else
613     {
614       pinentry_cmd_handler = curses_cmd_handler;
615     }
616 #endif
617 
618   pinentry_parse_opts (argc, argv);
619   if (pinentry_loop ())
620     return 1;
621 
622   return 0;
623 }
624