A prompt for ssh-agent(1) when your Yubikey needs poking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fork for each client connection

Using ssh with AddKeysToAgent enabled makes one ssh-agent connection
and keeps it open, then when the key passphrase is entered, it tries
connecting again to add it. Since we were blocking on one client
connection at a time, ssh would just hang in netio trying to
connect().

Each child process needs its own X11 connection so the unveil calls
in the main process have to go, but now each child process can
pledge to just "stdio ps".

+98 -69
+98 -69
ssh-agent-card-prompt.c
··· 101 101 unsigned char buf[4096]; 102 102 uid_t euid; 103 103 gid_t egid; 104 + pid_t pid; 104 105 int ch, sock, clientfd = -1; 105 106 106 107 prompt = strdup(DEFAULT_PROMPT); ··· 129 130 len = strlen(auth_sock) + 6; 130 131 if ((upstream_auth_sock = malloc(len)) == NULL) 131 132 err(1, "malloc"); 132 - 133 - /* do this early so we can pledge */ 134 - x11_init(); 135 133 136 134 /* move aside and let the man go through */ 137 135 snprintf(upstream_auth_sock, len, "%s.orig", auth_sock); ··· 164 162 } 165 163 166 164 #ifdef __OpenBSD__ 167 - if (unveil(auth_sock, "rwc") == -1) 168 - err(1, "unveil"); 169 - if (unveil(upstream_auth_sock, "rwc") == -1) 170 - err(1, "unveil"); 171 - if (pledge("stdio unix rpath cpath ps", NULL) == -1) 165 + if (pledge("stdio unix rpath cpath ps proc", NULL) == -1) 172 166 err(1, "pledge"); 173 167 #endif 174 168 169 + signal(SIGCHLD, SIG_IGN); 175 170 signal(SIGPIPE, SIG_IGN); 176 171 signal(SIGINT, rollback); 177 172 signal(SIGTERM, rollback); ··· 184 179 continue; 185 180 } 186 181 182 + pid = fork(); 183 + if (pid == -1) 184 + err(1, "fork"); 185 + else if (pid != 0) { 186 + /* parent */ 187 + close(clientfd); 188 + continue; 189 + } 190 + 187 191 if (getpeereid(clientfd, &euid, &egid) == -1) { 188 - warn("getpeereid"); 192 + warn("[%d] getpeereid", getpid()); 189 193 goto close; 190 194 } 191 195 192 196 if (euid != 0 && getuid() != euid) { 193 - warn("socket peer uid %u != uid %u", (u_int)euid, 194 - (u_int)getuid()); 197 + warn("[%d] socket peer uid %u != uid %u", getpid(), 198 + (u_int)euid, (u_int)getuid()); 195 199 goto close; 196 200 } 197 201 198 202 if (getsockopt(clientfd, SOL_SOCKET, SO_PEERCRED, &peercred, 199 203 &slen) == -1) { 200 - warn("getsockopt(SO_PEERCRED)"); 204 + warn("[%d] getsockopt(SO_PEERCRED)", getpid()); 201 205 goto close; 202 206 } 203 207 208 + DPRINTF(("[%d] got client connection from pid %d\n", getpid(), 209 + peercred.pid)); 210 + 204 211 if ((upstreamfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { 205 - warn("socket"); 212 + warn("[%d] socket", getpid()); 206 213 goto close; 207 214 } 208 215 ··· 210 217 sunaddr.sun_family = AF_UNIX; 211 218 if (strlcpy(sunaddr.sun_path, upstream_auth_sock, 212 219 sizeof(sunaddr.sun_path)) >= sizeof(sunaddr.sun_path)) { 213 - warn("strlcpy"); 220 + warn("[%d] strlcpy", getpid()); 214 221 goto close; 215 222 } 216 223 217 224 if (connect(upstreamfd, (struct sockaddr *)&sunaddr, 218 225 sizeof(sunaddr)) == -1) { 219 - warn("connect to upstream"); 226 + warn("[%d] connect to upstream", getpid()); 220 227 goto close; 221 228 } 222 229 223 - pkcs_key_len = 0; 230 + DPRINTF(("[%d] got upstream connection\n", getpid())); 224 231 225 - DPRINTF(("got client connection and upstream connection\n")); 232 + /* do this early so we can pledge */ 233 + x11_init(); 234 + 235 + if (pledge("stdio ps", NULL) == -1) 236 + err(1, "[%d] pledge", getpid()); 237 + 238 + pkcs_key_len = 0; 226 239 227 240 memset(&pfd, 0, sizeof(pfd)); 228 241 pfd[0].fd = clientfd; ··· 232 245 233 246 for (;;) { 234 247 if (poll(pfd, 2, INFTIM) == -1) { 235 - warn("poll failed"); 248 + warn("[%d] poll failed", getpid()); 236 249 goto close; 237 250 } 238 251 239 252 if ((pfd[0].revents & (POLLIN|POLLHUP))) { 240 253 /* client -> upstream */ 241 254 len = read(clientfd, buf, sizeof(buf)); 242 - DPRINTF(("got client data (%zu)\n", len)); 255 + DPRINTF(("[%d] got client data (%zu)\n", 256 + getpid(), len)); 243 257 if (len && (forward_agent_message(len, &buf, 244 258 upstreamfd, peercred.pid) == -1)) 245 259 goto close; ··· 250 264 if ((pfd[1].revents & (POLLIN|POLLHUP))) { 251 265 /* upstream -> client */ 252 266 len = read(upstreamfd, buf, sizeof(buf)); 253 - DPRINTF(("got upstream data (%zu)\n", len)); 267 + DPRINTF(("[%d] got upstream data (%zu)\n", 268 + getpid(), len)); 254 269 if (len && (forward_agent_message(len, &buf, 255 270 clientfd, peercred.pid) == -1)) 256 271 goto close; ··· 262 277 close: 263 278 explicit_bzero(&buf, sizeof(buf)); 264 279 explicit_bzero(&pkcs_key, sizeof(pkcs_key)); 280 + 281 + DPRINTF(("[%d] closing client and upstream connections\n", 282 + getpid())); 265 283 266 284 if (clientfd != -1) { 267 285 close(clientfd); ··· 271 289 close(upstreamfd); 272 290 upstreamfd = -1; 273 291 } 274 - } 275 292 276 - if (dpy) 277 - XCloseDisplay(dpy); 293 + if (dpy) 294 + XCloseDisplay(dpy); 278 295 279 - rollback(0); 296 + exit(0); 297 + } 280 298 299 + /* unreached */ 281 300 return 0; 282 301 } 283 302 ··· 290 309 int x; 291 310 292 311 if (debug >= 2) { 293 - DPRINTF(("forwarding data[%zu]:", len)); 312 + DPRINTF(("[%d] forwarding data[%zu]:", getpid(), len)); 294 313 for (x = 0; x < len; x++) 295 314 DPRINTF((" %02x", ((unsigned char *)buf)[x])); 296 315 DPRINTF(("\n")); 297 316 } 298 317 299 318 if (len < sizeof(struct agent_msg_hdr)) { 300 - warnx("short message (%zu < %zu)", len, 319 + warnx("[%d] short message (%zu < %zu)", getpid(), len, 301 320 sizeof(struct agent_msg_hdr)); 302 321 return -1; 303 322 } 304 323 305 324 if (len != (be32toh(hdr->len) + sizeof(uint32_t))) { 306 - warn("message invalid len (%zu != %d + %zu)", len, 307 - be32toh(hdr->len), sizeof(uint32_t)); 325 + warn("[%d] message invalid len (%zu != %d + %zu)", getpid(), 326 + len, be32toh(hdr->len), sizeof(uint32_t)); 308 327 return -1; 309 328 } 310 329 311 330 if (write(destfd, buf, len) != len) { 312 - warn("write to destfd failed"); 331 + warn("[%d] write to destfd failed", getpid()); 313 332 return -1; 314 333 } 315 334 ··· 332 351 switch (hdr->type) { 333 352 case SSH_AGENT_IDENTITIES_ANSWER: 334 353 if (len < sizeof(uint32_t)) { 335 - DPRINTF(("SSH_AGENT_IDENTITIES_ANSWER but remaining " 336 - "len too short\n")); 354 + DPRINTF(("[%d] SSH_AGENT_IDENTITIES_ANSWER but " 355 + "remaining len too short\n", getpid())); 337 356 break; 338 357 } 339 358 ··· 343 362 if (nkeys <= 0) 344 363 break; 345 364 346 - DPRINTF(("SSH_AGENT_IDENTITIES_ANSWER with %d key(s)\n", 347 - nkeys)); 365 + DPRINTF(("[%d] SSH_AGENT_IDENTITIES_ANSWER with %d key(s)\n", 366 + getpid(), nkeys)); 348 367 349 368 for (x = 0; x < nkeys && len > 0; x++) { 350 369 uint32_t kbloblen, kcommlen; ··· 352 371 353 372 /* key blob len */ 354 373 if (len < sizeof(uint32_t)) { 355 - warn("SSH_AGENT_IDENTITIES_ANSWER short (1)"); 374 + warn("[%d] SSH_AGENT_IDENTITIES_ANSWER short " 375 + "(1)", getpid()); 356 376 break; 357 377 } 358 378 kbloblen = be32bytes(buf); ··· 360 380 buf += sizeof(uint32_t); 361 381 362 382 if (kbloblen > len) { 363 - warn("SSH_AGENT_IDENTITIES_ANSWER short (2)"); 383 + warn("[%d] SSH_AGENT_IDENTITIES_ANSWER short " 384 + "(2)", getpid()); 364 385 break; 365 386 } 366 387 if (kbloblen <= 0) ··· 373 394 374 395 /* key comment len */ 375 396 if (len < sizeof(uint32_t)) { 376 - warn("SSH_AGENT_IDENTITIES_ANSWER short (3)"); 397 + warn("[%d] SSH_AGENT_IDENTITIES_ANSWER short " 398 + "(3)", getpid()); 377 399 break; 378 400 } 379 401 kcommlen = be32bytes(buf); ··· 381 403 buf += sizeof(uint32_t); 382 404 383 405 if (kcommlen > len) { 384 - warn("SSH_AGENT_IDENTITIES_ANSWER short (4)"); 406 + warn("[%d] SSH_AGENT_IDENTITIES_ANSWER short " 407 + "(4)", getpid()); 385 408 break; 386 409 } 387 410 if (kcommlen <= 0) ··· 390 413 /* key comment */ 391 414 kcomm = malloc(kcommlen + 2); 392 415 if (kcomm == NULL) { 393 - warn("malloc %d", kcommlen + 2); 416 + warn("[%d] malloc %d", getpid(), kcommlen + 2); 394 417 break; 395 418 } 396 419 strlcpy(kcomm, buf, kcommlen + 1); 397 - DPRINTF(("key[%d] = %s\n", x, kcomm)); 420 + DPRINTF(("[%d] key[%d] = %s\n", getpid(), x, kcomm)); 398 421 399 422 /* match on pkcs11 or pkcs15 */ 400 423 if (strstr(kcomm, "pkcs1")) { 401 - DPRINTF(("found pkcs1 key at %d\n", x)); 424 + DPRINTF(("[%d] found pkcs1 key at %d\n", 425 + getpid(), x)); 402 426 pkcs_key_len = kbloblen; 403 427 memcpy(pkcs_key, kblob, kbloblen); 404 428 } ··· 412 436 case SSH_AGENTC_SIGN_REQUEST: 413 437 /* key blob len */ 414 438 if (len < sizeof(uint32_t)) { 415 - DPRINTF(("SSH_AGENTC_SIGN_REQUEST but remaining " 416 - "len too short\n")); 439 + DPRINTF(("[%d] SSH_AGENTC_SIGN_REQUEST but remaining " 440 + "len too short\n", getpid())); 417 441 break; 418 442 } 419 443 klen = be32bytes(buf); ··· 421 445 buf += sizeof(uint32_t); 422 446 423 447 if (klen > len) { 424 - warn("SSH_AGENTC_SIGN_REQUEST short (1)"); 448 + warn("[%d] SSH_AGENTC_SIGN_REQUEST short (1)", 449 + getpid()); 425 450 break; 426 451 } 427 452 if (klen <= 0) ··· 429 454 430 455 if (klen > 0 && klen == pkcs_key_len && 431 456 memcmp(buf, pkcs_key, pkcs_key_len) == 0) { 432 - DPRINTF(("SSH_AGENTC_SIGN_REQUEST for our pkcs key\n")); 457 + DPRINTF(("[%d] SSH_AGENTC_SIGN_REQUEST for our pkcs " 458 + "key\n", getpid())); 433 459 if (x11_prompt(clientpid) == -1) 434 460 return -1; 435 461 } ··· 443 469 { 444 470 dpy = XOpenDisplay(NULL); 445 471 if (!dpy) 446 - errx(1, "XOpenDisplay failed"); 472 + errx(1, "[%d] XOpenDisplay failed", getpid()); 447 473 448 474 if (!XMatchVisualInfo(dpy, DefaultScreen(dpy), 32, TrueColor, &vinfo)) 449 - errx(1, "!XMatchVisualInfo failed"); 475 + errx(1, "[%d] !XMatchVisualInfo failed", getpid()); 450 476 451 477 colormap = XCreateColormap(dpy, DefaultRootWindow(dpy), vinfo.visual, 452 478 AllocNone); 453 479 454 480 font = XftFontOpenName(dpy, DefaultScreen(dpy), FONT); 455 481 if (font == NULL) 456 - errx(1, "failed opening font"); 482 + errx(1, "[%d] failed opening font", getpid()); 457 483 458 484 smallfont = XftFontOpenName(dpy, DefaultScreen(dpy), FONT_SMALL); 459 485 if (smallfont == NULL) 460 - errx(1, "failed opening small font"); 486 + errx(1, "[%d] failed opening small font", getpid()); 461 487 462 488 if (!XftColorAllocName(dpy, vinfo.visual, colormap, "white", &white)) 463 - errx(1, "failed allocating white"); 489 + errx(1, "[%d] failed allocating white", getpid()); 464 490 if (!XftColorAllocName(dpy, vinfo.visual, colormap, "#eeeeee", &gray)) 465 - errx(1, "failed allocating gray"); 491 + errx(1, "[%d] failed allocating gray", getpid()); 466 492 } 467 493 468 494 int ··· 491 517 492 518 draw = XftDrawCreate(dpy, win, vinfo.visual, colormap); 493 519 if (!draw) { 494 - warnx("can't draw with font"); 520 + warnx("[%d] can't draw with font", getpid()); 495 521 ret = -1; 496 522 goto done_x; 497 523 } 498 524 499 - /* and now if we can't grab the keyboard, pinentry probably has it */ 525 + /* if we can't grab the keyboard, maybe another child has it */ 500 526 for (x = 0; x < 30; x++) { 501 527 grab = XGrabKeyboard(dpy, DefaultRootWindow(dpy), True, 502 528 GrabModeAsync, GrabModeAsync, CurrentTime); 503 529 if (grab == GrabSuccess) 504 530 break; 505 531 506 - warn("couldn't grab keyboard"); 532 + warn("[%d] couldn't grab keyboard", getpid()); 507 533 sleep(1); 508 534 } 509 535 if (grab != GrabSuccess) { 510 - warn("couldn't grab keyboard, giving up"); 536 + warn("[%d] couldn't grab keyboard, giving up", getpid()); 511 537 ret = -1; 512 538 goto done_x; 513 539 } ··· 526 552 if (procname(clientpid, &clientproc) == -1) { 527 553 clientproc = strdup("(failed finding process info)"); 528 554 if (clientproc == NULL) 529 - err(1, "malloc"); 555 + err(1, "[%d] malloc", getpid()); 530 556 } 531 557 line = malloc(strlen(clientproc) + 20); 532 558 if (line == NULL) 533 - err(1, "malloc"); 559 + err(1, "[%d] malloc", getpid()); 534 560 snprintf(line, strlen(clientproc) + 20, "PID %d: %s", clientpid, 535 561 clientproc); 536 562 clientproc = line; ··· 538 564 /* process info may be long, so wrap it to multiple lines */ 539 565 line = strdup(""); 540 566 if (line == NULL) 541 - err(1, "strdup"); 567 + err(1, "[%d] strdup", getpid()); 542 568 for ((word = strsep(&clientproc, " ")); word && *word != '\0'; 543 569 (word = strsep(&clientproc, " "))) { 544 570 char *oldline = strdup(line); 545 571 if (oldline == NULL) 546 - err(1, "strdup"); 572 + err(1, "[%d] strdup", getpid()); 547 573 548 574 len = strlen(line) + 1 + strlen(word) + 1; 549 575 line = realloc(line, len); ··· 597 623 598 624 if ((pfd[0].revents & (POLLIN|POLLHUP))) { 599 625 XNextEvent(dpy, &ev); 600 - DPRINTF(("got X11 event of type %d\n", ev.type)); 626 + DPRINTF(("[%d] got X11 event of type %d\n", ev.type, 627 + getpid())); 601 628 if (ev.type == KeyPress) { 602 629 if (XLookupKeysym(&ev.xkey, 0) == XK_Escape) { 603 - DPRINTF(("escape pressed\n")); 630 + DPRINTF(("[%d] escape pressed\n", 631 + getpid())); 604 632 ret = -1; 605 633 break; 606 634 } else { 607 635 DPRINTF(("key pressed, not escape\n")); 608 636 } 609 637 } else { 610 - DPRINTF(("got other X11 event, re-raising\n")); 638 + DPRINTF(("[%d] got other X11 event, " 639 + "re-raising\n", getpid())); 611 640 XRaiseWindow(dpy, win); 612 641 } 613 642 } ··· 617 646 * gpg-agent is sending back data from 618 647 * SSH_AGENTC_SIGN_REQUEST, the key was touched 619 648 */ 620 - DPRINTF(("got data from upstream, key must have been " 621 - "touched\n")); 649 + DPRINTF(("[%d] got data from upstream, key must have " 650 + "been touched\n", getpid())); 622 651 ret = 0; 623 652 break; 624 653 } ··· 643 672 644 673 buf = malloc(buflen); 645 674 if (buf == NULL) 646 - err(1, "malloc"); 675 + err(1, "[%d] malloc", getpid()); 647 676 648 677 /* fetch process args */ 649 678 mib[0] = CTL_KERN; ··· 659 688 } 660 689 661 690 if ((buf = realloc(buf, buflen + 128)) == NULL) 662 - err(1, "realloc"); 691 + err(1, "[%d] realloc", getpid()); 663 692 buflen += 128; 664 693 } 665 694 ··· 671 700 672 701 *outbuf = malloc(1); 673 702 if (*outbuf == NULL) 674 - err(1, "malloc"); 703 + err(1, "[%d] malloc", getpid()); 675 704 676 705 *outbuf[0] = '\0'; 677 706 buflen = 1; ··· 681 710 buflen += strlen(*args) + 1; 682 711 *outbuf = realloc(*outbuf, buflen); 683 712 if (*outbuf == NULL) 684 - err(1, "realloc"); 713 + err(1, "[%d] realloc", getpid()); 685 714 if (*outbuf[0] != '\0') 686 715 strlcat(*outbuf, " ", buflen); 687 716 strlcat(*outbuf, *args, buflen); 688 717 args++; 689 718 } 690 719 691 - DPRINTF(("PID %d: %s\n", pid, *outbuf)); 720 + DPRINTF(("[%d] PID %d: %s\n", getpid(), pid, *outbuf)); 692 721 693 722 free(buf); 694 723 return 0; 695 724 #else 696 - warn("procname not implemented"); 725 + warn("[%d] procname not implemented", getpid()); 697 726 return -1; 698 727 #endif 699 728 }