pinentry-dmenu.c (15527B)
1 /* See LICENSE file for copyright and license details. */ 2 #include <ctype.h> 3 #include <locale.h> 4 #include <signal.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <strings.h> 9 #include <pwd.h> 10 #include <time.h> 11 #include <unistd.h> 12 13 #include <X11/Xlib.h> 14 #include <X11/Xatom.h> 15 #include <X11/Xutil.h> 16 #ifdef XINERAMA 17 #include <X11/extensions/Xinerama.h> 18 #endif 19 #include <X11/Xft/Xft.h> 20 21 #include "drw.h" 22 #include "util.h" 23 24 #include "pinentry/pinentry.h" 25 #include "pinentry/memory.h" 26 27 #define INTERSECT(x,y,w,h,r) (MAX(0, MIN((x)+(w),(r).x_org+(r).width) - MAX((x),(r).x_org)) \ 28 * MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org))) 29 #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) 30 #define MINDESCLEN 8 31 32 /* enums */ 33 enum { SchemePrompt, SchemeNormal, SchemeSelect, SchemeDesc, SchemeLast }; /* color schemes */ 34 enum { WinPin, WinConfirm }; 35 enum { Nothing, Yes, No }; 36 37 /* FIXME: this can't currently be set */ 38 static char *embed; 39 static int bh, mw, mh; 40 static int sel; 41 static int promptw, pdescw; 42 static int lrpad; /* sum of left and right padding */ 43 static size_t cursor; 44 static int mon = -1, screen; 45 46 static char *pin; 47 static int pin_len; 48 static char *pin_repeat; 49 static int pin_repeat_len; 50 static int repeat; 51 52 static Atom clip, utf8; 53 static Display *dpy; 54 static Window root, parentwin, win; 55 static XIC xic; 56 57 static Drw *drw; 58 static Clr *scheme[SchemeLast]; 59 60 static int timed_out; 61 static int winmode; 62 pinentry_t pinentry_info; 63 64 #include "config.h" 65 66 static int 67 drawitem(const char* text, Bool sel, int x, int y, int w) 68 { 69 unsigned int i = (sel) ? SchemeSelect : SchemeNormal; 70 71 drw_setscheme(drw, scheme[i]); 72 73 return drw_text(drw, x, y, w, bh, lrpad / 2, text, 0); 74 } 75 76 static void 77 grabfocus(void) 78 { 79 struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; 80 Window focuswin; 81 int i, revertwin; 82 83 for (i = 0; i < 100; ++i) { 84 XGetInputFocus(dpy, &focuswin, &revertwin); 85 if (focuswin == win) 86 return; 87 XSetInputFocus(dpy, win, RevertToParent, CurrentTime); 88 nanosleep(&ts, NULL); 89 } 90 die("cannot grab focus"); 91 } 92 93 static void 94 grabkeyboard(void) 95 { 96 struct timespec ts = { .tv_sec = 0, .tv_nsec = 1000000 }; 97 int i; 98 99 if (embed) 100 return; 101 /* try to grab keyboard, we may have to wait for another process to ungrab */ 102 for (i = 0; i < 1000; i++) { 103 if (XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, GrabModeAsync, 104 GrabModeAsync, CurrentTime) == GrabSuccess) 105 return; 106 nanosleep(&ts, NULL); 107 } 108 die("cannot grab keyboard"); 109 } 110 111 static size_t 112 nextrune(int inc) 113 { 114 ssize_t n; 115 116 /* return location of next utf8 rune in the given direction (+1 or -1) */ 117 for (n = cursor + inc; n + inc >= 0 && (pin[n] & 0xc0) == 0x80; n += inc) 118 ; 119 return n; 120 } 121 122 static void 123 setup_pin(char *pin_ptr, int len, int reset) 124 { 125 pin = pin_ptr; 126 pin_len = len; 127 128 if (reset) { 129 promptw = (prompt) ? TEXTW(prompt) - lrpad / 4 : 0; 130 cursor = 0; 131 132 if (pin) 133 pin[0] = '\0'; 134 } 135 } 136 137 static void 138 insert(const char *str, ssize_t n) 139 { 140 size_t len = strlen(pin); 141 142 // FIXME: Pinentry crashes when increasing the pin buffer the second time. 143 // Other pinentry programs has a limited passwort field length. 144 if (len + n > pin_len - 1) { 145 if (repeat) { 146 pin_repeat_len = 2 * pin_repeat_len; 147 pin_repeat = secmem_realloc(pin_repeat, pin_repeat_len); 148 setup_pin(pin_repeat, pin_repeat_len, 0); 149 if (!pin_repeat) 150 pin_len = 0; 151 } else { 152 if (!pinentry_setbufferlen(pinentry_info, 2 * pinentry_info->pin_len)) 153 pin_len = 0; 154 else 155 setup_pin(pinentry_info->pin, pinentry_info->pin_len, 0); 156 } 157 if (pin_len == 0) { 158 printf("Error: Couldn't allocate secure memory\n"); 159 return; 160 } 161 } 162 163 /* move existing text out of the way, insert new text, and update cursor */ 164 memmove(&pin[cursor + n], &pin[cursor], pin_len - cursor - MAX(n, 0)); 165 166 if (n > 0) 167 memcpy(&pin[cursor], str, n); 168 169 cursor += n; 170 pin[len + n] = '\0'; 171 } 172 173 static void 174 drawmenu(void) 175 { 176 unsigned int curpos; 177 int x = 0, fh = drw->fonts->h, pb, pbw = 0, i; 178 size_t asterlen = strlen(asterisk); 179 size_t pdesclen; 180 int leftinput; 181 char *censort; 182 183 char *pprompt = (repeat) ? pinentry_info->repeat_passphrase : pinentry_info->prompt; 184 int ppromptw = (pprompt) ? TEXTW(pprompt) : 0; 185 186 unsigned int censortl = minpwlen * TEXTW(asterisk) / strlen(asterisk); 187 unsigned int confirml = TEXTW(" YesNo ") + 3 * lrpad; 188 189 drw_setscheme(drw, scheme[SchemeNormal]); 190 drw_rect(drw, 0, 0, mw, mh, 1, 1); 191 192 if (prompt) { 193 drw_setscheme(drw, scheme[SchemePrompt]); 194 x = drw_text(drw, x, 0, promptw, bh, lrpad / 2, prompt, 0); 195 } 196 197 if (pprompt) { 198 drw_setscheme(drw, scheme[SchemePrompt]); 199 drw_text(drw, x, 0, ppromptw, bh, lrpad / 2, pprompt, 0); 200 x += ppromptw; 201 } 202 203 if (pinentry_info->description) { 204 pb = mw - x; 205 pdesclen = strlen(pinentry_info->description); 206 207 if (pb > 0) { 208 pb -= (winmode == WinPin) ? censortl : confirml; 209 pbw = MINDESCLEN * pdescw / pdesclen; 210 pbw = MIN(pbw, pdescw); 211 212 if (pb >= pbw) { 213 pbw = MAX(pbw, pdescw); 214 pbw = MIN(pbw, pb); 215 pb = mw - pbw; 216 217 for (i = 0; i < pdesclen; i++) 218 if (pinentry_info->description[i] == '\n') 219 pinentry_info->description[i] = ' '; 220 221 drw_setscheme(drw, scheme[SchemeDesc]); 222 drw_text(drw, pb, 0, pbw, bh, lrpad / 2, pinentry_info->description, 223 0); 224 } else { 225 pbw = 0; 226 } 227 } 228 } 229 230 /* Draw input field */ 231 drw_setscheme(drw, scheme[SchemeNormal]); 232 233 if (winmode == WinPin) { 234 censort = ecalloc(1, asterlen * pin_len); 235 236 for (i = 0; i < asterlen * strlen(pin); i += asterlen) 237 memcpy(&censort[i], asterisk, asterlen); 238 239 censort[i+1] = '\n'; 240 leftinput = mw - x - pbw; 241 drw_text(drw, x, 0, leftinput, bh, lrpad / 2, censort, 0); 242 curpos = TEXTW(censort) - TEXTW(&censort[cursor]); 243 244 if ((curpos += lrpad / 2 - 1) < leftinput) { 245 drw_setscheme(drw, scheme[SchemeNormal]); 246 drw_rect(drw, x + curpos, 2 + (bh - fh) / 2, 2, fh - 4, 1, 0); 247 } 248 249 free(censort); 250 } else { 251 x += TEXTW(" "); 252 x = drawitem("No", (sel == No), x, 0, TEXTW("No")); 253 x = drawitem("Yes", (sel == Yes), x, 0, TEXTW("Yes")); 254 } 255 drw_map(drw, win, 0, 0, mw, mh); 256 } 257 258 static void 259 setup(void) 260 { 261 int x, y, i, j; 262 unsigned int du; 263 XSetWindowAttributes swa; 264 XIM xim; 265 Window w, dw, *dws; 266 XWindowAttributes wa; 267 XClassHint ch = {"pinentry-dmenu", "pinentry-dmenu"}; 268 #ifdef XINERAMA 269 XineramaScreenInfo *info; 270 Window pw; 271 int a, di, n, area = 0; 272 #endif 273 /* init appearance */ 274 for (j = 0; j < SchemeLast; j++) 275 scheme[j] = drw_scm_create(drw, colors[j], 2); 276 277 clip = XInternAtom(dpy, "CLIPBOARD", False); 278 utf8 = XInternAtom(dpy, "UTF8_STRING", False); 279 280 /* Calculate menu geometry */ 281 bh = drw->fonts->h + 2; 282 mh = bh; 283 #ifdef XINERAMA 284 i = 0; 285 if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) { 286 XGetInputFocus(dpy, &w, &di); 287 if (mon >= 0 && mon < n) 288 i = mon; 289 else if (w != root && w != PointerRoot && w != None) { 290 /* find top-level window containing current input focus */ 291 do { 292 if (XQueryTree(dpy, (pw = w), &dw, &w, &dws, &du) && dws) 293 XFree(dws); 294 } while (w != root && w != pw); 295 /* find xinerama screen with which the window intersects most */ 296 if (XGetWindowAttributes(dpy, pw, &wa)) 297 for (j = 0; j < n; j++) 298 if ((a = INTERSECT(wa.x, wa.y, wa.width, wa.height, info[j])) > area) { 299 area = a; 300 i = j; 301 } 302 } 303 /* no focused window is on screen, so use pointer location instead */ 304 if (mon < 0 && !area && XQueryPointer(dpy, root, &dw, &dw, &x, &y, &di, &di, &du)) 305 for (i = 0; i < n; i++) 306 if (INTERSECT(x, y, 1, 1, info[i]) != 0) 307 break; 308 309 x = info[i].x_org; 310 y = info[i].y_org + (topbar ? 0 : info[i].height - mh); 311 mw = info[i].width; 312 XFree(info); 313 } else 314 #endif 315 { 316 if (!XGetWindowAttributes(dpy, parentwin, &wa)) 317 die("could not get embedding window attributes: 0x%lx", 318 parentwin); 319 x = 0; 320 y = topbar ? 0 : wa.height - mh; 321 mw = wa.width; 322 } 323 324 pdescw = (pinentry_info->description) ? TEXTW(pinentry_info->description) : 0; 325 326 /* Create menu window */ 327 swa.override_redirect = True; 328 swa.background_pixel = scheme[SchemePrompt][ColBg].pixel; 329 swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask; 330 win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0, 331 CopyFromParent, CopyFromParent, CopyFromParent, 332 CWOverrideRedirect | CWBackPixel | CWEventMask, &swa); 333 XSetClassHint(dpy, win, &ch); 334 335 /* input methods */ 336 if ((xim = XOpenIM(dpy, NULL, NULL, NULL)) == NULL) 337 die("XOpenIM failed: could not open input device"); 338 339 xic = XCreateIC(xim, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, 340 XNClientWindow, win, XNFocusWindow, win, NULL); 341 XMapRaised(dpy, win); 342 if (embed) { 343 XSelectInput(dpy, parentwin, FocusChangeMask | SubstructureNotifyMask); 344 345 if (XQueryTree(dpy, parentwin, &dw, &w, &dws, &du) && dws) { 346 for (i = 0; i < du && dws[i] != win; ++i) 347 XSelectInput(dpy, dws[i], FocusChangeMask); 348 XFree(dws); 349 } 350 grabfocus(); 351 } 352 drw_resize(drw, mw, mh); 353 } 354 355 static void 356 cleanup(void) 357 { 358 size_t i; 359 360 XUngrabKey(dpy, AnyKey, AnyModifier, root); 361 for (i = 0; i < SchemeLast; i++) 362 free(scheme[i]); 363 drw_free(drw); 364 XSync(dpy, False); 365 XCloseDisplay(dpy); 366 } 367 368 static int 369 keypress_confirm(XKeyEvent *ev, KeySym ksym) 370 { 371 if (ev->state & ControlMask) { 372 switch(ksym) { 373 case XK_c: 374 pinentry_info->canceled = 1; 375 sel = No; 376 return 1; 377 default: 378 return 1; 379 } 380 } 381 382 switch(ksym) { 383 case XK_KP_Enter: 384 case XK_Return: 385 if (sel != Nothing) 386 return 1; 387 break; 388 case XK_y: 389 case XK_Y: 390 sel = Yes; 391 return 1; 392 case XK_n: 393 case XK_N: 394 sel = No; 395 return 1; 396 case XK_g: 397 case XK_G: 398 case XK_Escape: 399 pinentry_info->canceled = 1; 400 sel = No; 401 return 1; 402 case XK_h: 403 case XK_j: 404 case XK_Home: 405 case XK_KP_Home: 406 case XK_Left: 407 case XK_KP_Left: 408 case XK_Prior: 409 case XK_KP_Prior: 410 case XK_Up: 411 case XK_KP_Up: 412 sel = No; 413 break; 414 case XK_k: 415 case XK_l: 416 case XK_Down: 417 case XK_KP_Down: 418 case XK_End: 419 case XK_Next: 420 case XK_Right: 421 case XK_KP_Right: 422 sel = Yes; 423 break; 424 } 425 426 return 0; 427 } 428 429 static int 430 keypress_pin(XKeyEvent *ev, KeySym ksym, char* buf, int len) 431 { 432 int old; 433 434 if (ev->state & ControlMask) { 435 switch(ksym) { 436 case XK_a: ksym = XK_Home; break; 437 case XK_b: ksym = XK_Left; break; 438 case XK_c: ksym = XK_Escape; break; 439 case XK_d: ksym = XK_Delete; break; 440 case XK_e: ksym = XK_End; break; 441 case XK_f: ksym = XK_Right; break; 442 case XK_g: ksym = XK_Escape; break; 443 case XK_h: ksym = XK_BackSpace; break; 444 case XK_k: 445 old = cursor; 446 cursor = strlen(pin); 447 insert(NULL, old - cursor); 448 break; 449 case XK_u: 450 insert(NULL, -cursor); 451 break; 452 case XK_v: 453 XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY, 454 utf8, utf8, win, CurrentTime); 455 return 0; 456 case XK_Return: 457 case XK_KP_Enter: 458 break; 459 case XK_bracketleft: 460 pinentry_info->canceled = 1; 461 return 1; 462 default: 463 return 1; 464 } 465 } 466 467 switch(ksym) { 468 case XK_Delete: 469 case XK_KP_Delete: 470 if (pin[cursor] == '\0') 471 return 0; 472 cursor = nextrune(+1); 473 /* Fallthrough */ 474 case XK_BackSpace: 475 if (cursor == 0) 476 return 0; 477 insert(NULL, nextrune(-1) - cursor); 478 break; 479 case XK_Escape: 480 pinentry_info->canceled = 1; 481 return 1; 482 case XK_Left: 483 case XK_KP_Left: 484 if (cursor > 0) 485 cursor = nextrune(-1); 486 break; 487 case XK_Right: 488 case XK_KP_Right: 489 if (pin[cursor] != '\0') 490 cursor = nextrune(+1); 491 break; 492 case XK_Home: 493 case XK_KP_Home: 494 cursor = 0; 495 break; 496 case XK_End: 497 case XK_KP_End: 498 cursor = strlen(pin); 499 break; 500 case XK_Return: 501 case XK_KP_Enter: 502 return 1; 503 break; 504 default: 505 if (!iscntrl((unsigned char)*buf)) 506 insert(buf, len); 507 } 508 509 return 0; 510 } 511 512 static int 513 keypress(XKeyEvent *ev) 514 { 515 char buf[32]; 516 int len; 517 int ret = 1; 518 519 KeySym ksym = NoSymbol; 520 Status status; 521 len = XmbLookupString(xic, ev, buf, sizeof(buf), &ksym, &status); 522 523 if (status != XBufferOverflow) { 524 if (winmode == WinConfirm) 525 ret = keypress_confirm(ev, ksym); 526 else 527 ret = keypress_pin(ev, ksym, buf, len); 528 529 if (ret == 0) 530 drawmenu(); 531 } 532 533 return ret; 534 } 535 536 static void 537 paste(void) 538 { 539 char *p, *q; 540 int di; 541 unsigned long dl; 542 Atom da; 543 544 /* we have been given the current selection, now insert it into input */ 545 if (XGetWindowProperty(dpy, win, utf8, 0, (pin_len/ 4) + 1, False, 546 utf8, &da, &di, &dl, &dl, (unsigned char **)&p) 547 == Success && p) { 548 insert(p, (q = strchr(p, '\n')) ? q - p : (ssize_t)strlen(p)); 549 XFree(p); 550 } 551 drawmenu(); 552 } 553 554 void 555 run(void) 556 { 557 XEvent ev; 558 559 drawmenu(); 560 561 while (!XNextEvent(dpy, &ev)) { 562 if (XFilterEvent(&ev, win)) 563 continue; 564 switch(ev.type) { 565 case DestroyNotify: 566 if (ev.xdestroywindow.window != win) 567 break; 568 cleanup(); 569 exit(1); 570 case Expose: 571 if (ev.xexpose.count == 0) 572 drw_map(drw, win, 0, 0, mw, mh); 573 break; 574 case KeyPress: 575 if (keypress(&ev.xkey)) 576 return; 577 break; 578 case SelectionNotify: 579 if (ev.xselection.property == utf8) 580 paste(); 581 break; 582 case VisibilityNotify: 583 if (ev.xvisibility.state != VisibilityUnobscured) 584 XRaiseWindow(dpy, win); 585 break; 586 } 587 } 588 } 589 590 /* FIXME: this does nothing */ 591 static void 592 catchsig(int sig) 593 { 594 if (sig == SIGALRM) 595 timed_out = 1; 596 } 597 598 static void 599 password(void) 600 { 601 winmode = WinPin; 602 repeat = 0; 603 setup_pin(pinentry_info->pin, pinentry_info->pin_len, 1); 604 run(); 605 606 if (!pinentry_info->canceled && pinentry_info->repeat_passphrase) { 607 repeat = 1; 608 pin_repeat_len = pinentry_info->pin_len; 609 pin_repeat = secmem_malloc(pinentry_info->pin_len); 610 setup_pin(pin_repeat, pin_repeat_len, 1); 611 run(); 612 613 pinentry_info->repeat_okay = (strcmp(pinentry_info->pin, pin_repeat) == 0)? 1 : 0; 614 secmem_free(pin_repeat); 615 616 if (!pinentry_info->repeat_okay) { 617 pinentry_info->result = -1; 618 return; 619 } 620 } 621 622 if (pinentry_info->canceled) { 623 pinentry_info->result = -1; 624 return; 625 } 626 627 pinentry_info->result = strlen(pinentry_info->pin); 628 } 629 630 static void 631 confirm(void) 632 { 633 winmode = WinConfirm; 634 sel = Nothing; 635 run(); 636 pinentry_info->result = sel != No; 637 } 638 639 static int 640 cmdhandler(pinentry_t received_pinentry) 641 { 642 struct sigaction sa; 643 XWindowAttributes wa; 644 645 pinentry_info = received_pinentry; 646 647 if (!setlocale(LC_CTYPE, "") || !XSupportsLocale()) 648 fputs("warning: no locale support\n", stderr); 649 if (!(dpy = XOpenDisplay(pinentry_info->display))) 650 die("cannot open display"); 651 screen = DefaultScreen(dpy); 652 root = RootWindow(dpy, screen); 653 embed = (pinentry_info->parent_wid) ? embed : 0; 654 parentwin = (embed) ? pinentry_info->parent_wid : root; 655 if (!XGetWindowAttributes(dpy, parentwin, &wa)) 656 die("could not get embedding window attributes: 0x%lx", parentwin); 657 drw = drw_create(dpy, screen, root, wa.width, wa.height); 658 if (!drw_fontset_create(drw, fonts, LENGTH(fonts))) 659 die("no fonts could be loaded."); 660 lrpad = drw->fonts->h; 661 662 #ifdef __OpenBSD__ 663 if (pledge("stdio rpath", NULL) == -1) 664 die("pledge"); 665 #endif 666 667 drw_setscheme(drw, scheme[SchemePrompt]); 668 669 if (pinentry_info->timeout) { 670 memset(&sa, 0, sizeof(sa)); 671 sa.sa_handler = catchsig; 672 sigaction(SIGALRM, &sa, NULL); 673 alarm(pinentry_info->timeout); 674 } 675 676 grabkeyboard(); 677 setup(); 678 679 if (pinentry_info->pin) 680 do { 681 password(); 682 } while (!pinentry_info->canceled && pinentry_info->repeat_passphrase 683 && !pinentry_info->repeat_okay); 684 else 685 confirm(); 686 687 cleanup(); 688 689 return pinentry_info->result; 690 } 691 692 pinentry_cmd_handler_t pinentry_cmd_handler = cmdhandler; 693 694 int 695 main(int argc, char *argv[]) 696 { 697 pinentry_init("pinentry-dmenu"); 698 pinentry_parse_opts(argc, argv); 699 700 if (pinentry_loop()) 701 return 1; 702 703 return 0; 704 }