1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2004-2006 William Jon McCann <mccann@jhu.edu>
4 * Copyright (C) 2012-2021 MATE Developers
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19 * 02110-1301, USA.
20 *
21 * Authors: William Jon McCann <mccann@jhu.edu>
22 *
23 */
24
25 #include "config.h"
26
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/time.h>
30 #include <sys/types.h>
31 #include <sys/wait.h>
32 #include <unistd.h>
33 #include <signal.h>
34
35 #include <glib/gi18n.h>
36 #include <gdk/gdkx.h>
37 #include <gtk/gtk.h>
38 #include <gtk/gtkx.h>
39
40 #include "gs-lock-plug.h"
41
42 #include "gs-auth.h"
43 #include "setuid.h"
44
45 #include "gs-debug.h"
46
47 #define MAX_FAILURES 5
48
49 static gboolean verbose = FALSE;
50 static gboolean show_version = FALSE;
51 static gboolean enable_logout = FALSE;
52 static gboolean enable_switch = FALSE;
53 static char* logout_command = NULL;
54 static char* status_message = NULL;
55 static char* away_message = NULL;
56
57 static GOptionEntry entries[] = {
58 {"verbose", 0, 0, G_OPTION_ARG_NONE, &verbose, N_("Show debugging output"), NULL},
59 {"version", 0, 0, G_OPTION_ARG_NONE, &show_version, N_("Version of this application"), NULL},
60 {"enable-logout", 0, 0, G_OPTION_ARG_NONE, &enable_logout, N_("Show the logout button"), NULL},
61 {"logout-command", 0, 0, G_OPTION_ARG_STRING, &logout_command, N_("Command to invoke from the logout button"), NULL},
62 {"enable-switch", 0, 0, G_OPTION_ARG_NONE, &enable_switch, N_("Show the switch user button"), NULL},
63 {"status-message", 0, 0, G_OPTION_ARG_STRING, &status_message, N_("Message to show in the dialog"), N_("MESSAGE")},
64 {"away-message", 0, 0, G_OPTION_ARG_STRING, &away_message, N_("Not used"), N_("MESSAGE")},
65 {NULL}
66 };
67
get_id_string(GtkWidget * widget)68 static char* get_id_string(GtkWidget* widget)
69 {
70 char* id = NULL;
71
72 g_return_val_if_fail(widget != NULL, NULL);
73 g_return_val_if_fail(GTK_IS_WIDGET(widget), NULL);
74
75 id = g_strdup_printf("%" G_GUINT32_FORMAT, (guint32) GDK_WINDOW_XID(gtk_widget_get_window(widget)));
76 return id;
77 }
78
print_id(GtkWidget * widget)79 static gboolean print_id(GtkWidget* widget)
80 {
81 char* id;
82
83 gs_profile_start(NULL);
84
85 id = get_id_string(widget);
86 printf("WINDOW ID=%s\n", id);
87 fflush(stdout);
88
89 gs_profile_end(NULL);
90
91 g_free(id);
92
93 return FALSE;
94 }
95
response_cancel(void)96 static void response_cancel(void)
97 {
98 printf("RESPONSE=CANCEL\n");
99 fflush(stdout);
100 }
101
response_ok(void)102 static void response_ok(void)
103 {
104 printf("RESPONSE=OK\n");
105 fflush(stdout);
106 }
107
quit_response_ok(gpointer data)108 static gboolean quit_response_ok(gpointer data)
109 {
110 (void)data; /* remove warning unused parameter ‘data’ */
111 response_ok();
112 gtk_main_quit();
113 return FALSE;
114 }
115
quit_response_cancel(void)116 static gboolean quit_response_cancel(void)
117 {
118 response_cancel();
119 gtk_main_quit();
120 return FALSE;
121 }
122
response_lock_init_failed(void)123 static void response_lock_init_failed(void)
124 {
125 /* if we fail to lock then we should drop the dialog */
126 response_ok();
127 }
128
request_response(GSLockPlug * plug,const char * prompt,gboolean visible)129 static char* request_response(GSLockPlug* plug, const char* prompt, gboolean visible)
130 {
131 int response;
132 char* text;
133
134 gs_lock_plug_set_sensitive(plug, TRUE);
135 gs_lock_plug_enable_prompt(plug, prompt, visible);
136 response = gs_lock_plug_run(plug);
137
138 gs_debug ("got response: %d", response);
139
140 text = NULL;
141
142 if (response == GS_LOCK_PLUG_RESPONSE_OK)
143 {
144 gs_lock_plug_get_text(plug, &text);
145 }
146
147 gs_lock_plug_disable_prompt(plug);
148
149 return text;
150 }
151
152 /* Adapted from MDM2 daemon/verify-pam.c on 2006-06-13 */
maybe_translate_message(const char * msg)153 static const char* maybe_translate_message(const char* msg)
154 {
155 char* s;
156 const char* ret;
157 static GHashTable* hash = NULL;
158
159 if (hash == NULL)
160 {
161 /* Here we come with some fairly standard messages so that
162 we have as much as possible translated. Should really be
163 translated in pam I suppose. This way we can "change"
164 some of these messages to be more sane. */
165 hash = g_hash_table_new (g_str_hash, g_str_equal);
166 /* login: is whacked always translate to Username: */
167 g_hash_table_insert(hash, "login:", _("Username:"));
168 g_hash_table_insert(hash, "Username:", _("Username:"));
169 g_hash_table_insert(hash, "username:", _("Username:"));
170 g_hash_table_insert(hash, "Password:", _("Password:"));
171 g_hash_table_insert(hash, "password:", _("Password:"));
172 g_hash_table_insert(hash, "You are required to change your password immediately (password aged)", _("You are required to change your password immediately (password aged)"));
173 g_hash_table_insert(hash, "You are required to change your password immediately (root enforced)", _("You are required to change your password immediately (root enforced)"));
174 g_hash_table_insert(hash, "Your account has expired; please contact your system administrator", _("Your account has expired; please contact your system administrator"));
175 g_hash_table_insert(hash, "No password supplied", _("No password supplied"));
176 g_hash_table_insert(hash, "Password unchanged", _("Password unchanged"));
177 g_hash_table_insert(hash, "Can not get username", _("Can not get username"));
178 g_hash_table_insert(hash, "Retype new UNIX password:", _("Retype new UNIX password:"));
179 g_hash_table_insert(hash, "Enter new UNIX password:", _("Enter new UNIX password:"));
180 g_hash_table_insert(hash, "(current) UNIX password:", _("(current) UNIX password:"));
181 g_hash_table_insert(hash, "Error while changing NIS password.", _("Error while changing NIS password."));
182 g_hash_table_insert(hash, "You must choose a longer password", _("You must choose a longer password"));
183 g_hash_table_insert(hash, "Password has been already used. Choose another.", _("Password has been already used. Choose another."));
184 g_hash_table_insert(hash, "You must wait longer to change your password", _("You must wait longer to change your password"));
185 g_hash_table_insert(hash, "Sorry, passwords do not match", _("Sorry, passwords do not match"));
186 /* FIXME: what about messages which have some variables in them, perhaps try to do those as well */
187 }
188
189 s = g_strstrip(g_strdup(msg));
190 ret = g_hash_table_lookup(hash, s);
191 g_free(s);
192
193 if (ret != NULL)
194 {
195 return ret;
196 }
197 else
198 {
199 return msg;
200 }
201 }
202
auth_message_handler(GSAuthMessageStyle style,const char * msg,char ** response,gpointer data)203 static gboolean auth_message_handler(GSAuthMessageStyle style, const char* msg, char** response, gpointer data)
204 {
205 gboolean ret;
206 GSLockPlug* plug;
207 const char* message;
208
209 plug = GS_LOCK_PLUG(data);
210
211 gs_profile_start(NULL);
212 gs_debug("Got message style %d: '%s'", style, msg);
213
214 gtk_widget_show(GTK_WIDGET(plug));
215 gs_lock_plug_set_ready(plug);
216
217 ret = TRUE;
218 *response = NULL;
219 message = maybe_translate_message(msg);
220
221 switch (style)
222 {
223 case GS_AUTH_MESSAGE_PROMPT_ECHO_ON:
224 if (msg != NULL)
225 {
226 char *resp;
227 resp = request_response(plug, message, TRUE);
228 *response = resp;
229 }
230 break;
231 case GS_AUTH_MESSAGE_PROMPT_ECHO_OFF:
232 if (msg != NULL)
233 {
234 char *resp;
235 resp = request_response(plug, message, FALSE);
236 *response = resp;
237 }
238 break;
239 case GS_AUTH_MESSAGE_ERROR_MSG:
240 gs_lock_plug_show_message(plug, message);
241 break;
242 case GS_AUTH_MESSAGE_TEXT_INFO:
243 gs_lock_plug_show_message(plug, message);
244 break;
245 default:
246 g_assert_not_reached();
247 }
248
249 if (*response == NULL)
250 {
251 gs_debug("Got no response");
252 ret = FALSE;
253 }
254 else
255 {
256 gs_lock_plug_show_message(plug, _("Checking..."));
257 gs_lock_plug_set_sensitive(plug, FALSE);
258 }
259
260 /* we may have pending events that should be processed before continuing back into PAM */
261 while (gtk_events_pending())
262 {
263 gtk_main_iteration();
264 }
265
266 gs_lock_plug_set_busy(plug);
267 gs_profile_end(NULL);
268
269 return ret;
270 }
271
reset_idle_cb(GSLockPlug * plug)272 static gboolean reset_idle_cb(GSLockPlug* plug)
273 {
274 gs_lock_plug_set_sensitive(plug, TRUE);
275 gs_lock_plug_show_message(plug, NULL);
276
277 return FALSE;
278 }
279
do_auth_check(GSLockPlug * plug)280 static gboolean do_auth_check(GSLockPlug* plug)
281 {
282 GError* error;
283 gboolean res;
284
285 error = NULL;
286
287 gs_lock_plug_disable_prompt(plug);
288 gs_lock_plug_set_busy(plug);
289 res = gs_auth_verify_user(g_get_user_name(), g_getenv("DISPLAY"), auth_message_handler, plug, &error);
290
291 gs_debug("Verify user returned: %s", res ? "TRUE" : "FALSE");
292
293 if (! res)
294 {
295 if (error != NULL)
296 {
297 gs_debug("Verify user returned error: %s", error->message);
298 gs_lock_plug_show_message(plug, error->message);
299 }
300 else
301 {
302 gs_lock_plug_show_message(plug, _("Authentication failed."));
303 }
304
305 printf("NOTICE=AUTH FAILED\n");
306 fflush(stdout);
307
308 if (error != NULL)
309 {
310 g_error_free(error);
311 }
312 }
313
314 return res;
315 }
316
response_cb(GSLockPlug * plug,gint response_id)317 static void response_cb(GSLockPlug* plug, gint response_id)
318 {
319 if ((response_id == GS_LOCK_PLUG_RESPONSE_CANCEL) || (response_id == GTK_RESPONSE_DELETE_EVENT))
320 {
321 quit_response_cancel();
322 }
323 }
324
response_request_quit(gpointer data)325 static gboolean response_request_quit(gpointer data)
326 {
327 (void)data; /* remove warning unused parameter ‘data’ */
328 printf("REQUEST QUIT\n");
329 fflush(stdout);
330 return FALSE;
331 }
332
quit_timeout_cb(gpointer data)333 static gboolean quit_timeout_cb(gpointer data)
334 {
335 gtk_main_quit();
336 return FALSE;
337 }
338
auth_check_idle(gpointer data)339 static gboolean auth_check_idle(gpointer data)
340 {
341 GSLockPlug* plug = data;
342 gboolean res;
343 gboolean again;
344 static guint loop_counter = 0;
345
346 again = TRUE;
347 res = do_auth_check(plug);
348
349 if (res)
350 {
351 again = FALSE;
352 g_idle_add (quit_response_ok, NULL);
353 }
354 else
355 {
356 loop_counter++;
357
358 if (loop_counter < MAX_FAILURES)
359 {
360 gs_debug ("Authentication failed, retrying (%u)", loop_counter);
361 g_timeout_add (3000, (GSourceFunc) reset_idle_cb, plug);
362 }
363 else
364 {
365 gs_debug ("Authentication failed, quitting (max failures)");
366 again = FALSE;
367 /* Don't quit immediately, but rather request that mate-screensaver
368 * terminates us after it has finished the dialog shake. Time out
369 * after 5 seconds and quit anyway if this doesn't happen though */
370 g_idle_add (response_request_quit, NULL);
371 g_timeout_add(5000, (GSourceFunc) quit_timeout_cb, NULL);
372 }
373 }
374
375 return again;
376 }
377
show_cb(GtkWidget * widget,gpointer data)378 static void show_cb(GtkWidget* widget, gpointer data)
379 {
380 print_id(widget);
381 }
382
popup_dialog_idle(gpointer data)383 static gboolean popup_dialog_idle(gpointer data)
384 {
385 GtkWidget* widget;
386
387 (void)data; /* remove warning unused parameter ‘data’ */
388 gs_profile_start(NULL);
389
390 widget = gs_lock_plug_new();
391
392 if (enable_logout)
393 {
394 g_object_set(widget, "logout-enabled", TRUE, NULL);
395 }
396
397 if (logout_command)
398 {
399 g_object_set(widget, "logout-command", logout_command, NULL);
400 }
401
402 if (enable_switch)
403 {
404 g_object_set(widget, "switch-enabled", TRUE, NULL);
405 }
406
407 if (status_message)
408 {
409 g_object_set(widget, "status-message", status_message, NULL);
410 }
411
412 g_signal_connect(GS_LOCK_PLUG(widget), "response", G_CALLBACK(response_cb), NULL);
413 g_signal_connect(widget, "show", G_CALLBACK(show_cb), NULL);
414
415 gtk_widget_realize(widget);
416
417 g_idle_add(auth_check_idle, widget);
418
419 gs_profile_end(NULL);
420
421 return FALSE;
422 }
423
424
425 /*
426 * Copyright (c) 1991-2004 Jamie Zawinski <jwz@jwz.org>
427 * Copyright (c) 2005 William Jon McCann <mccann@jhu.edu>
428 *
429 * Initializations that potentially take place as a priveleged user:
430 If the executable is setuid root, then these initializations
431 are run as root, before discarding privileges.
432 */
433 static gboolean
privileged_initialization(int * argc,char ** argv)434 privileged_initialization (int* argc, char** argv)
435 {
436 gboolean ret;
437 char* nolock_reason;
438 char* orig_uid;
439 char* uid_message;
440
441 gs_profile_start(NULL);
442
443 #ifndef NO_LOCKING
444 /* before hack_uid () for proper permissions */
445 gs_auth_priv_init();
446 #endif /* NO_LOCKING */
447
448 ret = hack_uid(&nolock_reason, &orig_uid, &uid_message);
449
450 if (nolock_reason)
451 {
452 g_debug("Locking disabled: %s", nolock_reason);
453 }
454
455 if (uid_message && verbose)
456 {
457 g_print("Modified UID: %s", uid_message);
458 }
459
460 g_free(nolock_reason);
461 g_free(orig_uid);
462 g_free(uid_message);
463
464 gs_profile_end(NULL);
465
466 return ret;
467 }
468
469
470 /*
471 * Copyright (c) 1991-2004 Jamie Zawinski <jwz@jwz.org>
472 * Copyright (c) 2005 William Jon McCann <mccann@jhu.edu>
473 *
474 * Figure out what locking mechanisms are supported.
475 */
476 static gboolean
lock_initialization(int * argc,char ** argv,char ** nolock_reason)477 lock_initialization (int* argc, char** argv, char** nolock_reason)
478 {
479 if (nolock_reason != NULL)
480 {
481 *nolock_reason = NULL;
482 }
483
484 #ifdef NO_LOCKING
485
486 if (nolock_reason != NULL)
487 {
488 *nolock_reason = g_strdup("not compiled with locking support");
489 }
490
491 return FALSE;
492 #else /* !NO_LOCKING */
493
494 /* Finish initializing locking, now that we're out of privileged code. */
495 if (!gs_auth_init())
496 {
497 if (nolock_reason != NULL)
498 {
499 *nolock_reason = g_strdup("error getting password");
500 }
501
502 return FALSE;
503 }
504
505 /* If locking is currently enabled, but the environment indicates that
506 * we have been launched as MDM's "Background" program, then disable
507 * locking just in case.
508 */
509 if (getenv("RUNNING_UNDER_MDM"))
510 {
511 if (nolock_reason != NULL)
512 {
513 *nolock_reason = g_strdup("running under MDM");
514 }
515
516 return FALSE;
517 }
518
519 /* If the server is XDarwin (MacOS X) then disable locking.
520 * (X grabs only affect X programs, so you can use Command-Tab
521 * to bring any other Mac program to the front, e.g., Terminal.)
522 */
523 {
524 gboolean macos = FALSE;
525
526 #ifdef __APPLE__
527 /* Disable locking if *running* on Apple hardware, since we have no
528 * reliable way to determine whether the server is running on MacOS.
529 * Hopefully __APPLE__ means "MacOS" and not "Linux on Mac hardware"
530 * but I'm not really sure about that.
531 */
532 macos = TRUE;
533 #endif /* __APPLE__ */
534
535 if (macos)
536 {
537 if (nolock_reason != NULL)
538 {
539 *nolock_reason = g_strdup("Cannot lock securely on MacOS X");
540 }
541
542 return FALSE;
543 }
544 }
545
546 #endif /* NO_LOCKING */
547
548 return TRUE;
549 }
550
main(int argc,char ** argv)551 int main(int argc, char** argv)
552 {
553 GError* error = NULL;
554 char* nolock_reason = NULL;
555
556 #ifdef ENABLE_NLS
557 bindtextdomain(GETTEXT_PACKAGE, MATELOCALEDIR);
558 #ifdef HAVE_BIND_TEXTDOMAIN_CODESET
559 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
560 #endif
561 textdomain(GETTEXT_PACKAGE);
562 #endif
563
564 gs_profile_start(NULL);
565
566 if (!privileged_initialization (&argc, argv))
567 {
568 response_lock_init_failed();
569 exit(1);
570 }
571
572 error = NULL;
573
574 if (!gtk_init_with_args(&argc, &argv, NULL, entries, NULL, &error))
575 {
576 if (error != NULL)
577 {
578 fprintf(stderr, "%s", error->message);
579 g_error_free(error);
580 }
581
582 exit(1);
583 }
584
585 if (show_version)
586 {
587 g_print("%s %s\n", argv[0], VERSION);
588 exit(1);
589 }
590
591 if (!lock_initialization(&argc, argv, &nolock_reason))
592 {
593 if (nolock_reason != NULL)
594 {
595 g_debug ("Screen locking disabled: %s", nolock_reason);
596 g_free (nolock_reason);
597 }
598
599 response_lock_init_failed();
600 exit (1);
601 }
602
603 gs_debug_init(verbose, FALSE);
604
605 g_idle_add (popup_dialog_idle, NULL);
606
607 gtk_main();
608
609 gs_profile_end(NULL);
610 gs_debug_shutdown();
611
612 return 0;
613 }
614