Remmina - The GTK+ Remote Desktop Client  v1.4.33
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.
exec_plugin.c
Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2017-2023 Antenore Gatta, Giovanni Panozzo
4  *
5  * Initially based on the plugin "Remmina Plugin EXEC", created and written by
6  * Fabio Castelli (Muflone) <muflone@vbsimple.net>.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  *
23  * In addition, as a special exception, the copyright holders give
24  * permission to link the code of portions of this program with the
25  * OpenSSL library under certain conditions as described in each
26  * individual source file, and distribute linked combinations
27  * including the two.
28  * You must obey the GNU General Public License in all respects
29  * for all of the code used other than OpenSSL. * If you modify
30  * file(s) with this exception, you may extend this exception to your
31  * version of the file(s), but you are not obligated to do so. * If you
32  * do not wish to do so, delete this exception statement from your
33  * version. * If you delete this exception statement from all source
34  * files in the program, then also delete it here.
35  *
36  */
37 
38 #include "exec_plugin_config.h"
39 
40 #include "common/remmina_plugin.h"
41 
42 #include <gdk/gdkkeysyms.h>
43 #include <gtk/gtk.h>
44 #include <glib.h>
45 #include <stdlib.h>
46 #include <sys/wait.h>
47 #include <unistd.h>
48 
49 #define GET_PLUGIN_DATA(gp) (RemminaPluginExecData*)g_object_get_data(G_OBJECT(gp), "plugin-data")
50 
51 typedef struct _RemminaPluginExecData {
52  GtkWidget *log_view;
53  GtkTextBuffer *log_buffer;
54  GtkTextBuffer *err;
55  GtkWidget *sw;
56  GPid pid;
58 
60 #define REMMINA_PLUGIN_DEBUG(fmt, ...) remmina_plugin_service->_remmina_debug(__func__, fmt, ##__VA_ARGS__)
61 
62 
63  static void
64 cb_child_watch( GPid pid, gint status)
65 {
66  /* Close pid */
67  g_spawn_close_pid( pid );
68 }
69 
70 static void cb_child_setup(gpointer data){
71  int pid = getpid();
72  setpgid(pid, 0);
73 }
74 
75  static gboolean
76 cb_out_watch (GIOChannel *channel, GIOCondition cond, RemminaProtocolWidget *gp)
77 {
78  gchar *string;
79  gsize size;
80 
81  RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp);
82 
83  if( cond == G_IO_HUP )
84  {
85  g_io_channel_unref( channel );
86  return FALSE;
87  }
88 
89  g_io_channel_read_line( channel, &string, &size, NULL, NULL );
90  gtk_text_buffer_insert_at_cursor( gpdata->log_buffer, string, -1 );
91  g_free( string );
92 
93  return TRUE;
94 }
95 
96  static gboolean
97 cb_err_watch (GIOChannel *channel, GIOCondition cond, RemminaProtocolWidget *gp)
98 {
99  gchar *string;
100  gsize size;
101 
102  RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp);
103 
104  if( cond == G_IO_HUP )
105  {
106  g_io_channel_unref( channel );
107  return FALSE;
108  }
109 
110  g_io_channel_read_line( channel, &string, &size, NULL, NULL );
111  gtk_text_buffer_insert_at_cursor( gpdata->err, string, -1 );
112  g_free( string );
113 
114  return TRUE;
115 }
116 
118 {
119  TRACE_CALL(__func__);
120  RemminaPluginExecData *gpdata;
121 
122  REMMINA_PLUGIN_DEBUG("[%s] Plugin init", PLUGIN_NAME);
123 
124  gpdata = g_new0(RemminaPluginExecData, 1);
125  g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
126 
127  gpdata->pid = 0;
128  gpdata->log_view = gtk_text_view_new();
129  gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(gpdata->log_view), GTK_WRAP_CHAR);
130  gtk_text_view_set_editable(GTK_TEXT_VIEW(gpdata->log_view), FALSE);
131  gtk_text_view_set_left_margin (GTK_TEXT_VIEW (gpdata->log_view), 20);
132  gtk_text_view_set_right_margin (GTK_TEXT_VIEW (gpdata->log_view), 20);
133  gpdata->log_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (gpdata->log_view));
134  gpdata->sw = gtk_scrolled_window_new (NULL, NULL);
135  gtk_widget_set_size_request (gpdata->sw, 640, 480);
136  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (gpdata->sw),
137  GTK_POLICY_AUTOMATIC,
138  GTK_POLICY_AUTOMATIC);
139  gtk_container_add(GTK_CONTAINER(gp), gpdata->sw);
140  gtk_container_add(GTK_CONTAINER(gpdata->sw), gpdata->log_view);
141  gtk_text_buffer_set_text (gpdata->log_buffer, "Remmina Exec Plugin Logger", -1);
142 
143  gtk_widget_show_all(gpdata->sw);
144 }
145 
147 {
148  TRACE_CALL(__func__);
149  RemminaFile* remminafile;
150  const gchar *cmd;
151  gchar *stdout_buffer;
152  gchar *stderr_buffer;
153  char **argv;
154  GError *error = NULL;
155  GPid child_pid;
156  gint child_stdout, child_stderr;
157  GtkDialog *dialog;
158  GIOChannel *out_ch, *err_ch;
159 
160  REMMINA_PLUGIN_DEBUG("[%s] Plugin run", PLUGIN_NAME);
161  RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp);
162  remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
163 
164  cmd = remmina_plugin_service->file_get_string(remminafile, "execcommand");
165  if (!cmd) {
166  gtk_text_buffer_set_text (gpdata->log_buffer,
167  _("You did not set any command to be executed"), -1);
168  remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);
169  return TRUE;
170  }
171 
172  g_shell_parse_argv(cmd, NULL, &argv, &error);
173  if (error) {
174  gtk_text_buffer_set_text (gpdata->log_buffer, error->message, -1);
175  remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);
176  g_error_free(error);
177  return TRUE;
178  }
179 
180  if (remmina_plugin_service->file_get_int(remminafile, "runasync", FALSE)) {
181  REMMINA_PLUGIN_DEBUG("[%s] Run Async", PLUGIN_NAME);
182  g_spawn_async_with_pipes( NULL,
183  argv,
184  NULL,
185  G_SPAWN_DO_NOT_REAP_CHILD |
186  G_SPAWN_SEARCH_PATH_FROM_ENVP |
187  G_SPAWN_SEARCH_PATH,
189  NULL,
190  &child_pid,
191  NULL,
192  &child_stdout,
193  &child_stderr,
194  &error);
195  if (error != NULL) {
196  gtk_text_buffer_set_text (gpdata->log_buffer, error->message, -1);
197  g_error_free(error);
198  remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);
199  return TRUE;
200  }
201  g_child_watch_add(child_pid, (GChildWatchFunc)cb_child_watch, gp );
202 
203  gpdata->pid = child_pid;
204  /* Create channels that will be used to read data from pipes. */
205  out_ch = g_io_channel_unix_new(child_stdout);
206  err_ch = g_io_channel_unix_new(child_stderr);
207  /* Add watches to channels */
208  g_io_add_watch(out_ch, G_IO_IN | G_IO_HUP, (GIOFunc)cb_out_watch, gp );
209  g_io_add_watch(err_ch, G_IO_IN | G_IO_HUP, (GIOFunc)cb_err_watch, gp );
210 
211  }else {
212  dialog = GTK_DIALOG(gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL,
213  GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
214  _("Warning: Running a command synchronously may cause Remmina not to respond.\nDo you really want to continue?")));
215  gint result = gtk_dialog_run (GTK_DIALOG (dialog));
216 
217  switch (result)
218  {
219  case GTK_RESPONSE_YES:
220  break;
221  default:
222  gtk_widget_destroy(GTK_WIDGET(dialog));
223  return FALSE;
224  break;
225  }
226  gtk_widget_destroy(GTK_WIDGET(dialog));
227  REMMINA_PLUGIN_DEBUG("[%s] Run Sync", PLUGIN_NAME);
228  g_spawn_sync (NULL, // CWD or NULL
229  argv,
230  NULL, // ENVP or NULL
231  G_SPAWN_SEARCH_PATH |
232  G_SPAWN_SEARCH_PATH_FROM_ENVP,
233  NULL,
234  NULL,
235  &stdout_buffer, // STDOUT
236  &stderr_buffer, // STDERR
237  NULL, // Exit status
238  &error);
239  if (!error) {
240  REMMINA_PLUGIN_DEBUG("[%s] Command executed", PLUGIN_NAME);
241  gtk_text_buffer_set_text (gpdata->log_buffer, stdout_buffer, -1);
242  }else {
243  g_warning("Command %s exited with error: %s\n", cmd, error->message);
244  gtk_text_buffer_set_text (gpdata->log_buffer, error->message, -1);
245  g_error_free(error);
246  }
247  }
248 
249  g_strfreev(argv);
250 
251  remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);
252  return TRUE;
253 }
254 
256 {
257  TRACE_CALL(__func__);
258  REMMINA_PLUGIN_DEBUG("[%s] Plugin close", PLUGIN_NAME);
259  RemminaPluginExecData *gpdata = GET_PLUGIN_DATA(gp);
260  //if async process was started, make sure it's dead if option is selected
261  RemminaFile* remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
262  if (remmina_plugin_service->file_get_int(remminafile, "kill_proc", FALSE)) {
263  if (gpdata->pid !=0 ){
264  int pgid = getpgid(gpdata->pid);
265  if (pgid != 0){
266  kill(-gpdata->pid, SIGHUP);
267  }
268  else{
269  kill(gpdata->pid, SIGHUP);
270  }
271 
272  }
273  }
274  remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
275  return FALSE;
276 }
277 
278 /* Array of RemminaProtocolSetting for basic settings.
279  * Each item is composed by:
280  * a) RemminaProtocolSettingType for setting type
281  * b) Setting name
282  * c) Setting description
283  * d) Compact disposition
284  * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
285  * f) Setting tooltip
286  * g) Validation data pointer, will be passed to the validation callback method.
287  * h) Validation callback method (Can be NULL. Every entry will be valid then.)
288  * use following prototype:
289  * gboolean mysetting_validator_method(gpointer key, gpointer value,
290  * gpointer validator_data);
291  * gpointer key is a gchar* containing the setting's name,
292  * gpointer value contains the value which should be validated,
293  * gpointer validator_data contains your passed data.
294  */
296 {
297  { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "execcommand", N_("Command"), FALSE, NULL, NULL, NULL, NULL },
298  { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "runasync", N_("Asynchronous execution"), FALSE, NULL, NULL, NULL, NULL },
299  { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "kill_proc", N_("Kill process on disconnect"), FALSE, NULL, NULL, NULL, NULL },
300  { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL, NULL, NULL }
301 };
302 
303 /* Protocol plugin definition and features */
306  PLUGIN_NAME, // Name
307  PLUGIN_DESCRIPTION, // Description
308  GETTEXT_PACKAGE, // Translation domain
309  PLUGIN_VERSION, // Version number
310  PLUGIN_APPICON, // Icon for normal connection
311  PLUGIN_APPICON, // Icon for SSH connection
312  remmina_plugin_exec_basic_settings, // Array for basic settings
313  NULL, // Array for advanced settings
314  REMMINA_PROTOCOL_SSH_SETTING_NONE, // SSH settings type
315  NULL, // Array for available features
316  remmina_plugin_exec_init, // Plugin initialization
317  remmina_plugin_exec_run, // Plugin open connection
318  remmina_plugin_exec_close, // Plugin close connection
319  NULL, // Query for available features
320  NULL, // Call a feature
321  NULL, // Send a keystroke
322  NULL, // No screenshot support available
323  NULL, // RCW map event
324  NULL // RCW unmap event
325 };
326 
327 G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service)
328 {
329  TRACE_CALL(__func__);
330  remmina_plugin_service = service;
331 
332  bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
333  bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
334 
335  if (!service->register_plugin((RemminaPlugin*)&remmina_plugin)) {
336  return FALSE;
337  }
338 
339  return TRUE;
340 }
static const RemminaProtocolSetting remmina_plugin_exec_basic_settings[]
Definition: exec_plugin.c:295
static void cb_child_watch(GPid pid, gint status)
Definition: exec_plugin.c:64
typedefG_BEGIN_DECLS struct _RemminaFile RemminaFile
Definition: types.h:44
GtkTextBuffer * err
Definition: exec_plugin.c:54
static RemminaProtocolPlugin remmina_plugin
Definition: exec_plugin.c:304
static void remmina_plugin_exec_init(RemminaProtocolWidget *gp)
Definition: exec_plugin.c:117
struct _RemminaPluginExecData RemminaPluginExecData
static gboolean remmina_plugin_exec_run(RemminaProtocolWidget *gp)
Definition: exec_plugin.c:146
void(* protocol_plugin_signal_connection_closed)(RemminaProtocolWidget *gp)
Definition: plugin.h:185
gint(* file_get_int)(RemminaFile *remminafile, const gchar *setting, gint default_value)
Definition: plugin.h:222
void(* protocol_plugin_signal_connection_opened)(RemminaProtocolWidget *gp)
Definition: plugin.h:186
gboolean(* register_plugin)(RemminaPlugin *plugin)
Definition: plugin.h:166
static gboolean remmina_plugin_exec_close(RemminaProtocolWidget *gp)
Definition: exec_plugin.c:255
GtkWidget * log_view
Definition: exec_plugin.c:52
RemminaFile *(* protocol_plugin_get_file)(RemminaProtocolWidget *gp)
Definition: plugin.h:178
static void cb_child_setup(gpointer data)
Definition: exec_plugin.c:70
const gchar *(* file_get_string)(RemminaFile *remminafile, const gchar *setting)
Definition: plugin.h:219
static gboolean cb_err_watch(GIOChannel *channel, GIOCondition cond, RemminaProtocolWidget *gp)
Definition: exec_plugin.c:97
GtkTextBuffer * log_buffer
Definition: exec_plugin.c:53
N_("Unable to connect to VNC server")
Definition: vnc_plugin.c:953
static gboolean cb_out_watch(GIOChannel *channel, GIOCondition cond, RemminaProtocolWidget *gp)
Definition: exec_plugin.c:76
G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service)
Definition: exec_plugin.c:327
static RemminaPluginService * remmina_plugin_service
Definition: exec_plugin.c:59