Remmina - The GTK+ Remote Desktop Client  v1.4.2
Remmina is a remote desktop client written in GTK+, aiming to be useful for system administrators and travellers, who need to work with lots of remote computers in front of either large monitors or tiny netbooks. Remmina supports multiple network protocols in an integrated and consistent user interface. Currently RDP, VNC, NX, XDMCP and SSH are supported.
nx_session.c
Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2010 Vic Lee
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2020 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 "common/remmina_plugin.h"
40 #include <glib/gstdio.h>
41 #define LIBSSH_STATIC 1
42 #include <libssh/libssh.h>
43 #include "nx_session.h"
44 
45 /* Some missing stuff in libssh */
46 #define REMMINA_SSH_TYPE_DSS 1
47 #define REMMINA_SSH_TYPE_RSA 2
48 
49 static gboolean remmina_get_keytype(const gchar *private_key_file, gint *keytype, gboolean *encrypted)
50 {
51  TRACE_CALL(__func__);
52  FILE *fp;
53  gchar buf1[100], buf2[100];
54 
55  if ((fp = g_fopen(private_key_file, "r")) == NULL) {
56  return FALSE;
57  }
58  if (!fgets(buf1, sizeof(buf1), fp) || !fgets(buf2, sizeof(buf2), fp)) {
59  fclose(fp);
60  return FALSE;
61  }
62  fclose(fp);
63 
64  if (strstr(buf1, "BEGIN RSA"))
65  *keytype = REMMINA_SSH_TYPE_RSA;
66  else if (strstr(buf1, "BEGIN DSA"))
67  *keytype = REMMINA_SSH_TYPE_DSS;
68  else
69  return FALSE;
70 
71  *encrypted = (strstr(buf2, "ENCRYPTED") ? TRUE : FALSE);
72 
73  return TRUE;
74 }
75 
76 /*****/
77 
78 static const gchar nx_default_private_key[] = "-----BEGIN DSA PRIVATE KEY-----\n"
79  "MIIBuwIBAAKBgQCXv9AzQXjxvXWC1qu3CdEqskX9YomTfyG865gb4D02ZwWuRU/9\n"
80  "C3I9/bEWLdaWgJYXIcFJsMCIkmWjjeSZyTmeoypI1iLifTHUxn3b7WNWi8AzKcVF\n"
81  "aBsBGiljsop9NiD1mEpA0G+nHHrhvTXz7pUvYrsrXcdMyM6rxqn77nbbnwIVALCi\n"
82  "xFdHZADw5KAVZI7r6QatEkqLAoGBAI4L1TQGFkq5xQ/nIIciW8setAAIyrcWdK/z\n"
83  "5/ZPeELdq70KDJxoLf81NL/8uIc4PoNyTRJjtT3R4f8Az1TsZWeh2+ReCEJxDWgG\n"
84  "fbk2YhRqoQTtXPFsI4qvzBWct42WonWqyyb1bPBHk+JmXFscJu5yFQ+JUVNsENpY\n"
85  "+Gkz3HqTAoGANlgcCuA4wrC+3Cic9CFkqiwO/Rn1vk8dvGuEQqFJ6f6LVfPfRTfa\n"
86  "QU7TGVLk2CzY4dasrwxJ1f6FsT8DHTNGnxELPKRuLstGrFY/PR7KeafeFZDf+fJ3\n"
87  "mbX5nxrld3wi5titTnX+8s4IKv29HJguPvOK/SI7cjzA+SqNfD7qEo8CFDIm1xRf\n"
88  "8xAPsSKs6yZ6j1FNklfu\n"
89  "-----END DSA PRIVATE KEY-----\n";
90 
91 static const gchar nx_hello_server_msg[] = "hello nxserver - version ";
92 
94  /* Common SSH members */
95  ssh_session session;
96  ssh_channel channel;
97  gchar *server;
98  gchar *error;
100 
101  /* Tunnel related members */
102  pthread_t thread;
103  gboolean running;
105 
106  /* NX related members */
107  GHashTable *session_parameters;
108 
109  GString *response;
111  gint status;
113  gint localport;
114 
115  gchar *version;
116  gchar *session_id;
118  gchar *proxy_cookie;
119 
120  gboolean allow_start;
121  GtkListStore *session_list;
123 
124  GPid proxy_pid;
126 };
127 
130 {
131  TRACE_CALL(__func__);
132  RemminaNXSession *nx;
133 
134  nx = g_new0(RemminaNXSession, 1);
135 
136  nx->session_parameters = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
137  nx->response = g_string_new(NULL);
138  nx->status = -1;
139  nx->encryption = 1;
140  nx->server_sock = -1;
141 
142  return nx;
143 }
144 
146 {
147  TRACE_CALL(__func__);
148  pthread_t thread;
149 
150  if (nx->proxy_watch_source) {
151  g_source_remove(nx->proxy_watch_source);
152  nx->proxy_watch_source = 0;
153  }
154  if (nx->proxy_pid) {
155  kill(nx->proxy_pid, SIGTERM);
156  g_spawn_close_pid(nx->proxy_pid);
157  nx->proxy_pid = 0;
158  }
159  thread = nx->thread;
160  if (thread) {
161  nx->running = FALSE;
162  pthread_cancel(thread);
163  pthread_join(thread, NULL);
164  nx->thread = 0;
165  }
166  if (nx->channel) {
167  ssh_channel_close(nx->channel);
168  ssh_channel_free(nx->channel);
169  }
170  if (nx->server_sock >= 0) {
171  close(nx->server_sock);
172  nx->server_sock = -1;
173  }
174 
175  g_free(nx->server);
176  g_free(nx->error);
177  g_hash_table_destroy(nx->session_parameters);
178  g_string_free(nx->response, TRUE);
179  g_free(nx->version);
180  g_free(nx->session_id);
181  g_free(nx->proxy_cookie);
182 
183  if (nx->session_list) {
184  g_object_unref(nx->session_list);
185  nx->session_list = NULL;
186  }
187  if (nx->session) {
188  ssh_free(nx->session);
189  nx->session = NULL;
190  }
191  g_free(nx);
192 }
193 
194 static void remmina_nx_session_set_error(RemminaNXSession *nx, const gchar *fmt)
195 {
196  TRACE_CALL(__func__);
197  const gchar *err;
198 
199  if (nx->error)
200  g_free(nx->error);
201  err = ssh_get_error(nx->session);
202  nx->error = g_strdup_printf(fmt, err);
203 }
204 
205 static void remmina_nx_session_set_application_error(RemminaNXSession *nx, const gchar *fmt, ...)
206 {
207  TRACE_CALL(__func__);
208  va_list args;
209 
210  if (nx->error) g_free(nx->error);
211  va_start(args, fmt);
212  nx->error = g_strdup_vprintf(fmt, args);
213  va_end(args);
214 }
215 
217 {
218  TRACE_CALL(__func__);
219  return (nx->error != NULL);
220 }
221 
222 const gchar*
224 {
225  TRACE_CALL(__func__);
226  return nx->error;
227 }
228 
230 {
231  TRACE_CALL(__func__);
232  if (nx->error) {
233  g_free(nx->error);
234  nx->error = NULL;
235  }
236 }
237 
239 {
240  TRACE_CALL(__func__);
241  nx->encryption = encryption;
242 }
243 
245 {
246  TRACE_CALL(__func__);
247  nx->localport = localport;
248 }
249 
251 {
252  TRACE_CALL(__func__);
254 }
255 
257 {
258  TRACE_CALL(__func__);
259  struct timeval timeout;
260  ssh_channel ch[2];
261  gchar *buffer;
262  gint len;
263  gint is_stderr;
264 
265  timeout.tv_sec = 60;
266  timeout.tv_usec = 0;
267  ch[0] = nx->channel;
268  ch[1] = NULL;
269  ssh_channel_select(ch, NULL, NULL, &timeout);
270 
271  is_stderr = 0;
272  while (is_stderr <= 1) {
273  len = ssh_channel_poll(nx->channel, is_stderr);
274  if (len == SSH_ERROR) {
275  remmina_nx_session_set_error(nx, "Error reading channel: %s");
276  return FALSE;
277  }
278  if (len > 0)
279  break;
280  is_stderr++;
281  }
282  if (is_stderr > 1)
283  return FALSE;
284 
285  buffer = g_malloc(sizeof(*buffer) * len);
286  len = ssh_channel_read(nx->channel, buffer, len, is_stderr);
287  if (len <= 0) {
288  remmina_nx_session_set_application_error(nx, "Channel closed.");
289  return FALSE;
290  }
291 
292  g_string_append_len(nx->response, buffer, len);
293 
294  g_free(buffer);
295  return TRUE;
296 }
297 
299 {
300  TRACE_CALL(__func__);
301  gchar *p1, *p2;
302  gchar *val;
303  gint i;
304  GtkTreeIter iter;
305 
306  p1 = (char*)line;
307  while (*p1 == ' ')
308  p1++;
309  if (*p1 == '\0')
310  return;
311 
312  gtk_list_store_append(nx->session_list, &iter);
313 
314  p1 = (char*)line;
315  for (i = 0; i < 7; i++) {
316  p2 = strchr(p1, ' ');
317  if (!p2)
318  return;
319  val = g_strndup(p1, (gint)(p2 - p1));
320  switch (i) {
321  case 0:
322  gtk_list_store_set(nx->session_list, &iter, REMMINA_NX_SESSION_COLUMN_DISPLAY, val, -1);
323  break;
324  case 1:
325  gtk_list_store_set(nx->session_list, &iter, REMMINA_NX_SESSION_COLUMN_TYPE, val, -1);
326  break;
327  case 2:
328  gtk_list_store_set(nx->session_list, &iter, REMMINA_NX_SESSION_COLUMN_ID, val, -1);
329  break;
330  case 6:
331  gtk_list_store_set(nx->session_list, &iter, REMMINA_NX_SESSION_COLUMN_STATUS, val, -1);
332  break;
333  default:
334  break;
335  }
336  g_free(val);
337 
338  while (*p2 == ' ')
339  p2++;
340  p1 = p2;
341  }
342  /* The last name column might contains space so it’s not in the above loop. We simply rtrim it here. */
343  i = strlen(p1);
344  if (i < 1)
345  return;
346  p2 = p1 + i - 1;
347  while (*p2 == ' ' && p2 > p1)
348  p2--;
349  val = g_strndup(p1, (gint)(p2 - p1 + 1));
350  gtk_list_store_set(nx->session_list, &iter, REMMINA_NX_SESSION_COLUMN_NAME, val, -1);
351  g_free(val);
352 }
353 
354 static gint remmina_nx_session_parse_line(RemminaNXSession *nx, const gchar *line, gchar **valueptr)
355 {
356  TRACE_CALL(__func__);
357  gchar *s;
358  gchar *ptr;
359  gint status;
360 
361  *valueptr = NULL;
362 
363  /* Get the server version from the initial line */
364  if (!nx->version) {
365  s = g_ascii_strdown(line, -1);
366  ptr = strstr(s, nx_hello_server_msg);
367  if (!ptr) {
368  /* Try to use a default version */
369  nx->version = g_strdup("3.3.0");
370  } else {
371  nx->version = g_strdup(ptr + strlen(nx_hello_server_msg));
372  ptr = strchr(nx->version, ' ');
373  if (ptr)
374  *ptr = '\0';
375  /* NoMachine NX append a dash+subversion. Need to be removed. */
376  ptr = strchr(nx->version, '-');
377  if (ptr)
378  *ptr = '\0';
379  }
380  g_free(s);
381  return nx->status;
382  }
383 
384  if (sscanf(line, "NX> %i ", &status) < 1) {
385  if (nx->session_list_state && nx->session_list) {
386  if (nx->session_list_state == 1 && strncmp(line, "----", 4) == 0) {
387  nx->session_list_state = 2;
388  } else if (nx->session_list_state == 2) {
390  }
391  return -1;
392  }
393  return nx->status;
394  }
395 
396  nx->session_list_state = 0;
397  nx->status = status;
398  ptr = strchr(line, ':');
399  if (!ptr)
400  return status;
401  *valueptr = ptr + 2;
402  return status;
403 }
404 
405 static gchar*
407 {
408  TRACE_CALL(__func__);
409  gchar *line;
410  gchar *pos, *ptr;
411  gint len;
412  gint l;
413 
414  if (nx->response_pos >= nx->response->len)
415  return NULL;
416 
417  pos = nx->response->str + nx->response_pos;
418  if ((ptr = strchr(pos, '\n')) == NULL)
419  return NULL;
420 
421  len = ((gint)(ptr - pos)) + 1;
422  line = g_strndup(pos, len - 1);
423 
424  l = strlen(line);
425  if (l > 0 && line[l - 1] == '\r') {
426  line[l - 1] = '\0';
427  }
428 
429  nx->response_pos += len;
430 
431  return line;
432 }
433 
435 {
436  TRACE_CALL(__func__);
437  gchar *line;
438  gchar *pos, *p;
439  gint status = -1;
440 
441  if (nx->response_pos >= nx->response->len)
442  return -1;
443 
444  while ((line = remmina_nx_session_get_line(nx)) != NULL) {
445  if (nx->log_callback)
446  nx->log_callback(line);
447 
448  status = remmina_nx_session_parse_line(nx, line, &p);
449  if (status == 500) {
450  /* 500: Last operation failed. Should be ignored. */
451  } else if (status >= 400 && status <= 599) {
453  } else {
454  switch (status) {
455  case 127: /* Session list */
456  nx->session_list_state = 1;
457  break;
458  case 148: /* Server capacity not reached for user xxx */
459  nx->session_list_state = 0;
460  nx->allow_start = TRUE;
461  break;
462  case 700:
463  nx->session_id = g_strdup(p);
464  break;
465  case 705:
466  nx->session_display = atoi(p);
467  break;
468  case 701:
469  nx->proxy_cookie = g_strdup(p);
470  break;
471  }
472  }
473  g_free(line);
474 
475  nx->status = status;
476  }
477 
478  pos = nx->response->str + nx->response_pos;
479  if (sscanf(pos, "NX> %i ", &status) < 1) {
480  status = nx->status;
481  } else {
482  if (nx->log_callback)
483  nx->log_callback(pos);
484  nx->response_pos += 8;
485  }
486  nx->status = -1;
487  return status;
488 }
489 
490 static gint remmina_nx_session_expect_status2(RemminaNXSession *nx, gint status, gint status2)
491 {
492  TRACE_CALL(__func__);
493  gint response;
494 
495  while ((response = remmina_nx_session_parse_response(nx)) != status && response != status2) {
496  if (response == 999)
497  break;
499  return -1;
500  }
501  nx->session_list_state = 0;
503  return -1;
504  return response;
505 }
506 
508 {
509  TRACE_CALL(__func__);
510  return (remmina_nx_session_expect_status2(nx, status, 0) == status);
511 }
512 
513 static void remmina_nx_session_send_command(RemminaNXSession *nx, const gchar *cmdfmt, ...)
514 {
515  TRACE_CALL(__func__);
516  va_list args;
517  gchar *cmd;
518 
519  va_start(args, cmdfmt);
520  cmd = g_strdup_vprintf(cmdfmt, args);
521  ssh_channel_write(nx->channel, cmd, strlen(cmd));
522  g_free(cmd);
523 
524  ssh_set_fd_towrite(nx->session);
525  ssh_channel_write(nx->channel, "\n", 1);
526  va_end(args);
527 }
528 
529 gboolean remmina_nx_session_open(RemminaNXSession *nx, const gchar *server, guint port, const gchar *private_key_file,
530  RemminaNXPassphraseCallback passphrase_func, gpointer userdata)
531 {
532  TRACE_CALL(__func__);
533  gint ret;
534  ssh_key priv_key;
535  gint keytype;
536  gboolean encrypted;
537  gchar *passphrase = NULL;
538 
539  nx->session = ssh_new();
540  ssh_options_set(nx->session, SSH_OPTIONS_HOST, server);
541  ssh_options_set(nx->session, SSH_OPTIONS_PORT, &port);
542  ssh_options_set(nx->session, SSH_OPTIONS_USER, "nx");
543 
544  if (private_key_file && private_key_file[0]) {
545  if (!remmina_get_keytype(private_key_file, &keytype, &encrypted)) {
546  remmina_nx_session_set_application_error(nx, "Invalid private key file.");
547  return FALSE;
548  }
549  if (encrypted && !passphrase_func(&passphrase, userdata)) {
550  return FALSE;
551  }
552  if ( ssh_pki_import_privkey_file(private_key_file, (passphrase ? passphrase : ""), NULL, NULL, &priv_key) != SSH_OK ) {
553  remmina_nx_session_set_application_error(nx, "Error importing private key from file.");
554  g_free(passphrase);
555  return FALSE;
556  }
557  g_free(passphrase);
558  } else {
559  /* Use NoMachine’s default nx private key */
560  if ( ssh_pki_import_privkey_base64(nx_default_private_key, NULL, NULL, NULL, &priv_key) != SSH_OK ) {
561  remmina_nx_session_set_application_error(nx, "Failed to import NX default private key.");
562  return FALSE;
563  }
564  }
565 
566  if (ssh_connect(nx->session)) {
567  ssh_key_free(priv_key);
568  remmina_nx_session_set_error(nx, "Failed to startup SSH session: %s");
569  return FALSE;
570  }
571 
572  ret = ssh_userauth_publickey(nx->session, NULL, priv_key);
573 
574  ssh_key_free(priv_key);
575 
576  if (ret != SSH_AUTH_SUCCESS) {
577  remmina_nx_session_set_error(nx, "NX SSH authentication failed: %s");
578  return FALSE;
579  }
580 
581  if ((nx->channel = ssh_channel_new(nx->session)) == NULL || ssh_channel_open_session(nx->channel) != SSH_OK) {
582  return FALSE;
583  }
584 
585  if (ssh_channel_request_shell(nx->channel) != SSH_OK) {
586  return FALSE;
587  }
588 
589  /* NX server starts the session with an initial 105 status */
590  if (!remmina_nx_session_expect_status(nx, 105))
591  return FALSE;
592 
593  /* Say hello to the NX server */
594  remmina_nx_session_send_command(nx, "HELLO NXCLIENT - Version %s", nx->version);
595  if (!remmina_nx_session_expect_status(nx, 105))
596  return FALSE;
597 
598  /* Set the NX session environment */
599  remmina_nx_session_send_command(nx, "SET SHELL_MODE SHELL");
600  if (!remmina_nx_session_expect_status(nx, 105))
601  return FALSE;
602  remmina_nx_session_send_command(nx, "SET AUTH_MODE PASSWORD");
603  if (!remmina_nx_session_expect_status(nx, 105))
604  return FALSE;
605 
606  nx->server = g_strdup(server);
607 
608  return TRUE;
609 }
610 
611 gboolean remmina_nx_session_login(RemminaNXSession *nx, const gchar *username, const gchar *password)
612 {
613  TRACE_CALL(__func__);
614  gint response;
615 
616  /* Login to the NX server */
617  remmina_nx_session_send_command(nx, "login");
618  if (!remmina_nx_session_expect_status(nx, 101))
619  return FALSE;
620  remmina_nx_session_send_command(nx, username);
621  /* NoMachine Testdrive does not prompt for password, in which case 105 response is received without 102 */
622  response = remmina_nx_session_expect_status2(nx, 102, 105);
623  if (response == 102) {
624  remmina_nx_session_send_command(nx, password);
625  if (!remmina_nx_session_expect_status(nx, 105))
626  return FALSE;
627  } else if (response != 105) {
628  return FALSE;
629  }
630 
631  return TRUE;
632 }
633 
634 void remmina_nx_session_add_parameter(RemminaNXSession *nx, const gchar *name, const gchar *valuefmt, ...)
635 {
636  TRACE_CALL(__func__);
637  va_list args;
638  gchar *value;
639 
640  va_start(args, valuefmt);
641  value = g_strdup_vprintf(valuefmt, args);
642  g_hash_table_insert(nx->session_parameters, g_strdup(name), value);
643  va_end(args);
644 }
645 
646 static gboolean remmina_nx_session_send_session_command(RemminaNXSession *nx, const gchar *cmd_type, gint response)
647 {
648  TRACE_CALL(__func__);
649  GString *cmd;
650  GHashTableIter iter;
651  gchar *key, *value;
652 
653  cmd = g_string_new(cmd_type);
654  g_hash_table_iter_init(&iter, nx->session_parameters);
655  while (g_hash_table_iter_next(&iter, (gpointer*)&key, (gpointer*)&value)) {
656  g_string_append_printf(cmd, " --%s=\"%s\"", key, value);
657  }
658 
659  remmina_nx_session_send_command(nx, cmd->str);
660  g_string_free(cmd, TRUE);
661 
662  g_hash_table_remove_all(nx->session_parameters);
663 
664  return remmina_nx_session_expect_status(nx, response);
665 }
666 
668 {
669  TRACE_CALL(__func__);
670  gboolean ret;
671 
672  if (nx->session_list == NULL) {
673  nx->session_list = gtk_list_store_new(REMMINA_NX_SESSION_N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
674  G_TYPE_STRING, G_TYPE_STRING);
675  } else {
676  gtk_list_store_clear(nx->session_list);
677  }
678  ret = remmina_nx_session_send_session_command(nx, "listsession", 105);
679 
680  return ret;
681 }
682 
684 {
685  TRACE_CALL(__func__);
686  gtk_tree_view_set_model(tree, GTK_TREE_MODEL(nx->session_list));
687 }
688 
689 gboolean remmina_nx_session_iter_first(RemminaNXSession *nx, GtkTreeIter *iter)
690 {
691  TRACE_CALL(__func__);
692  if (!nx->session_list)
693  return FALSE;
694  return gtk_tree_model_get_iter_first(GTK_TREE_MODEL(nx->session_list), iter);
695 }
696 
697 gboolean remmina_nx_session_iter_next(RemminaNXSession *nx, GtkTreeIter *iter)
698 {
699  TRACE_CALL(__func__);
700  if (!nx->session_list)
701  return FALSE;
702  return gtk_tree_model_iter_next(GTK_TREE_MODEL(nx->session_list), iter);
703 }
704 
705 gchar*
706 remmina_nx_session_iter_get(RemminaNXSession *nx, GtkTreeIter *iter, gint column)
707 {
708  TRACE_CALL(__func__);
709  gchar *val;
710 
711  gtk_tree_model_get(GTK_TREE_MODEL(nx->session_list), iter, column, &val, -1);
712  return val;
713 }
714 
715 void remmina_nx_session_iter_set(RemminaNXSession *nx, GtkTreeIter *iter, gint column, const gchar *data)
716 {
717  TRACE_CALL(__func__);
718  gtk_list_store_set(nx->session_list, iter, column, data, -1);
719 }
720 
722 {
723  TRACE_CALL(__func__);
724  return nx->allow_start;
725 }
726 
728 {
729  TRACE_CALL(__func__);
730  gchar *value;
731 
732  /* Add fixed session parameters for startsession */
733  remmina_nx_session_add_parameter(nx, "cache", "16M");
734  remmina_nx_session_add_parameter(nx, "images", "64M");
735  remmina_nx_session_add_parameter(nx, "render", "1");
736  remmina_nx_session_add_parameter(nx, "backingstore", "1");
737  remmina_nx_session_add_parameter(nx, "agent_server", "");
738  remmina_nx_session_add_parameter(nx, "agent_user", "");
739  remmina_nx_session_add_parameter(nx, "agent_password", "");
740 
741  value = g_strdup_printf("%i", nx->encryption);
742  remmina_nx_session_add_parameter(nx, "encryption", value);
743  g_free(value);
744 }
745 
747 {
748  TRACE_CALL(__func__);
750  return remmina_nx_session_send_session_command(nx, "startsession", 105);
751 }
752 
754 {
755  TRACE_CALL(__func__);
757  return remmina_nx_session_send_session_command(nx, "attachsession", 105);
758 }
759 
761 {
762  TRACE_CALL(__func__);
764  return remmina_nx_session_send_session_command(nx, "restoresession", 105);
765 }
766 
768 {
769  TRACE_CALL(__func__);
770  return remmina_nx_session_send_session_command(nx, "terminate", 105);
771 }
772 
773 static gpointer remmina_nx_session_tunnel_main_thread(gpointer data)
774 {
775  TRACE_CALL(__func__);
776  RemminaNXSession *nx = (RemminaNXSession*)data;
777  gchar *ptr;
778  ssize_t len = 0, lenw = 0;
779  fd_set set;
780  struct timeval timeout;
781  ssh_channel channels[2];
782  ssh_channel channels_out[2];
783  gint sock;
784  gint ret;
785  gchar buffer[10240];
786  gchar socketbuffer[10240];
787  gchar *socketbuffer_ptr = NULL;
788  gint socketbuffer_len = 0;
789 
790  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
791  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
792 
793  /* Accept a local connection */
794  sock = accept(nx->server_sock, NULL, NULL);
795  if (sock < 0) {
796  remmina_nx_session_set_application_error(nx, "Failed to accept local socket");
797  nx->thread = 0;
798  return NULL;
799  }
800  close(nx->server_sock);
801  nx->server_sock = -1;
802 
803  channels[0] = nx->channel;
804  channels[1] = NULL;
805 
806  /* Start the tunnel data transmission */
807  while (nx->running) {
808  timeout.tv_sec = 1;
809  timeout.tv_usec = 0;
810 
811  FD_ZERO(&set);
812  FD_SET(sock, &set);
813 
814  ret = ssh_select(channels, channels_out, sock + 1, &set, &timeout);
815  if (!nx->running)
816  break;
817  if (ret == SSH_EINTR)
818  continue;
819  if (ret == -1)
820  break;
821 
822  if (FD_ISSET(sock, &set)) {
823  len = read(sock, buffer, sizeof(buffer));
824  if (len == 0)
825  nx->running = FALSE;
826  else if (len > 0) {
827  for (ptr = buffer, lenw = 0; len > 0; len -= lenw, ptr += lenw) {
828  ssh_set_fd_towrite(nx->session);
829  lenw = ssh_channel_write(channels[0], (char*)ptr, len);
830  if (lenw <= 0) {
831  nx->running = FALSE;
832  break;
833  }
834  }
835  }
836  }
837 
838  if (!nx->running)
839  break;
840 
841  if (channels_out[0] && socketbuffer_len <= 0) {
842  len = ssh_channel_read_nonblocking(channels_out[0], socketbuffer, sizeof(socketbuffer), 0);
843  if (len == SSH_ERROR || len == SSH_EOF) {
844  nx->running = FALSE;
845  break;
846  } else if (len > 0) {
847  socketbuffer_ptr = socketbuffer;
848  socketbuffer_len = len;
849  } else {
850  /* Clean up the stderr buffer in case FreeNX send something there */
851  len = ssh_channel_read_nonblocking(channels_out[0], buffer, sizeof(buffer), 1);
852  if (len == SSH_ERROR || len == SSH_EOF) {
853  nx->running = FALSE;
854  break;
855  }
856  }
857  }
858 
859  if (nx->running && socketbuffer_len > 0) {
860  for (lenw = 0; socketbuffer_len > 0; socketbuffer_len -= lenw, socketbuffer_ptr += lenw) {
861  lenw = write(sock, socketbuffer_ptr, socketbuffer_len);
862  if (lenw == -1 && errno == EAGAIN && nx->running) {
863  /* Sometimes we cannot write to a socket (always EAGAIN), probably because it’s internal
864  * buffer is full. We need read the pending bytes from the socket first. so here we simply
865  * break, leave the buffer there, and continue with other data */
866  break;
867  }
868  if (lenw <= 0) {
869  nx->running = FALSE;
870  break;
871  }
872  }
873  }
874  }
875 
876  nx->thread = 0;
877 
878  return NULL;
879 }
880 
882 {
883  TRACE_CALL(__func__);
884  gint port;
885  gint sock;
886  gint sockopt = 1;
887  struct sockaddr_in sin;
888 
889  if (!nx->encryption)
890  return TRUE;
891 
893  if (!remmina_nx_session_expect_status(nx, 999)) {
894  /* Shoud not happen, just in case */
895  remmina_nx_session_set_application_error(nx, "Server won’t say bye to us?");
896  return FALSE;
897  }
898 
899  port = (nx->localport ? nx->localport : nx->session_display) + 4000;
900 
901  /* Create the server socket that listens on the local port */
902  sock = socket(AF_INET, SOCK_STREAM, 0);
903  if (sock < 0) {
904  remmina_nx_session_set_application_error(nx, "Failed to create socket.");
905  return FALSE;
906  }
907  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &sockopt, sizeof(sockopt));
908 
909  sin.sin_family = AF_INET;
910  sin.sin_port = htons(port);
911  sin.sin_addr.s_addr = inet_addr("127.0.0.1");
912 
913  if (bind(sock, (struct sockaddr *)&sin, sizeof(sin))) {
914  remmina_nx_session_set_application_error(nx, "Failed to bind on local port.");
915  close(sock);
916  return FALSE;
917  }
918 
919  if (listen(sock, 1)) {
920  remmina_nx_session_set_application_error(nx, "Failed to listen on local port.");
921  close(sock);
922  return FALSE;
923  }
924 
925  nx->server_sock = sock;
926  nx->running = TRUE;
927 
928  if (pthread_create(&nx->thread, NULL, remmina_nx_session_tunnel_main_thread, nx)) {
929  remmina_nx_session_set_application_error(nx, "Failed to initialize pthread.");
930  nx->thread = 0;
931  return FALSE;
932  }
933  return TRUE;
934 }
935 
936 static gchar*
938 {
939  TRACE_CALL(__func__);
940  if (nx->encryption) {
941  return g_strdup_printf("nx,session=%s,cookie=%s,id=%s,shmem=1,shpix=1,connect=127.0.0.1:%i",
942  (gchar*)g_hash_table_lookup(nx->session_parameters, "session"), nx->proxy_cookie,
943  nx->session_id, (nx->localport ? nx->localport : nx->session_display));
944  } else {
945  return g_strdup_printf("nx,session=%s,cookie=%s,id=%s,shmem=1,shpix=1,connect=%s:%i",
946  (gchar*)g_hash_table_lookup(nx->session_parameters, "session"), nx->proxy_cookie,
947  nx->session_id, nx->server, nx->session_display);
948  }
949 }
950 
951 gboolean remmina_nx_session_invoke_proxy(RemminaNXSession *nx, gint display, GChildWatchFunc exit_func, gpointer user_data)
952 {
953  TRACE_CALL(__func__);
954  gchar *argv[50];
955  gint argc;
956  GError *error = NULL;
957  gboolean ret;
958  gchar **envp;
959  gchar *s;
960  gint i;
961 
962  /* Copy all current environment variable, but change DISPLAY. Assume we should always have DISPLAY… */
963  if (display >= 0) {
964  envp = g_listenv();
965  for (i = 0; envp[i]; i++) {
966  if (g_strcmp0(envp[i], "DISPLAY") == 0) {
967  s = g_strdup_printf("DISPLAY=:%i", display);
968  } else {
969  s = g_strdup_printf("%s=%s", envp[i], g_getenv(envp[i]));
970  }
971  g_free(envp[i]);
972  envp[i] = s;
973  }
974  } else {
975  envp = NULL;
976  }
977 
978  argc = 0;
979  argv[argc++] = g_strdup("nxproxy");
980  argv[argc++] = g_strdup("-S");
981  argv[argc++] = remmina_nx_session_get_proxy_option(nx);
982  argv[argc++] = NULL;
983 
984  ret = g_spawn_async(NULL, argv, envp, G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &nx->proxy_pid,
985  &error);
986  g_strfreev(envp);
987  for (i = 0; i < argc; i++)
988  g_free(argv[i]);
989 
990  if (!ret) {
991  remmina_nx_session_set_application_error(nx, "%s", error->message);
992  return FALSE;
993  }
994 
995  if (exit_func) {
996  nx->proxy_watch_source = g_child_watch_add(nx->proxy_pid, exit_func, user_data);
997  }
998 
999  return TRUE;
1000 }
1001 
1003 {
1004  TRACE_CALL(__func__);
1007 }
1008 
gboolean remmina_nx_session_open(RemminaNXSession *nx, const gchar *server, guint port, const gchar *private_key_file, RemminaNXPassphraseCallback passphrase_func, gpointer userdata)
Definition: nx_session.c:529
guint proxy_watch_source
Definition: nx_session.c:125
void remmina_nx_session_set_log_callback(RemminaNXSession *nx, RemminaNXLogCallback log_callback)
Definition: nx_session.c:250
void remmina_nx_session_set_localport(RemminaNXSession *nx, gint localport)
Definition: nx_session.c:244
static gboolean remmina_nx_session_expect_status(RemminaNXSession *nx, gint status)
Definition: nx_session.c:507
void remmina_nx_session_bye(RemminaNXSession *nx)
Definition: nx_session.c:1002
static gint remmina_nx_session_parse_line(RemminaNXSession *nx, const gchar *line, gchar **valueptr)
Definition: nx_session.c:354
gboolean remmina_nx_session_attach(RemminaNXSession *nx)
Definition: nx_session.c:753
gboolean remmina_nx_session_tunnel_open(RemminaNXSession *nx)
Definition: nx_session.c:881
ssh_session session
Definition: nx_session.c:95
gboolean remmina_nx_session_start(RemminaNXSession *nx)
Definition: nx_session.c:746
static gchar * remmina_nx_session_get_line(RemminaNXSession *nx)
Definition: nx_session.c:406
void(* RemminaNXLogCallback)(const gchar *fmt,...)
Definition: nx_session.h:52
static gboolean remmina_get_keytype(const gchar *private_key_file, gint *keytype, gboolean *encrypted)
Definition: nx_session.c:49
gboolean remmina_nx_session_invoke_proxy(RemminaNXSession *nx, gint display, GChildWatchFunc exit_func, gpointer user_data)
Definition: nx_session.c:951
gchar * session_id
Definition: nx_session.c:116
gboolean allow_start
Definition: nx_session.c:120
const gchar * remmina_nx_session_get_error(RemminaNXSession *nx)
Definition: nx_session.c:223
static gboolean remmina_nx_session_send_session_command(RemminaNXSession *nx, const gchar *cmd_type, gint response)
Definition: nx_session.c:646
gchar * proxy_cookie
Definition: nx_session.c:118
GString * response
Definition: nx_session.c:109
static gchar * remmina_nx_session_get_proxy_option(RemminaNXSession *nx)
Definition: nx_session.c:937
static gint remmina_nx_session_expect_status2(RemminaNXSession *nx, gint status, gint status2)
Definition: nx_session.c:490
RemminaNXSession * remmina_nx_session_new(void)
Definition: nx_session.c:129
gboolean(* RemminaNXPassphraseCallback)(gchar **passphrase, gpointer userdata)
Definition: nx_session.h:51
gboolean remmina_nx_session_has_error(RemminaNXSession *nx)
Definition: nx_session.c:216
static void remmina_nx_session_parse_session_list_line(RemminaNXSession *nx, const gchar *line)
Definition: nx_session.c:298
static void remmina_nx_session_add_common_parameters(RemminaNXSession *nx)
Definition: nx_session.c:727
gboolean running
Definition: nx_session.c:103
static const gchar nx_default_private_key[]
Definition: nx_session.c:78
void remmina_nx_session_free(RemminaNXSession *nx)
Definition: nx_session.c:145
static const gchar nx_hello_server_msg[]
Definition: nx_session.c:91
void remmina_nx_session_set_encryption(RemminaNXSession *nx, gint encryption)
Definition: nx_session.c:238
gboolean remmina_nx_session_list(RemminaNXSession *nx)
Definition: nx_session.c:667
RemminaNXLogCallback log_callback
Definition: nx_session.c:99
gboolean remmina_nx_session_iter_next(RemminaNXSession *nx, GtkTreeIter *iter)
Definition: nx_session.c:697
GHashTable * session_parameters
Definition: nx_session.c:107
gboolean remmina_nx_session_allow_start(RemminaNXSession *nx)
Definition: nx_session.c:721
static void remmina_nx_session_send_command(RemminaNXSession *nx, const gchar *cmdfmt,...)
Definition: nx_session.c:513
static gint remmina_nx_session_parse_response(RemminaNXSession *nx)
Definition: nx_session.c:434
void remmina_nx_session_add_parameter(RemminaNXSession *nx, const gchar *name, const gchar *valuefmt,...)
Definition: nx_session.c:634
gchar * remmina_nx_session_iter_get(RemminaNXSession *nx, GtkTreeIter *iter, gint column)
Definition: nx_session.c:706
static gpointer remmina_nx_session_tunnel_main_thread(gpointer data)
Definition: nx_session.c:773
static void remmina_nx_session_set_application_error(RemminaNXSession *nx, const gchar *fmt,...)
Definition: nx_session.c:205
GtkListStore * session_list
Definition: nx_session.c:121
void remmina_nx_session_iter_set(RemminaNXSession *nx, GtkTreeIter *iter, gint column, const gchar *data)
Definition: nx_session.c:715
gboolean remmina_nx_session_terminate(RemminaNXSession *nx)
Definition: nx_session.c:767
void remmina_nx_session_clear_error(RemminaNXSession *nx)
Definition: nx_session.c:229
static void remmina_nx_session_set_error(RemminaNXSession *nx, const gchar *fmt)
Definition: nx_session.c:194
gboolean remmina_nx_session_login(RemminaNXSession *nx, const gchar *username, const gchar *password)
Definition: nx_session.c:611
void remmina_nx_session_set_tree_view(RemminaNXSession *nx, GtkTreeView *tree)
Definition: nx_session.c:683
gboolean remmina_nx_session_iter_first(RemminaNXSession *nx, GtkTreeIter *iter)
Definition: nx_session.c:689
pthread_t thread
Definition: nx_session.c:102
static gboolean remmina_nx_session_get_response(RemminaNXSession *nx)
Definition: nx_session.c:256
gboolean remmina_nx_session_restore(RemminaNXSession *nx)
Definition: nx_session.c:760
ssh_channel channel
Definition: nx_session.c:96