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