1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2010-2011 Vic Lee
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2021 Antenore Gatta, Giovanni Panozzo
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA  02110-1301, USA.
21  *
22  *  In addition, as a special exception, the copyright holders give
23  *  permission to link the code of portions of this program with the
24  *  OpenSSL library under certain conditions as described in each
25  *  individual source file, and distribute linked combinations
26  *  including the two.
27  *  You must obey the GNU General Public License in all respects
28  *  for all of the code used other than OpenSSL. *  If you modify
29  *  file(s) with this exception, you may extend this exception to your
30  *  version of the file(s), but you are not obligated to do so. *  If you
31  *  do not wish to do so, delete this exception statement from your
32  *  version. *  If you delete this exception statement from all source
33  *  files in the program, then also delete it here.
34  *
35  */
36 
37 #include <errno.h>
38 #include <pthread.h>
39 #include <stdarg.h>
40 #include "common/remmina_plugin.h"
41 #include <gtk/gtkx.h>
42 #include <time.h>
43 #define LIBSSH_STATIC 1
44 #include <libssh/libssh.h>
45 #include <X11/Xlib.h>
46 #include <X11/XKBlib.h>
47 #include <X11/extensions/XKBrules.h>
48 #include "nx_plugin.h"
49 #include "nx_session_manager.h"
50 
51 #define REMMINA_PLUGIN_NX_FEATURE_TOOL_SENDCTRLALTDEL 1
52 #define REMMINA_PLUGIN_NX_FEATURE_GTKSOCKET 1
53 
54 /* Forward declaration */
55 static RemminaProtocolPlugin remmina_plugin_nx;
56 
57 RemminaPluginService *remmina_plugin_nx_service = NULL;
58 
59 static gchar *remmina_kbtype = "pc102/us";
60 
61 /* When more than one NX sessions is connecting in progress, we need this mutex and array
62  * to prevent them from stealing the same window ID.
63  */
64 static pthread_mutex_t remmina_nx_init_mutex;
65 static GArray *remmina_nx_window_id_array;
66 
67 /* --------- Support for execution on main thread of GTK functions -------------- */
68 
69 struct onMainThread_cb_data {
70 	enum { FUNC_GTK_SOCKET_ADD_ID } func;
71 
72 	GtkSocket* sk;
73 	Window w;
74 
75 	/* Mutex for thread synchronization */
76 	pthread_mutex_t mu;
77 	/* Flag to catch cancellations */
78 	gboolean cancelled;
79 };
80 
onMainThread_cb(struct onMainThread_cb_data * d)81 static gboolean onMainThread_cb(struct onMainThread_cb_data *d)
82 {
83 	TRACE_CALL(__func__);
84 	if ( !d->cancelled ) {
85 		switch ( d->func ) {
86 		case FUNC_GTK_SOCKET_ADD_ID:
87 			gtk_socket_add_id( d->sk, d->w );
88 			break;
89 		}
90 		pthread_mutex_unlock( &d->mu );
91 	} else {
92 		/* Thread has been cancelled, so we must free d memory here */
93 		g_free( d );
94 	}
95 	return G_SOURCE_REMOVE;
96 }
97 
98 
onMainThread_cleanup_handler(gpointer data)99 static void onMainThread_cleanup_handler(gpointer data)
100 {
101 	TRACE_CALL(__func__);
102 	struct onMainThread_cb_data *d = data;
103 	d->cancelled = TRUE;
104 }
105 
106 
onMainThread_schedule_callback_and_wait(struct onMainThread_cb_data * d)107 static void onMainThread_schedule_callback_and_wait( struct onMainThread_cb_data *d )
108 {
109 	TRACE_CALL(__func__);
110 	d->cancelled = FALSE;
111 	pthread_cleanup_push( onMainThread_cleanup_handler, d );
112 	pthread_mutex_init( &d->mu, NULL );
113 	pthread_mutex_lock( &d->mu );
114 	gdk_threads_add_idle( (GSourceFunc)onMainThread_cb, (gpointer)d );
115 
116 	pthread_mutex_lock( &d->mu );
117 
118 	pthread_cleanup_pop(0);
119 	pthread_mutex_unlock( &d->mu );
120 	pthread_mutex_destroy( &d->mu );
121 }
122 
onMainThread_gtk_socket_add_id(GtkSocket * sk,Window w)123 static void onMainThread_gtk_socket_add_id( GtkSocket* sk, Window w)
124 {
125 	TRACE_CALL(__func__);
126 
127 	struct onMainThread_cb_data *d;
128 
129 	d = (struct onMainThread_cb_data *)g_malloc( sizeof(struct onMainThread_cb_data) );
130 	d->func = FUNC_GTK_SOCKET_ADD_ID;
131 	d->sk = sk;
132 	d->w = w;
133 
134 	onMainThread_schedule_callback_and_wait( d );
135 	g_free(d);
136 
137 }
138 
139 
140 /* --------------------------------------- */
141 
142 
remmina_plugin_nx_try_window_id(Window window_id)143 static gboolean remmina_plugin_nx_try_window_id(Window window_id)
144 {
145 	TRACE_CALL(__func__);
146 	gint i;
147 	gboolean found = FALSE;
148 
149 	pthread_mutex_lock(&remmina_nx_init_mutex);
150 	for (i = 0; i < remmina_nx_window_id_array->len; i++) {
151 		if (g_array_index(remmina_nx_window_id_array, Window, i) == window_id) {
152 			found = TRUE;
153 			break;
154 		}
155 	}
156 	if (!found) {
157 		g_array_append_val(remmina_nx_window_id_array, window_id);
158 	}
159 	pthread_mutex_unlock(&remmina_nx_init_mutex);
160 
161 	return (!found);
162 }
163 
remmina_plugin_nx_remove_window_id(Window window_id)164 static void remmina_plugin_nx_remove_window_id(Window window_id)
165 {
166 	TRACE_CALL(__func__);
167 	gint i;
168 	gboolean found = FALSE;
169 
170 	pthread_mutex_lock(&remmina_nx_init_mutex);
171 	for (i = 0; i < remmina_nx_window_id_array->len; i++) {
172 		if (g_array_index(remmina_nx_window_id_array, Window, i) == window_id) {
173 			found = TRUE;
174 			break;
175 		}
176 	}
177 	if (found) {
178 		g_array_remove_index_fast(remmina_nx_window_id_array, i);
179 	}
180 	pthread_mutex_unlock(&remmina_nx_init_mutex);
181 }
182 
remmina_plugin_nx_on_plug_added(GtkSocket * socket,RemminaProtocolWidget * gp)183 static void remmina_plugin_nx_on_plug_added(GtkSocket *socket, RemminaProtocolWidget *gp)
184 {
185 	TRACE_CALL(__func__);
186 	remmina_plugin_nx_service->protocol_plugin_signal_connection_opened(gp);
187 }
188 
remmina_plugin_nx_on_plug_removed(GtkSocket * socket,RemminaProtocolWidget * gp)189 static void remmina_plugin_nx_on_plug_removed(GtkSocket *socket, RemminaProtocolWidget *gp)
190 {
191 	TRACE_CALL(__func__);
192 	remmina_plugin_nx_service->protocol_plugin_signal_connection_closed(gp);
193 }
194 
remmina_plugin_nx_ssh_auth_callback(gchar ** passphrase,gpointer userdata)195 gboolean remmina_plugin_nx_ssh_auth_callback(gchar **passphrase, gpointer userdata)
196 {
197 	TRACE_CALL(__func__);
198 	RemminaProtocolWidget *gp = (RemminaProtocolWidget*)userdata;
199 	gint ret;
200 
201 	/* SSH passwords must not be saved */
202 	ret = remmina_plugin_nx_service->protocol_plugin_init_auth(gp, 0,
203 		_("SSH credentials"), NULL,
204 		NULL,
205 		NULL,
206 		_("Password for private SSH key"));
207 	if (ret == GTK_RESPONSE_OK) {
208 		*passphrase = remmina_plugin_nx_service->protocol_plugin_init_get_password(gp);
209 		return TRUE;
210 	} else
211 		return FALSE;
212 }
213 
remmina_plugin_nx_on_proxy_exit(GPid pid,gint status,gpointer data)214 static void remmina_plugin_nx_on_proxy_exit(GPid pid, gint status, gpointer data)
215 {
216 	TRACE_CALL(__func__);
217 	RemminaProtocolWidget *gp = (RemminaProtocolWidget*)data;
218 
219 	remmina_plugin_nx_service->protocol_plugin_signal_connection_closed(gp);
220 }
221 
remmina_plugin_nx_dummy_handler(Display * dsp,XErrorEvent * err)222 static int remmina_plugin_nx_dummy_handler(Display *dsp, XErrorEvent *err)
223 {
224 	TRACE_CALL(__func__);
225 	return 0;
226 }
227 
remmina_plugin_nx_start_create_notify(RemminaProtocolWidget * gp)228 static gboolean remmina_plugin_nx_start_create_notify(RemminaProtocolWidget *gp)
229 {
230 	TRACE_CALL(__func__);
231 	RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
232 
233 	gpdata->display = XOpenDisplay(gdk_display_get_name(gdk_display_get_default()));
234 	if (gpdata->display == NULL)
235 		return FALSE;
236 
237 	gpdata->orig_handler = XSetErrorHandler(remmina_plugin_nx_dummy_handler);
238 
239 	XSelectInput(gpdata->display, XDefaultRootWindow(gpdata->display), SubstructureNotifyMask);
240 
241 	return TRUE;
242 }
243 
remmina_plugin_nx_monitor_create_notify(RemminaProtocolWidget * gp,const gchar * cmd)244 static gboolean remmina_plugin_nx_monitor_create_notify(RemminaProtocolWidget *gp, const gchar *cmd)
245 {
246 	TRACE_CALL(__func__);
247 	RemminaPluginNxData *gpdata;
248 	Atom atom;
249 	XEvent xev;
250 	Window w;
251 	Atom type;
252 	int format;
253 	unsigned long nitems, rest;
254 	unsigned char *data = NULL;
255 	struct timespec ts;
256 
257 	CANCEL_DEFER
258 
259 		gpdata = GET_PLUGIN_DATA(gp);
260 	atom = XInternAtom(gpdata->display, "WM_COMMAND", True);
261 	if (atom == None)
262 		return FALSE;
263 
264 	ts.tv_sec = 0;
265 	ts.tv_nsec = 200000000;
266 
267 	while (1) {
268 		pthread_testcancel();
269 		while (!XPending(gpdata->display)) {
270 			nanosleep(&ts, NULL);
271 			continue;
272 		}
273 		XNextEvent(gpdata->display, &xev);
274 		if (xev.type != CreateNotify)
275 			continue;
276 		w = xev.xcreatewindow.window;
277 		if (XGetWindowProperty(gpdata->display, w, atom, 0, 255, False, AnyPropertyType, &type, &format, &nitems, &rest,
278 			    &data) != Success)
279 			continue;
280 		if (data && strstr((char*)data, cmd) && remmina_plugin_nx_try_window_id(w)) {
281 			gpdata->window_id = w;
282 			XFree(data);
283 			break;
284 		}
285 		if (data)
286 			XFree(data);
287 	}
288 
289 	XSetErrorHandler(gpdata->orig_handler);
290 	XCloseDisplay(gpdata->display);
291 	gpdata->display = NULL;
292 
293 	CANCEL_ASYNC
294 	return TRUE;
295 }
296 
remmina_plugin_nx_wait_signal(RemminaPluginNxData * gpdata)297 static gint remmina_plugin_nx_wait_signal(RemminaPluginNxData *gpdata)
298 {
299 	TRACE_CALL(__func__);
300 	fd_set set;
301 	guchar dummy = 0;
302 
303 	FD_ZERO(&set);
304 	FD_SET(gpdata->event_pipe[0], &set);
305 	select(gpdata->event_pipe[0] + 1, &set, NULL, NULL, NULL);
306 	if (read(gpdata->event_pipe[0], &dummy, 1)) {
307 	}
308 	return (gint)dummy;
309 }
310 
remmina_plugin_nx_log_callback(const gchar * fmt,...)311 static void remmina_plugin_nx_log_callback(const gchar *fmt, ...)
312 {
313 	char buffer[256];
314 	va_list args;
315 	va_start(args, fmt);
316 	vsnprintf(buffer, sizeof(buffer), fmt, args);
317 	REMMINA_PLUGIN_DEBUG(buffer);
318 	va_end(args);
319 }
320 
321 
remmina_plugin_nx_start_session(RemminaProtocolWidget * gp)322 static gboolean remmina_plugin_nx_start_session(RemminaProtocolWidget *gp)
323 {
324 	TRACE_CALL(__func__);
325 	RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
326 	RemminaFile *remminafile;
327 	RemminaNXSession *nx;
328 	const gchar *type, *app;
329 	gchar *s1, *s2;
330 	gint port;
331 	gint ret;
332 	gboolean is_empty_list;
333 	gint event_type = 0;
334 	const gchar *cs;
335 	gint i;
336 	gboolean disablepasswordstoring;
337 
338 	remminafile = remmina_plugin_nx_service->protocol_plugin_get_file(gp);
339 	nx = gpdata->nx;
340 
341 	/* Connect */
342 
343 	remmina_nx_session_set_encryption(nx,
344 		remmina_plugin_nx_service->file_get_int(remminafile, "disableencryption", FALSE) ? 0 : 1);
345 	remmina_nx_session_set_localport(nx, remmina_plugin_nx_service->pref_get_sshtunnel_port());
346 	remmina_nx_session_set_log_callback(nx, remmina_plugin_nx_log_callback);
347 
348 	s2 = remmina_plugin_nx_service->protocol_plugin_start_direct_tunnel(gp, 22, FALSE);
349 	if (s2 == NULL) {
350 		return FALSE;
351 	}
352 	remmina_plugin_nx_service->get_server_port(s2, 22, &s1, &port);
353 	g_free(s2);
354 
355 	if (!remmina_nx_session_open(nx, s1, port, remmina_plugin_nx_service->file_get_string(remminafile, "nx_privatekey"),
356 		    remmina_plugin_nx_ssh_auth_callback, gp)) {
357 		g_free(s1);
358 		return FALSE;
359 	}
360 	g_free(s1);
361 
362 	/* Login */
363 
364 	s1 = g_strdup(remmina_plugin_nx_service->file_get_string(remminafile, "username"));
365 	s2 = g_strdup(remmina_plugin_nx_service->file_get_string(remminafile, "password"));
366 
367 	if (s1 && s2) {
368 		ret = remmina_nx_session_login(nx, s1, s2);
369 	} else {
370 		gchar *s_username, *s_password;
371 
372 		disablepasswordstoring = remmina_plugin_nx_service->file_get_int(remminafile, "disablepasswordstoring", FALSE);
373 
374 		ret = remmina_plugin_nx_service->protocol_plugin_init_auth(gp,
375 			(disablepasswordstoring ? 0 : REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD) | REMMINA_MESSAGE_PANEL_FLAG_USERNAME,
376 			_("Enter NX authentication credentials"),
377 			remmina_plugin_nx_service->file_get_string(remminafile, "username"),
378 			remmina_plugin_nx_service->file_get_string(remminafile, "password"),
379 			NULL,
380 			NULL);
381 		if (ret == GTK_RESPONSE_OK) {
382 			gboolean save;
383 			s_username = remmina_plugin_nx_service->protocol_plugin_init_get_username(gp);
384 			s_password = remmina_plugin_nx_service->protocol_plugin_init_get_password(gp);
385 			save = remmina_plugin_nx_service->protocol_plugin_init_get_savepassword(gp);
386 			if (save) {
387 				remmina_plugin_nx_service->file_set_string(remminafile, "username", s_username);
388 				remmina_plugin_nx_service->file_set_string(remminafile, "password", s_password);
389 			} else
390 				remmina_plugin_nx_service->file_unsave_passwords(remminafile);
391 		} else {
392 			return False;
393 		}
394 
395 		ret = remmina_nx_session_login(nx, s_username, s_password);
396 		g_free(s_username);
397 		g_free(s_password);
398 	}
399 	g_free(s1);
400 	g_free(s2);
401 
402 	if (!ret)
403 		return FALSE;
404 
405 	remmina_plugin_nx_service->protocol_plugin_init_save_cred(gp);
406 
407 	/* Prepare the session type and application */
408 	cs = remmina_plugin_nx_service->file_get_string(remminafile, "exec");
409 	if (!cs || g_strcmp0(cs, "GNOME") == 0) {
410 		type = "unix-gnome";
411 		app = NULL;
412 	}else
413 	if (g_strcmp0(cs, "KDE") == 0) {
414 		type = "unix-kde";
415 		app = NULL;
416 	}else
417 	if (g_strcmp0(cs, "Xfce") == 0) {
418 		/* NX does not know Xfce. So we simply launch the Xfce start-up program. */
419 		type = "unix-application";
420 		app = "startxfce4";
421 	}else
422 	if (g_strcmp0(cs, "Shadow") == 0) {
423 		type = "shadow";
424 		app = NULL;
425 	}else  {
426 		type = "unix-application";
427 		app = cs;
428 	}
429 
430 	/* List sessions */
431 
432 	gpdata->attach_session = (g_strcmp0(type, "shadow") == 0);
433 	while (1) {
434 		remmina_nx_session_add_parameter(nx, "type", type);
435 		if (!gpdata->attach_session) {
436 			remmina_nx_session_add_parameter(nx, "user",
437 				remmina_plugin_nx_service->file_get_string(remminafile, "username"));
438 			remmina_nx_session_add_parameter(nx, "status", "suspended,running");
439 		}
440 
441 		if (!remmina_nx_session_list(nx)) {
442 			return FALSE;
443 		}
444 
445 		is_empty_list = !remmina_nx_session_iter_first(nx, &gpdata->iter);
446 		if (is_empty_list && !gpdata->manager_started && !gpdata->attach_session) {
447 			event_type = REMMINA_NX_EVENT_START;
448 		}else  {
449 			remmina_nx_session_manager_start(gp);
450 			event_type = remmina_plugin_nx_wait_signal(gpdata);
451 			if (event_type == REMMINA_NX_EVENT_CANCEL) {
452 				return FALSE;
453 			}
454 			if (event_type == REMMINA_NX_EVENT_TERMINATE) {
455 				if (!is_empty_list) {
456 					s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_ID);
457 					remmina_nx_session_add_parameter(nx, "sessionid", s1);
458 					g_free(s1);
459 					if (!remmina_nx_session_terminate(nx)) {
460 						remmina_nx_session_manager_start(gp);
461 						remmina_plugin_nx_wait_signal(gpdata);
462 					}
463 				}
464 				continue;
465 			}
466 		}
467 
468 		break;
469 	}
470 
471 	/* Start, Restore or Attach, based on the setting and existing session */
472 	remmina_nx_session_add_parameter(nx, "type", type);
473 	i = remmina_plugin_nx_service->file_get_int(remminafile, "quality", 0);
474 	remmina_nx_session_add_parameter(nx, "link", i > 2 ? "lan" : i == 2 ? "adsl" : i == 1 ? "isdn" : "modem");
475 	remmina_nx_session_add_parameter(nx, "geometry", "%ix%i",
476 		remmina_plugin_nx_service->get_profile_remote_width(gp),
477 		remmina_plugin_nx_service->get_profile_remote_height(gp));
478 	remmina_nx_session_add_parameter(nx, "keyboard", remmina_kbtype);
479 	remmina_nx_session_add_parameter(nx, "client", "linux");
480 	remmina_nx_session_add_parameter(nx, "media", "0");
481 	remmina_nx_session_add_parameter(nx, "clipboard",
482 		remmina_plugin_nx_service->file_get_int(remminafile, "disableclipboard", FALSE) ? "none" : "both");
483 
484 	switch (event_type) {
485 
486 	case REMMINA_NX_EVENT_START:
487 		if (app)
488 			remmina_nx_session_add_parameter(nx, "application", app);
489 
490 		remmina_nx_session_add_parameter(nx, "session",
491 			remmina_plugin_nx_service->file_get_string(remminafile, "name"));
492 		remmina_nx_session_add_parameter(nx, "screeninfo", "%ix%ix24+render",
493 			remmina_plugin_nx_service->file_get_int(remminafile, "resolution_width", 0),
494 			remmina_plugin_nx_service->file_get_int(remminafile, "resolution_height", 0));
495 
496 		if (!remmina_nx_session_start(nx))
497 			return FALSE;
498 		break;
499 
500 	case REMMINA_NX_EVENT_ATTACH:
501 		s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_ID);
502 		remmina_nx_session_add_parameter(nx, "id", s1);
503 		g_free(s1);
504 
505 		s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_DISPLAY);
506 		remmina_nx_session_add_parameter(nx, "display", s1);
507 		g_free(s1);
508 
509 		if (!remmina_nx_session_attach(nx))
510 			return FALSE;
511 		break;
512 
513 	case REMMINA_NX_EVENT_RESTORE:
514 		s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_ID);
515 		remmina_nx_session_add_parameter(nx, "id", s1);
516 		g_free(s1);
517 
518 		remmina_nx_session_add_parameter(nx, "session",
519 			remmina_plugin_nx_service->file_get_string(remminafile, "name"));
520 
521 		if (!remmina_nx_session_restore(nx))
522 			return FALSE;
523 		break;
524 
525 	default:
526 		return FALSE;
527 	}
528 
529 	if (!remmina_nx_session_tunnel_open(nx))
530 		return FALSE;
531 
532 	if (!remmina_plugin_nx_start_create_notify(gp))
533 		return FALSE;
534 
535 	/* nxproxy */
536 	if (!remmina_nx_session_invoke_proxy(nx, -1, remmina_plugin_nx_on_proxy_exit, gp))
537 		return FALSE;
538 
539 	/* get the window id of the remote nxagent */
540 	if (!remmina_plugin_nx_monitor_create_notify(gp, "nxagent"))
541 		return FALSE;
542 
543 	/* embed it */
544 	onMainThread_gtk_socket_add_id(GTK_SOCKET(gpdata->socket), gpdata->window_id);
545 
546 
547 	return TRUE;
548 }
549 
remmina_plugin_nx_main(RemminaProtocolWidget * gp)550 static gboolean remmina_plugin_nx_main(RemminaProtocolWidget *gp)
551 {
552 	TRACE_CALL(__func__);
553 	RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
554 	gboolean ret;
555 	const gchar *err;
556 
557 	gpdata->nx = remmina_nx_session_new();
558 	ret = remmina_plugin_nx_start_session(gp);
559 	if (!ret) {
560 		err = remmina_nx_session_get_error(gpdata->nx);
561 		if (err) {
562 			remmina_plugin_nx_service->protocol_plugin_set_error(gp, "%s", err);
563 		}
564 	}
565 
566 	gpdata->thread = 0;
567 	return ret;
568 }
569 
remmina_plugin_nx_main_thread(gpointer data)570 static gpointer remmina_plugin_nx_main_thread(gpointer data)
571 {
572 	TRACE_CALL(__func__);
573 	RemminaProtocolWidget *gp = (RemminaProtocolWidget *)data;
574 	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
575 
576 	CANCEL_ASYNC
577 	if (!remmina_plugin_nx_main(gp)) {
578 		remmina_plugin_nx_service->protocol_plugin_signal_connection_closed(gp);
579 	}
580 	return NULL;
581 }
582 
remmina_plugin_nx_init(RemminaProtocolWidget * gp)583 static void remmina_plugin_nx_init(RemminaProtocolWidget *gp)
584 {
585 	TRACE_CALL(__func__);
586 	RemminaPluginNxData *gpdata;
587 	gint flags;
588 
589 	gpdata = g_new0(RemminaPluginNxData, 1);
590 	g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
591 
592 	gpdata->socket = gtk_socket_new();
593 	remmina_plugin_nx_service->protocol_plugin_register_hostkey(gp, gpdata->socket);
594 	gtk_widget_show(gpdata->socket);
595 	g_signal_connect(G_OBJECT(gpdata->socket), "plug-added", G_CALLBACK(remmina_plugin_nx_on_plug_added), gp);
596 	g_signal_connect(G_OBJECT(gpdata->socket), "plug-removed", G_CALLBACK(remmina_plugin_nx_on_plug_removed), gp);
597 	gtk_container_add(GTK_CONTAINER(gp), gpdata->socket);
598 
599 	if (pipe(gpdata->event_pipe)) {
600 		g_print("Error creating pipes.\n");
601 		gpdata->event_pipe[0] = -1;
602 		gpdata->event_pipe[1] = -1;
603 	}else  {
604 		flags = fcntl(gpdata->event_pipe[0], F_GETFL, 0);
605 		fcntl(gpdata->event_pipe[0], F_SETFL, flags | O_NONBLOCK);
606 	}
607 }
608 
remmina_plugin_nx_open_connection(RemminaProtocolWidget * gp)609 static gboolean remmina_plugin_nx_open_connection(RemminaProtocolWidget *gp)
610 {
611 	TRACE_CALL(__func__);
612 	RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
613 	gint width, height;
614 
615 	if (!remmina_plugin_nx_service->gtksocket_available()) {
616 		remmina_plugin_nx_service->protocol_plugin_set_error(gp,
617 			_("The protocol “%s” is unavailable because GtkSocket only works under X.Org."),
618 			remmina_plugin_nx.name);
619 		return FALSE;
620 	}
621 
622 	width = remmina_plugin_nx_service->get_profile_remote_width(gp);
623 	height = remmina_plugin_nx_service->get_profile_remote_height(gp);
624 
625 	remmina_plugin_nx_service->protocol_plugin_set_width(gp, width);
626 	remmina_plugin_nx_service->protocol_plugin_set_height(gp, height);
627 	gtk_widget_set_size_request(GTK_WIDGET(gp), width, height);
628 
629 	gpdata->socket_id = gtk_socket_get_id(GTK_SOCKET(gpdata->socket));
630 
631 	if (pthread_create(&gpdata->thread, NULL, remmina_plugin_nx_main_thread, gp)) {
632 		remmina_plugin_nx_service->protocol_plugin_set_error(gp,
633 			"Failed to initialize pthread. Falling back to non-thread mode…");
634 		gpdata->thread = 0;
635 		return FALSE;
636 	}else  {
637 		return TRUE;
638 	}
639 }
640 
remmina_plugin_nx_close_connection(RemminaProtocolWidget * gp)641 static gboolean remmina_plugin_nx_close_connection(RemminaProtocolWidget *gp)
642 {
643 	TRACE_CALL(__func__);
644 	RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
645 
646 	if (gpdata->thread) {
647 		pthread_cancel(gpdata->thread);
648 		if (gpdata->thread)
649 			pthread_join(gpdata->thread, NULL);
650 	}
651 	if (gpdata->session_manager_start_handler) {
652 		g_source_remove(gpdata->session_manager_start_handler);
653 		gpdata->session_manager_start_handler = 0;
654 	}
655 
656 	if (gpdata->window_id) {
657 		remmina_plugin_nx_remove_window_id(gpdata->window_id);
658 	}
659 
660 	if (gpdata->nx) {
661 		remmina_nx_session_free(gpdata->nx);
662 		gpdata->nx = NULL;
663 	}
664 
665 	if (gpdata->display) {
666 		XSetErrorHandler(gpdata->orig_handler);
667 		XCloseDisplay(gpdata->display);
668 		gpdata->display = NULL;
669 	}
670 	close(gpdata->event_pipe[0]);
671 	close(gpdata->event_pipe[1]);
672 
673 	remmina_plugin_nx_service->protocol_plugin_signal_connection_closed(gp);
674 
675 	return FALSE;
676 }
677 
678 /* Send CTRL+ALT+DEL keys keystrokes to the plugin socket widget */
remmina_plugin_nx_send_ctrlaltdel(RemminaProtocolWidget * gp)679 static void remmina_plugin_nx_send_ctrlaltdel(RemminaProtocolWidget *gp)
680 {
681 	TRACE_CALL(__func__);
682 	guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
683 	RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
684 
685 	remmina_plugin_nx_service->protocol_plugin_send_keys_signals(gpdata->socket,
686 		keys, G_N_ELEMENTS(keys), GDK_KEY_PRESS | GDK_KEY_RELEASE);
687 }
688 
remmina_plugin_nx_query_feature(RemminaProtocolWidget * gp,const RemminaProtocolFeature * feature)689 static gboolean remmina_plugin_nx_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
690 {
691 	TRACE_CALL(__func__);
692 	return TRUE;
693 }
694 
remmina_plugin_nx_call_feature(RemminaProtocolWidget * gp,const RemminaProtocolFeature * feature)695 static void remmina_plugin_nx_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
696 {
697 	TRACE_CALL(__func__);
698 	switch (feature->id) {
699 	case REMMINA_PLUGIN_NX_FEATURE_TOOL_SENDCTRLALTDEL:
700 		remmina_plugin_nx_send_ctrlaltdel(gp);
701 		break;
702 	default:
703 		break;
704 	}
705 }
706 
707 /* Array of key/value pairs for quality selection */
708 static gpointer quality_list[] =
709 {
710 	"0", N_("Poor (fastest)"),
711 	"1", N_("Medium"),
712 	"2", N_("Good"),
713 	"9", N_("Best (slowest)"),
714 	NULL
715 };
716 
717 /* Array of RemminaProtocolSetting for basic settings.
718  * Each item is composed by:
719  * a) RemminaProtocolSettingType for setting type
720  * b) Setting name
721  * c) Setting description
722  * d) Compact disposition
723  * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
724  * f) Setting tooltip
725  */
726 static const RemminaProtocolSetting remmina_plugin_nx_basic_settings[] =
727 {
728 	{ REMMINA_PROTOCOL_SETTING_TYPE_SERVER,	    "server",	     NULL,		    FALSE, NULL,		    NULL },
729 	{ REMMINA_PROTOCOL_SETTING_TYPE_FILE,	    "nx_privatekey", N_("SSH identity file"),   FALSE, NULL,		    NULL },
730 	{ REMMINA_PROTOCOL_SETTING_TYPE_TEXT,	    "username",	     N_("Username"),	    FALSE, NULL,		    NULL },
731 	{ REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD,   "password",	     N_("User password"),   FALSE, NULL,		    NULL },
732 	{ REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION, "resolution",    NULL,		    FALSE, GINT_TO_POINTER(1),	    NULL },
733 	{ REMMINA_PROTOCOL_SETTING_TYPE_SELECT,	    "quality",	     N_("Quality"),	    FALSE, quality_list,	    NULL },
734 	{ REMMINA_PROTOCOL_SETTING_TYPE_COMBO,	    "exec",	     N_("Start-up program"), FALSE, "GNOME,KDE,Xfce,Shadow", NULL },
735 	{ REMMINA_PROTOCOL_SETTING_TYPE_END,	    NULL,	     NULL,		    FALSE, NULL,		    NULL }
736 };
737 
738 /* Array of RemminaProtocolSetting for advanced settings.
739  * Each item is composed by:
740  * a) RemminaProtocolSettingType for setting type
741  * b) Setting name
742  * c) Setting description
743  * d) Compact disposition
744  * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
745  * f) Setting tooltip
746  */
747 static const RemminaProtocolSetting remmina_plugin_nx_advanced_settings[] =
748 {
749 	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard",	 N_("Disable clipboard sync"),	 FALSE, NULL, NULL },
750 	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableencryption",	 N_("Disable encryption"),	 FALSE, NULL, NULL },
751 	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "showcursor",		 N_("Use local cursor"),	 FALSE, NULL, NULL },
752 	{ REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Forget passwords after use"), FALSE, NULL, NULL },
753 	{ REMMINA_PROTOCOL_SETTING_TYPE_END,   NULL,			 NULL,				 FALSE, NULL, NULL }
754 };
755 
756 /* Array for available features.
757  * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */
758 static const RemminaProtocolFeature remmina_plugin_nx_features[] =
759 {
760 	{ REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_NX_FEATURE_TOOL_SENDCTRLALTDEL, N_("Send Ctrl+Alt+Del"), NULL, NULL },
761 	{ REMMINA_PROTOCOL_FEATURE_TYPE_GTKSOCKET, REMMINA_PLUGIN_NX_FEATURE_GTKSOCKET,	    NULL,	   NULL,	NULL},
762 	{ REMMINA_PROTOCOL_FEATURE_TYPE_END,  0,					     NULL,			 NULL, NULL }
763 };
764 
765 /* Protocol plugin definition and features */
766 static RemminaProtocolPlugin remmina_plugin_nx =
767 {
768 	REMMINA_PLUGIN_TYPE_PROTOCOL,                   // Type
769 	"NX",                                           // Name
770 	N_("NX - NX Technology"),                       // Description
771 	GETTEXT_PACKAGE,                                // Translation domain
772 	VERSION,                                        // Version number
773 	"remmina-nx-symbolic",                          // Icon for normal connection
774 	"remmina-nx-symbolic",                          // Icon for SSH connection
775 	remmina_plugin_nx_basic_settings,               // Array for basic settings
776 	remmina_plugin_nx_advanced_settings,            // Array for advanced settings
777 	REMMINA_PROTOCOL_SSH_SETTING_TUNNEL,            // SSH settings type
778 	remmina_plugin_nx_features,                     // Array for available features
779 	remmina_plugin_nx_init,                         // Plugin initialization
780 	remmina_plugin_nx_open_connection,              // Plugin open connection
781 	remmina_plugin_nx_close_connection,             // Plugin close connection
782 	remmina_plugin_nx_query_feature,                // Query for available features
783 	remmina_plugin_nx_call_feature,                 // Call a feature
784 	NULL,                                           // Send a keystroke
785 	NULL,                                           // No screenshot support available
786 	NULL,                                           // RCW map event
787 	NULL                                            // RCW unmap event
788 };
789 
790 G_MODULE_EXPORT gboolean
remmina_plugin_entry(RemminaPluginService * service)791 remmina_plugin_entry(RemminaPluginService *service)
792 {
793 	TRACE_CALL(__func__);
794 	Display *dpy;
795 	XkbRF_VarDefsRec vd;
796 	gchar *s;
797 
798 	remmina_plugin_nx_service = service;
799 
800 	bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
801 	bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
802 
803 	if ((dpy = XkbOpenDisplay(NULL, NULL, NULL, NULL, NULL, NULL)) != NULL) {
804 		if (XkbRF_GetNamesProp(dpy, NULL, &vd)) {
805 			remmina_kbtype = g_strdup_printf("%s/%s", vd.model, vd.layout);
806 			if (vd.layout)
807 				XFree(vd.layout);
808 			if (vd.model)
809 				XFree(vd.model);
810 			if (vd.variant)
811 				XFree(vd.variant);
812 			if (vd.options)
813 				XFree(vd.options);
814 			s = strchr(remmina_kbtype, ',');
815 			if (s)
816 				*s = '\0';
817 			/* g_print("NX: Detected “%s” keyboard type\n", remmina_kbtype); */
818 		}
819 		XCloseDisplay(dpy);
820 	}
821 
822 	if (!service->register_plugin((RemminaPlugin*)&remmina_plugin_nx)) {
823 		return FALSE;
824 	}
825 
826 	ssh_init();
827 	pthread_mutex_init(&remmina_nx_init_mutex, NULL);
828 	remmina_nx_window_id_array = g_array_new(FALSE, TRUE, sizeof(Window));
829 
830 	return TRUE;
831 }
832