1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069
|
/*
* Copyright (C) 1997, 2001-2003, 2007, 2008 Red Hat, Inc.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include "config.h"
#include <assert.h>
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <libintl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#ifdef USE_STARTUP_NOTIFICATION
#include <libsn/sn.h>
#endif
#include "userdialogs.h"
#include "userhelper.h"
#include "userhelper-messages.h"
#include "userhelper-wrap.h"
#ifdef DEBUG_USERHELPER
#define debug_msg(...) fprintf(stderr, __VA_ARGS__)
#else
#define debug_msg(...) ((void)0)
#endif
#define PAD 8
#define RESPONSE_FALLBACK 100
static int childin[2];
static int childpid;
static int childout_tag;
static int child_exit_status;
static gboolean child_success_dialog = TRUE;
static gboolean child_was_execed = FALSE;
struct message {
int type;
GtkWidget *entry;
GtkWidget *label;
};
struct response {
int responses, rows;
gboolean fallback_allowed;
char *service, *suggestion, *banner, *title;
GList *message_list; /* contains pointers to messages */
void *dialog; /* Actually a GtkWidget * */
GtkWidget *user_label, *first, *last, *table;
gulong dialog_response_handler;
};
static char *sn_id = NULL;
static char *sn_name = NULL;
static char *sn_description = NULL;
static int sn_workspace = -1;
static char *sn_wmclass = NULL;
static char *sn_binary_name = NULL;
static char *sn_icon_name = NULL;
#ifdef USE_STARTUP_NOTIFICATION
/* Push errors for the specified display. */
static void
trap_push(SnDisplay *display, Display *xdisplay)
{
(void)xdisplay;
sn_display_error_trap_push(display);
gdk_error_trap_push();
}
/* Pop errors for the specified display. */
static void
trap_pop(SnDisplay *display, Display *xdisplay)
{
(void)xdisplay;
gdk_error_trap_pop();
sn_display_error_trap_pop(display);
}
#endif
/* Complete startup notification for consolehelper. */
static void
userhelper_startup_notification_launchee(const char *id)
{
(void)id;
#ifdef USE_STARTUP_NOTIFICATION
GdkDisplay *gdisp;
GdkScreen *gscreen;
SnDisplay *disp;
SnLauncheeContext *ctx;
int screen;
gdisp = gdk_display_get_default();
gscreen = gdk_display_get_default_screen(gdisp);
disp = sn_display_new(GDK_DISPLAY(), trap_push, trap_pop);
screen = gdk_screen_get_number(gscreen);
if (id == NULL) {
ctx = sn_launchee_context_new_from_environment(disp, screen);
} else {
ctx = sn_launchee_context_new(disp, screen, id);
}
if (ctx != NULL) {
debug_msg("Completing startup notification for \"%s\".\n",
sn_launchee_context_get_startup_id(ctx) ?
sn_launchee_context_get_startup_id(ctx) : "?");
sn_launchee_context_complete(ctx);
sn_launchee_context_unref(ctx);
}
sn_display_unref(disp);
#endif
}
/* Setup startup notification for our child. */
static void
userhelper_startup_notification_launcher(void)
{
#ifdef USE_STARTUP_NOTIFICATION
GdkDisplay *gdisp;
GdkScreen *gscreen;
SnDisplay *disp;
SnLauncherContext *ctx;
int screen;
if (sn_name == NULL) {
return;
}
gdisp = gdk_display_get_default();
gscreen = gdk_display_get_default_screen(gdisp);
disp = sn_display_new(GDK_DISPLAY(), trap_push, trap_pop);
screen = gdk_screen_get_number(gscreen);
ctx = sn_launcher_context_new(disp, screen);
if (sn_name) {
sn_launcher_context_set_name(ctx, sn_name);
}
if (sn_description) {
sn_launcher_context_set_description(ctx, sn_description);
}
if (sn_workspace != -1) {
sn_launcher_context_set_workspace(ctx, sn_workspace);
}
if (sn_wmclass) {
sn_launcher_context_set_wmclass(ctx, sn_wmclass);
}
if (sn_binary_name) {
sn_launcher_context_set_binary_name(ctx, sn_binary_name);
}
if (sn_icon_name) {
sn_launcher_context_set_binary_name(ctx, sn_icon_name);
}
debug_msg("Starting launch of \"%s\", id=\"%s\".\n",
sn_description ? sn_description : sn_name, sn_id);
sn_launcher_context_initiate(ctx, "userhelper", sn_name, CurrentTime);
if (sn_launcher_context_get_startup_id(ctx) != NULL) {
sn_id = g_strdup(sn_launcher_context_get_startup_id(ctx));
}
sn_launcher_context_unref(ctx);
sn_display_unref(disp);
#endif
}
/* Call gtk_main_quit. */
void
userhelper_main_quit(void)
{
debug_msg("Quitting main loop %d.\n", gtk_main_level());
gtk_main_quit();
}
static void
userhelper_fatal_error(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
(void)widget;
(void)event;
(void)user_data;
userhelper_main_quit();
}
/* Display a dialog explaining a child's exit status, and exit ourselves. */
static void
userhelper_parse_exitstatus(int exitstatus)
{
const char *message;
enum uh_message_type type;
GtkWidget *message_box;
if (child_was_execed)
debug_msg("Wrapped application returned exit status %d.\n",
exitstatus);
else
debug_msg("Child returned exit status %d.\n", exitstatus);
/* If the exit status came from what the child execed, then we don't
* care about reporting it to the user. */
if (child_was_execed) {
child_exit_status = exitstatus;
return;
}
/* Create a dialog suitable for displaying this code. */
uh_exitstatus_message(exitstatus, &message, &type);
debug_msg("Status is \"%s\".\n", message);
/* If we recognize this code, create the error dialog for it if we
need to display one. */
switch (type) {
case UHM_MESSAGE:
message_box = create_message_box(message, NULL);
break;
case UHM_ERROR:
message_box = create_error_box(message, NULL);
break;
case UHM_SILENT:
message_box = NULL;
break;
default:
g_assert_not_reached();
}
/* Run the dialog box. */
if (message_box != NULL) {
if (child_success_dialog || (exitstatus != 0)) {
gtk_dialog_run(GTK_DIALOG(message_box));
}
gtk_widget_destroy(message_box);
}
}
/* Attempt to grab focus for the toplevel of this widget, so that peers can
* get events too. */
static void
userhelper_grab_keyboard(GtkWidget *widget, GdkEvent *event, gpointer user_data)
{
(void)event;
(void)user_data;
#ifndef DEBUG_USERHELPER
GdkGrabStatus ret;
ret = gdk_keyboard_grab(gtk_widget_get_window (widget), TRUE,
GDK_CURRENT_TIME);
if (ret != GDK_GRAB_SUCCESS) {
switch (ret) {
case GDK_GRAB_ALREADY_GRABBED:
g_warning("keyboard grab failed: keyboard already grabbed by another window");
break;
case GDK_GRAB_INVALID_TIME:
g_warning("keyboard grab failed: requested grab time was invalid (shouldn't happen)");
break;
case GDK_GRAB_NOT_VIEWABLE:
g_warning("keyboard grab failed: window requesting grab is not viewable");
break;
case GDK_GRAB_FROZEN:
g_warning("keyboard grab failed: keyboard is already grabbed");
break;
default:
g_warning("keyboard grab failed: unknown error %d", (int) ret);
break;
}
}
#endif
}
/* Try to send a string encoded in UTF-8 to the child. */
static void
write_childin_string(const char *s)
{
char *converted;
gsize s_len, converted_len;
GError *err;
s_len = strlen(s);
err = NULL;
converted = g_locale_from_utf8(s, s_len, NULL, &converted_len, &err);
if (err == NULL) {
ssize_t nbytes = write(childin[1], converted, converted_len);
if (nbytes < 0 || (gsize)nbytes < converted_len)
g_warning("Unable to send data to child");
g_free(converted);
} else {
g_error_free(err);
/* Oh well, some data is better than no data... */
ssize_t nbytes = write(childin[1], s, s_len);
if (nbytes < 0 || (gsize)nbytes < converted_len)
g_warning("Unable to send data to child");
}
}
/* Handle the executed dialog, writing responses back to the child when
* possible. */
static void
userhelper_write_childin(GtkResponseType response, struct response *resp)
{
static const unsigned char sync_point[] = { UH_SYNC_POINT, '\n' };
static const unsigned char eol = '\n';
ssize_t nbytes;
gboolean startup = FALSE;
switch ((int)response) { // The (int) cast silences a warning: case value ‘100’ not in enumerated type ‘GtkResponseType {aka enum <anonymous>}’
case RESPONSE_FALLBACK: {
/* The user wants to run unprivileged. */
static const unsigned char cmd[] = { UH_FALLBACK, '\n' };
debug_msg("Responding FALLBACK.\n");
nbytes = write(childin[1], cmd, sizeof(cmd));
if (nbytes < 0 || (size_t)nbytes < sizeof(cmd))
g_warning("Unable to send data to child");
startup = TRUE;
break;
}
case GTK_RESPONSE_CANCEL: {
/* The user doesn't want to run this after all. */
static const unsigned char cmd[] = { UH_CANCEL, '\n' };
debug_msg("Responding CANCEL.\n");
nbytes = write(childin[1], cmd, sizeof(cmd));
if (nbytes < 0 || (size_t)nbytes < sizeof(cmd))
g_warning("Unable to send data to child");
startup = FALSE;
break;
}
case GTK_RESPONSE_OK: {
GList *message_list;
/* The user answered the questions. */
for (message_list = resp->message_list;
message_list != NULL && message_list->data != NULL;
message_list = g_list_next(message_list)) {
struct message *m = ((struct message *)
message_list->data);
debug_msg("message %d\n", m->type);
if (GTK_IS_ENTRY(m->entry))
debug_msg("Responding `%s'.\n",
gtk_entry_get_text(GTK_ENTRY
(m->entry)));
if (GTK_IS_ENTRY(m->entry)) {
static const unsigned char cmd = UH_TEXT;
const char *s;
s = gtk_entry_get_text(GTK_ENTRY (m->entry));
nbytes = write(childin[1], &cmd, sizeof(cmd));
if (nbytes < 0 || (size_t)nbytes < sizeof(cmd))
g_warning("Unable to send data to child");
write_childin_string(s);
nbytes = write(childin[1], &eol, sizeof(eol));
if (nbytes < 0 || (size_t)nbytes < sizeof(eol))
g_warning("Unable to send data to child");
}
}
startup = TRUE;
break;
}
default:
/* We were closed, deleted, canceled, or something else which
we can treat as a cancellation. */
/*startup = FALSE;*/ /* this is not neccessary */
_exit(1);
break;
}
if (startup && sn_name != NULL) {
/* If we haven't set up notification yet, do so. */
if (sn_id == NULL)
userhelper_startup_notification_launcher();
#ifdef USE_STARTUP_NOTIFICATION
/* Tell the child what its ID is. */
if (sn_id != NULL) {
static const unsigned char cmd = UH_SN_ID;
debug_msg("Sending new window startup ID \"%s\".\n",
sn_id);
nbytes = write(childin[1], &cmd, sizeof(cmd));
if (nbytes < 0 || (size_t)nbytes < sizeof(cmd))
g_warning("Unable to send data to child");
write_childin_string(sn_id);
nbytes = write(childin[1], &eol, sizeof(eol));
if (nbytes < 0 || (size_t)nbytes < sizeof(eol))
g_warning("Unable to send data to child");
}
#endif
}
/* Tell the child we have no more to say. */
debug_msg("Sending synchronization point.\n");
nbytes = write(childin[1], sync_point, sizeof(sync_point));
if (nbytes < 0 || (size_t)nbytes < sizeof(sync_point))
g_warning("Unable to send data to child");
}
/* Glue. */
static void
fake_respond_ok(GtkWidget *widget, gpointer user_data)
{
(void)widget;
gtk_dialog_response(GTK_DIALOG(user_data), GTK_RESPONSE_OK);
}
/* Used only when resp->dialog is displayed non-modally. */
static void
userhelper_dialog_response(GtkDialog *dialog, gint response, gpointer user_data)
{
(void)dialog;
(void)user_data;
if (response == GTK_RESPONSE_CANCEL)
/* This is the best we can do because the child is not waiting
for a reponse. */ {
g_print("cancel from response!\n");
userhelper_main_quit();
}
}
/* Handle a request from the child userhelper process and display it in a
message box. */
static void
userhelper_handle_childout(char prompt_type, char *prompt)
{
static struct response *resp = NULL;
if (resp == NULL) {
/* Allocate the response structure. */
resp = g_malloc0(sizeof(struct response));
/* Create a table to hold the entry fields and labels. */
resp->table = gtk_table_new(2, 1, FALSE);
/* Hold to the table after gtk_box_pack_start(). */
g_object_ref_sink(resp->table);
/* The First row is used for the "Authenticating as \"%s\""
label. */
resp->rows = 1;
}
debug_msg("Child message: (%d)/\"%s\"\n", prompt_type, prompt);
switch (prompt_type) {
case UH_PROMPT_SUGGESTION:
/* A suggestion for the next input. */
g_free(resp->suggestion);
resp->suggestion = g_strdup(prompt);
debug_msg("Suggested response \"%s\".\n", resp->suggestion);
break;
case UH_ECHO_OFF_PROMPT: case UH_ECHO_ON_PROMPT: {
struct message *msg;
/* Prompts. Create a label and entry field. */
msg = g_malloc(sizeof(*msg));
msg->type = prompt_type;
/* Only set the title to "Query" if it isn't already set to
"Error" or something else more meaningful. */
if (resp->title == NULL)
resp->title = _("Query");
/* Create a label to hold the prompt */
msg->label = gtk_label_new(_(prompt));
gtk_label_set_line_wrap(GTK_LABEL(msg->label), TRUE);
gtk_misc_set_alignment(GTK_MISC(msg->label), 1.0, 0.5);
/* Create an entry field to hold the answer. */
msg->entry = gtk_entry_new();
/* Make a feeble gesture at being accessible. */
gtk_label_set_mnemonic_widget(GTK_LABEL(msg->label),
GTK_WIDGET(msg->entry));
gtk_entry_set_visibility(GTK_ENTRY(msg->entry),
prompt_type == UH_ECHO_ON_PROMPT);
/* If we had a suggestion, use it up. */
if (resp->suggestion) {
gtk_entry_set_text(GTK_ENTRY(msg->entry),
resp->suggestion);
g_free(resp->suggestion);
resp->suggestion = NULL;
}
/* Keep track of the first entry field in the dialog box. */
if (resp->first == NULL)
resp->first = msg->entry;
/* Keep track of the last entry field in the dialog box. */
resp->last = msg->entry;
/* Insert them. */
gtk_table_attach(GTK_TABLE(resp->table), msg->label, 0, 1,
resp->rows, resp->rows + 1,
GTK_EXPAND | GTK_FILL, 0, PAD, PAD);
gtk_table_attach(GTK_TABLE(resp->table), msg->entry, 1, 2,
resp->rows, resp->rows + 1,
GTK_EXPAND | GTK_FILL, 0, PAD, PAD);
/* Add this message to the list of messages. */
resp->message_list = g_list_append(resp->message_list, msg);
/* Note that this one needs a response. */
resp->responses++;
resp->rows++;
debug_msg("Now we need %d responses.\n", resp->responses);
break;
}
case UH_FALLBACK_ALLOW:
/* Fallback flag. Read it and save it for later. */
resp->fallback_allowed = atoi(prompt) != 0;
debug_msg("Fallback %sallowed.\n",
resp->fallback_allowed ? "" : "not ");
break;
case UH_USER: {
char *text;
/* Tell the user which user's passwords to enter */
if (resp->user_label != NULL)
gtk_widget_destroy(resp->user_label);
debug_msg("User is \"%s\".\n", prompt);
text = g_strdup_printf(_("Authenticating as \"%s\""), prompt);
resp->user_label = gtk_label_new(text);
g_free(text);
gtk_misc_set_alignment(GTK_MISC(resp->user_label), 0.0, 0.5);
gtk_table_attach(GTK_TABLE(resp->table), resp->user_label, 0, 2,
0, 1, GTK_EXPAND | GTK_FILL, 0, PAD, PAD);
break;
}
case UH_SERVICE_NAME:
/* Service name. Read it and save it for later. */
g_free(resp->service);
resp->service = g_strdup(prompt);
debug_msg("Service is \"%s\".\n", resp->service);
break;
case UH_ERROR_MSG: case UH_INFO_MSG: {
struct message *msg;
/* An error/informational message. */
resp->title = (prompt_type == UH_ERROR_MSG
? _("Error") : _("Information"));
msg = g_malloc(sizeof(*msg));
msg->type = prompt_type;
msg->entry = NULL;
msg->label = gtk_label_new(_(prompt));
gtk_misc_set_alignment(GTK_MISC(msg->label), 0.0, 0.5);
gtk_table_attach(GTK_TABLE(resp->table), msg->label, 0, 2,
resp->rows, resp->rows + 1,
GTK_EXPAND | GTK_FILL, 0, PAD, PAD);
resp->message_list = g_list_append(resp->message_list, msg);
resp->rows++;
break;
}
case UH_BANNER:
/* An informative banner. */
g_free(resp->banner);
resp->banner = g_strdup(prompt);
debug_msg("Banner is \"%s\".\n", resp->banner);
break;
case UH_EXEC_START:
/* Userhelper is trying to exec. */
child_was_execed = TRUE;
debug_msg("Child started.\n");
break;
case UH_EXEC_FAILED:
/* Userhelper failed to exec. */
child_was_execed = FALSE;
debug_msg("Child failed.\n");
break;
case UH_SN_NAME:
/* Startup notification name. */
g_free(sn_name);
sn_name = g_strdup(prompt);
debug_msg("SN Name is \"%s\".\n", sn_name);
break;
case UH_SN_DESCRIPTION:
/* Startup notification description. */
g_free(sn_description);
sn_description = g_strdup(prompt);
debug_msg("SN Description is \"%s\".\n", sn_description);
break;
case UH_SN_WORKSPACE:
/* Startup notification workspace. */
sn_workspace = atoi(prompt);
debug_msg("SN Workspace is %d.\n", sn_workspace);
break;
case UH_SN_WMCLASS:
/* Startup notification wmclass. */
g_free(sn_wmclass);
sn_wmclass = g_strdup(prompt);
debug_msg("SN WMClass is \"%s\".\n", sn_wmclass);
break;
case UH_SN_BINARY_NAME:
/* Startup notification binary name. */
g_free(sn_binary_name);
sn_binary_name = g_strdup(prompt);
debug_msg("SN Binary name is \"%s\".\n", sn_binary_name);
break;
case UH_SN_ICON_NAME:
/* Startup notification icon name. */
g_free(sn_icon_name);
sn_icon_name = g_strdup(prompt);
debug_msg("SN Icon name is \"%s\".\n", sn_icon_name);
break;
case UH_EXPECT_RESP:
/* Sanity-check for the number of expected responses. */
if (resp->responses != atoi(prompt)) {
fprintf(stderr, "Protocol error (%d responses expected "
"from %d prompts)!\n", atoi(prompt),
resp->responses);
_exit(1);
}
break;
/* Synchronization point -- no more prompts. */
case UH_SYNC_POINT:
break;
default:
break;
}
/* Complete startup notification for consolehelper. */
userhelper_startup_notification_launchee(NULL);
if (prompt_type != UH_SYNC_POINT)
return;
/* Destroy the dialog if a child is executing because userhelper
already has all required information. Destroy the dialog also if
resp->responses != 0 because the dialog has a slightly different
layout in that case, and if resp->dialog != NULL, it must have been
created when resp->responses was 0. */
if (resp->dialog != NULL
&& (child_was_execed || resp->responses != 0)) {
GtkWidget *vbox;
/* Don't destroy anything inside resp->table. */
vbox = gtk_dialog_get_content_area (GTK_DIALOG(resp->dialog));
gtk_container_remove(GTK_CONTAINER(vbox), resp->table);
gtk_widget_destroy(resp->dialog);
resp->dialog = NULL;
}
/* Don't show any diaogs if a child is executing or there is nothing
to show. */
if (child_was_execed || (resp->responses == 0 && resp->rows == 1)) {
/* This can happens when the child needs to wait for
UH_EXEC_START or UH_EXEC_FAILED response to make sure the
exit status is processed correctly. */
userhelper_write_childin(GTK_RESPONSE_OK, resp);
return;
}
/* Create and show the dialog if there is a message, even if no
reponses are required. The dialog might ask the user to do
something else (e.g. insert a hardware token). */
if (resp->dialog == NULL) {
char *text;
GtkWidget *label, *vbox;
/* If we already set up startup notification for the child,
something must have gone wrong (e.g. wrong password). Clean
it up, which will allow setting it up again. */
if (sn_id != NULL) {
userhelper_startup_notification_launchee(sn_id);
g_free(sn_id);
sn_id = NULL;
}
resp->dialog = gtk_message_dialog_new(NULL, 0,
resp->responses > 0 ?
GTK_MESSAGE_QUESTION :
GTK_MESSAGE_INFO,
resp->responses > 0 ?
GTK_BUTTONS_OK_CANCEL :
GTK_BUTTONS_CANCEL,
"Placeholder text.");
/* Ensure that we don't get dangling crap widget pointers. */
g_object_add_weak_pointer(G_OBJECT(resp->dialog),
&resp->dialog);
/* If we didn't get a title from userhelper, assume badness. */
gtk_window_set_title(GTK_WINDOW(resp->dialog),
resp->title ? resp->title : _("Error"));
gtk_window_set_position(GTK_WINDOW(resp->dialog),
GTK_WIN_POS_CENTER_ALWAYS);
gtk_window_set_keep_above(GTK_WINDOW(resp->dialog), TRUE);
gtk_window_set_icon_from_file(GTK_WINDOW(resp->dialog),
PIXMAPDIR "/password.png", NULL);
vbox = gtk_dialog_get_content_area (GTK_DIALOG(resp->dialog));
gtk_box_pack_start(GTK_BOX(vbox), resp->table, TRUE, TRUE, 0);
if (resp->responses > 0)
g_signal_connect(G_OBJECT(resp->dialog), "map_event",
G_CALLBACK(userhelper_grab_keyboard),
NULL);
g_signal_connect(G_OBJECT(resp->dialog), "delete_event",
G_CALLBACK(userhelper_fatal_error),
NULL);
resp->dialog_response_handler
= g_signal_connect(G_OBJECT(resp->dialog), "response",
G_CALLBACK
(userhelper_dialog_response), NULL);
if (resp->responses == 0)
text = g_strdup("");
else if (resp->service != NULL) {
if (strcmp(resp->service, "passwd") == 0)
text = g_strdup(_("Changing password."));
else if (strcmp(resp->service, "chfn") == 0)
text = g_strdup(_("Changing personal "
"information."));
else if (strcmp(resp->service, "chsh") == 0)
text = g_strdup(_("Changing login shell."));
else if (resp->banner != NULL)
text = g_strdup(resp->banner);
else if (resp->fallback_allowed)
text = g_strdup_printf(_("You are attempting to run \"%s\" which may benefit from administrative privileges, but more information is needed in order to do so."), resp->service);
else
text = g_strdup_printf(_("You are attempting to run \"%s\" which requires administrative privileges, but more information is needed in order to do so."), resp->service);
} else if (resp->banner != NULL)
text = g_strdup(resp->banner);
else if (resp->fallback_allowed)
text = g_strdup(_("You are attempting to run a command which may benefit from administrative privileges, but more information is needed in order to do so."));
else
text = g_strdup(_("You are attempting to run a command which requires administrative privileges, but more information is needed in order to do so."));
label = (GTK_MESSAGE_DIALOG(resp->dialog))->label;
gtk_label_set_text(GTK_LABEL(label), text);
g_free(text);
}
/* Do this every time to show widgets newly added to resp->table. */
gtk_widget_show_all(resp->dialog);
if (resp->responses == 0)
/* This might be just a module being stupid and calling the
conversation callback once. for. every. chunk. of. output
and we'll get an actual prompt later. */
userhelper_write_childin(GTK_RESPONSE_OK, resp);
else {
/* A non-zero number of queries demands an answer. */
GtkWidget *image;
GtkResponseType response;
#ifdef DEBUG_USERHELPER
{
int timeout = 2;
debug_msg("Ready to ask %d questions.\n", resp->responses);
debug_msg("Pausing for %d seconds for debugging.\n", timeout);
sleep(timeout);
}
#endif
/* We're asking questions, change the dialog's icon. */
image = gtk_message_dialog_get_image (GTK_MESSAGE_DIALOG
(resp->dialog));
gtk_image_set_from_file(GTK_IMAGE(image),
PIXMAPDIR "/keyring.png");
/* Add an "unprivileged" button if we're allowed to offer
* unprivileged execution as an option. */
if (resp->fallback_allowed) {
gtk_dialog_add_button(GTK_DIALOG(resp->dialog),
_("_Run Unprivileged"),
RESPONSE_FALLBACK);
}
/* Have the activation signal for the last entry field be
* equivalent to hitting the default button. */
if (resp->last) {
g_signal_connect(G_OBJECT(resp->last), "activate",
G_CALLBACK(fake_respond_ok),
resp->dialog);
}
/* Show the dialog and grab focus. */
gtk_widget_show_all(resp->dialog);
if (GTK_IS_ENTRY(resp->first)) {
gtk_widget_grab_focus(resp->first);
}
/* Run the dialog. */
g_signal_handler_disconnect(resp->dialog,
resp->dialog_response_handler);
response = gtk_dialog_run(GTK_DIALOG(resp->dialog));
/* Release the keyboard. */
gdk_keyboard_ungrab(GDK_CURRENT_TIME);
/* Answer the child's questions. */
userhelper_write_childin(response, resp);
/* Destroy the dialog box. */
gtk_widget_destroy(resp->dialog);
g_object_unref(resp->table);
g_free(resp->banner);
g_free(resp->suggestion);
g_free(resp->service);
if (resp->message_list) {
GList *e;
for (e = g_list_first(resp->message_list); e != NULL;
e = g_list_next(e))
g_free(e->data);
g_list_free(resp->message_list);
}
g_free(resp);
resp = NULL;
}
}
/* Handle a child-exited signal by disconnecting from its stdout. */
static void
userhelper_child_exited(GPid pid, int status, gpointer data)
{
(void)data;
debug_msg("Child %d exited (looking for %d).\n", pid, childpid);
if (pid == childpid) {
/* If we're doing startup notification, clean it up just in
* case the child didn't complete startup. */
if (sn_id != NULL) {
userhelper_startup_notification_launchee(sn_id);
}
/* If we haven't lost the connection with the child, it's
* gone now. */
if (childout_tag != 0) {
g_source_remove(childout_tag);
childout_tag = 0;
}
if (WIFEXITED(status)) {
debug_msg("Child %d exited normally, ret = %d.\n", pid,
WEXITSTATUS(status));
userhelper_parse_exitstatus(WEXITSTATUS(status));
} else {
debug_msg("Child %d exited abnormally.\n", pid);
if (WIFSIGNALED(status)) {
debug_msg("Child %d died on signal %d.\n", pid,
WTERMSIG(status));
userhelper_parse_exitstatus(ERR_UNK_ERROR);
}
}
userhelper_main_quit();
}
}
/* Read data sent from the child userhelper process and pass it on to
* userhelper_parse_childout(). */
static gboolean
userhelper_read_childout(GIOChannel *source, GIOCondition condition,
gpointer data)
{
(void)data;
if ((condition & (G_IO_ERR | G_IO_NVAL)) != 0) {
/* Serious error, this is. Panic, we must. */
_exit(1);
}
if ((condition & G_IO_HUP) != 0) {
/* EOF from the child. */
debug_msg("EOF from child.\n");
childout_tag = 0;
return FALSE;
}
while ((condition & G_IO_IN) != 0) {
char command, eol, buf[BUFSIZ], *end, *converted;
unsigned long request_size;
gsize bytes_read;
GError *err;
err = NULL;
g_io_channel_read_chars(source, &command, 1, &bytes_read, &err);
if (err != NULL || bytes_read != 1)
_exit(0); /* EOF, or error of some kind. */
assert(UH_REQUEST_SIZE_DIGITS + 1 <= sizeof(buf));
g_io_channel_read_chars(source, buf, UH_REQUEST_SIZE_DIGITS,
&bytes_read, &err);
if (err != NULL || bytes_read != UH_REQUEST_SIZE_DIGITS
|| !g_ascii_isdigit(*buf))
_exit(1); /* Error of some kind. */
buf[bytes_read] = '\0';
errno = 0;
request_size = strtoul(buf, &end, 10);
if (errno != 0 || *end != 0 || end == buf ||
request_size + 1 > sizeof(buf))
_exit(1); /* Error of some kind. */
g_io_channel_read_chars(source, buf, request_size, &bytes_read,
&err);
if (err != NULL || bytes_read != request_size)
_exit(1); /* Error of some kind. */
converted = g_locale_to_utf8(buf, request_size, &bytes_read,
NULL, &err);
if (err != NULL || bytes_read != request_size)
_exit(1);
g_io_channel_read_chars(source, &eol, 1, &bytes_read, &err);
if (err != NULL || bytes_read != 1 || eol != '\n')
_exit(0); /* EOF, or error of some kind. */
userhelper_handle_childout(command, converted);
g_free(converted);
condition = g_io_channel_get_buffer_condition(source);
}
return TRUE;
}
int
userhelper_runv(gboolean dialog_success, const char *path, char **args)
{
int childout[2];
/* Create pipes with which to interact with the userhelper child. */
if ((pipe(childout) == -1) || (pipe(childin) == -1)) {
fprintf(stderr, _("Pipe error.\n"));
_exit(1);
}
/* Start up a new process. */
childpid = fork();
if (childpid == -1) {
fprintf(stderr, _("Cannot fork().\n"));
_exit(0);
}
if (childpid > 0) {
GIOChannel *childout_channel;
GError *err;
/* We're the parent; close the write-end of the reading pipe,
* and the read-end of the writing pipe. */
close(childout[1]);
close(childin[0]);
/* Keep track of whether or not we need to display a dialog
* box on successful termination. */
child_success_dialog = dialog_success;
/* Watch the reading end of the reading pipe for data from the
child. */
childout_channel = g_io_channel_unix_new(childout[0]);
err = NULL;
g_io_channel_set_encoding(childout_channel, NULL, &err);
if (err != NULL) {
fprintf(stderr, _("Can't set binary encoding on an IO "
"channel: %s\n"), err->message);
_exit(1);
}
g_io_channel_set_line_term(childout_channel, "\n", -1);
childout_tag = g_io_add_watch(childout_channel,
G_IO_IN | G_IO_HUP | G_IO_ERR
| G_IO_NVAL,
userhelper_read_childout, NULL);
/* Watch for child exits. */
child_exit_status = 0;
g_child_watch_add(childpid, userhelper_child_exited, NULL);
debug_msg("Running child pid=%ld.\n", (long) childpid);
/* Tell the child we're ready for it to run. */
if (write(childin[1], "Go", 1) < 1)
g_warning("Unable to send data to child");
gtk_main();
/* If we're doing startup notification, clean it up just in
* case the child didn't complete startup. */
userhelper_startup_notification_launchee(sn_id);
debug_msg("Child exited, continuing.\n");
} else {
int i, fd[4];
unsigned char byte;
long open_max;
/* We're the child; close the read-end of the parent's reading
* pipe, and the write-end of the parent's writing pipe. */
close(childout[0]);
close(childin[1]);
/* Read one byte from the parent so that we know it's ready
* to go. */
if (read(childin[0], &byte, 1) < 1)
g_warning("Can't read from parent");
/* Close all of descriptors which aren't stdio or the two
* pipe descriptors we'll be using. */
open_max = sysconf(_SC_OPEN_MAX);
for (i = 3; i < open_max; i++) {
if ((i != childout[1]) && (i != childin[0])) {
close(i);
}
}
/* First create two copies of stdin, in case 3 and 4 aren't
currently in use. STDIN_FILENO is surely valid even if the
user has started us with <&- because the X11 socket uses
a file descriptor. */
fd[0] = dup(STDIN_FILENO);
fd[1] = dup(STDIN_FILENO);
/* Now create temporary copies of the pipe descriptors, which
* aren't goint to be 3 or 4 because they are surely in use
* by now. */
fd[2] = dup(childin[0]);
fd[3] = dup(childout[1]);
/* Now get rid of the temporary descriptors, */
close(fd[0]);
close(fd[1]);
close(childin[0]);
close(childout[1]);
/* and move the pipe descriptors to their new homes. */
if (dup2(fd[2], UH_INFILENO) == -1) {
fprintf(stderr, _("dup2() error.\n"));
_exit(2);
}
if (dup2(fd[3], UH_OUTFILENO) == -1) {
fprintf(stderr, _("dup2() error.\n"));
_exit(2);
}
close(fd[2]);
close(fd[3]);
#ifdef DEBUG_USERHELPER
for (i = 0; args[i] != NULL; i++)
debug_msg("Exec arg %d = \"%s\".\n", i, args[i]);
#endif
execv(path, args);
fprintf(stderr, _("execl() error, errno=%d\n"), errno);
_exit(0);
}
return child_exit_status;
}
void
userhelper_run(gboolean dialog_success, const char *path, ...)
{
va_list ap;
char **argv;
int argc = 0;
int i = 0;
/* Count the number of arguments. */
va_start(ap, path);
while (va_arg(ap, char *) != NULL) {
argc++;
}
va_end(ap);
/* Copy the arguments into a normal array. */
argv = g_malloc_n(argc + 1, sizeof(*argv));
va_start(ap, path);
for (i = 0; i < argc; i++)
argv[i] = va_arg(ap, char *);
argv[i] = NULL;
va_end(ap);
/* Pass the array into userhelper_runv() to actually run it. */
userhelper_runv(dialog_success, path, argv);
g_free(argv);
}
|