epan: use SPDX indentifiers.
[metze/wireshark/wip.git] / extcap_spawn.c
1 /* extcap_spawn.c
2  *
3  * Routines to spawn extcap external capture programs
4  * Copyright 2016, Roland Knall <rknall@gmail.com>
5  *
6  * Wireshark - Network traffic analyzer
7  * By Gerald Combs <gerald@wireshark.org>
8  * Copyright 1998 Gerald Combs
9  *
10  * SPDX-License-Identifier: GPL-2.0-or-later
11  */
12
13 #include <config.h>
14
15 #include <stdio.h>
16 #include <glib.h>
17 #include <string.h>
18
19 #include <wsutil/file_util.h>
20 #include <wsutil/filesystem.h>
21 #include <wsutil/ws_pipe.h>
22 #ifdef _WIN32
23 #include <wsutil/win32-utils.h>
24 #endif
25
26 #include <log.h>
27
28 #include "extcap.h"
29 #include "extcap_spawn.h"
30
31 gboolean extcap_spawn_sync(gchar *dirname, gchar *command, gint argc, gchar **args, gchar **command_output)
32 {
33     gboolean status = FALSE;
34     gboolean result = FALSE;
35     gchar **argv = NULL;
36     gint cnt = 0;
37     gchar *local_output = NULL;
38 #ifdef _WIN32
39
40 #define BUFFER_SIZE 16384
41
42     GString *winargs = g_string_sized_new(200);
43     gchar *quoted_arg;
44     gunichar2 *wcommandline;
45
46     STARTUPINFO info;
47     PROCESS_INFORMATION processInfo;
48
49     SECURITY_ATTRIBUTES sa;
50     HANDLE child_stdout_rd = NULL;
51     HANDLE child_stdout_wr = NULL;
52     HANDLE child_stderr_rd = NULL;
53     HANDLE child_stderr_wr = NULL;
54
55     const gchar *oldpath = g_getenv("PATH");
56     gchar *newpath = NULL;
57 #else
58     gint exit_status = 0;
59 #endif
60
61     argv = (gchar **) g_malloc0(sizeof(gchar *) * (argc + 2));
62
63 #ifdef _WIN32
64     newpath = g_strdup_printf("%s;%s", g_strescape(get_progfile_dir(), NULL), oldpath);
65     g_setenv("PATH", newpath, TRUE);
66
67     argv[0] = g_strescape(command, NULL);
68 #else
69     argv[0] = g_strdup(command);
70 #endif
71
72     for (cnt = 0; cnt < argc; cnt++)
73         argv[cnt + 1] = args[cnt];
74     argv[argc + 1] = NULL;
75
76 #ifdef _WIN32
77
78     sa.nLength = sizeof(SECURITY_ATTRIBUTES);
79     sa.bInheritHandle = TRUE;
80     sa.lpSecurityDescriptor = NULL;
81
82     if (!CreatePipe(&child_stdout_rd, &child_stdout_wr, &sa, 0))
83     {
84         g_free(argv[0]);
85         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Could not create stdout handle");
86         return FALSE;
87     }
88
89     if (!CreatePipe(&child_stderr_rd, &child_stderr_wr, &sa, 0))
90     {
91         CloseHandle(child_stdout_rd);
92         CloseHandle(child_stdout_wr);
93         g_free(argv[0]);
94         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Could not create stderr handle");
95         return FALSE;
96     }
97
98     /* convert args array into a single string */
99     /* XXX - could change sync_pipe_add_arg() instead */
100     /* there is a drawback here: the length is internally limited to 1024 bytes */
101     for (cnt = 0; argv[cnt] != 0; cnt++) {
102         if (cnt != 0) g_string_append_c(winargs, ' ');    /* don't prepend a space before the path!!! */
103         quoted_arg = protect_arg(argv[cnt]);
104         g_string_append(winargs, quoted_arg);
105         g_free(quoted_arg);
106     }
107
108     wcommandline = g_utf8_to_utf16(winargs->str, (glong)winargs->len, NULL, NULL, NULL);
109
110     memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
111     memset(&info, 0, sizeof(STARTUPINFO));
112
113     info.cb = sizeof(STARTUPINFO);
114     info.hStdError = child_stderr_wr;
115     info.hStdOutput = child_stdout_wr;
116     info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
117     info.wShowWindow = SW_HIDE;
118
119     if (CreateProcess(NULL, wcommandline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &info, &processInfo))
120     {
121         gchar* buffer;
122
123         WaitForSingleObject(processInfo.hProcess, INFINITE);
124         buffer = (gchar*)g_malloc(BUFFER_SIZE);
125         status = ws_read_string_from_pipe(child_stdout_rd, buffer, BUFFER_SIZE);
126         if (status)
127         {
128             local_output = g_strdup_printf("%s", buffer);
129         }
130         g_free(buffer);
131
132         CloseHandle(child_stdout_rd);
133         CloseHandle(child_stdout_wr);
134         CloseHandle(child_stderr_rd);
135         CloseHandle(child_stderr_wr);
136
137         CloseHandle(processInfo.hProcess);
138         CloseHandle(processInfo.hThread);
139     }
140     else
141         status = FALSE;
142
143     g_setenv("PATH", oldpath, TRUE);
144 #else
145
146     status = g_spawn_sync(dirname, argv, NULL,
147                           (GSpawnFlags) 0, NULL, NULL, &local_output, NULL, &exit_status, NULL);
148
149     if (status && exit_status != 0)
150         status = FALSE;
151 #endif
152
153     if (status)
154     {
155         if (command_output != NULL && local_output != NULL)
156             *command_output = g_strdup(local_output);
157
158         result = TRUE;
159     }
160
161     g_free(local_output);
162     g_free(argv[0]);
163     g_free(argv);
164
165     return result;
166 }
167
168 GPid extcap_spawn_async(extcap_userdata *userdata, GPtrArray *args)
169 {
170     GPid pid = INVALID_EXTCAP_PID;
171
172 #ifdef _WIN32
173     gint cnt = 0;
174     gchar **tmp = NULL;
175
176     GString *winargs = g_string_sized_new(200);
177     gchar *quoted_arg;
178     gunichar2 *wcommandline;
179
180     STARTUPINFO info;
181     PROCESS_INFORMATION processInfo;
182
183     SECURITY_ATTRIBUTES sa;
184     HANDLE child_stdout_rd = NULL;
185     HANDLE child_stdout_wr = NULL;
186     HANDLE child_stderr_rd = NULL;
187     HANDLE child_stderr_wr = NULL;
188
189     const gchar *oldpath = g_getenv("PATH");
190     gchar *newpath = NULL;
191
192     newpath = g_strdup_printf("%s;%s", g_strescape(get_progfile_dir(), NULL), oldpath);
193     g_setenv("PATH", newpath, TRUE);
194
195     sa.nLength = sizeof(SECURITY_ATTRIBUTES);
196     sa.bInheritHandle = TRUE;
197     sa.lpSecurityDescriptor = NULL;
198
199     if (!CreatePipe(&child_stdout_rd, &child_stdout_wr, &sa, 0))
200     {
201         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Could not create stdout handle");
202         return FALSE;
203     }
204
205     if (!CreatePipe(&child_stderr_rd, &child_stderr_wr, &sa, 0))
206     {
207         CloseHandle(child_stdout_rd);
208         CloseHandle(child_stdout_wr);
209         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Could not create stderr handle");
210         return FALSE;
211     }
212
213     /* convert args array into a single string */
214     /* XXX - could change sync_pipe_add_arg() instead */
215     /* there is a drawback here: the length is internally limited to 1024 bytes */
216     for (tmp = (gchar **)args->pdata, cnt = 0; *tmp && **tmp; ++cnt, ++tmp) {
217         if (cnt != 0) g_string_append_c(winargs, ' ');    /* don't prepend a space before the path!!! */
218         quoted_arg = protect_arg(*tmp);
219         g_string_append(winargs, quoted_arg);
220         g_free(quoted_arg);
221     }
222
223     wcommandline = g_utf8_to_utf16(winargs->str, (glong)winargs->len, NULL, NULL, NULL);
224
225     memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
226     memset(&info, 0, sizeof(STARTUPINFO));
227
228     info.cb = sizeof(STARTUPINFO);
229     info.hStdError = child_stderr_wr;
230     info.hStdOutput = child_stdout_wr;
231     info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
232     info.wShowWindow = SW_HIDE;
233
234     if (CreateProcess(NULL, wcommandline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &info, &processInfo))
235     {
236         userdata->extcap_stderr_rd = _open_osfhandle((intptr_t)(child_stderr_rd), _O_BINARY);
237         userdata->extcap_stdout_rd = _open_osfhandle((intptr_t)(child_stdout_rd), _O_BINARY);
238         userdata->threadId = processInfo.hThread;
239         pid = processInfo.hProcess;
240     }
241
242     g_setenv("PATH", oldpath, TRUE);
243 #else
244     g_spawn_async_with_pipes(NULL, (gchar **)args->pdata, NULL,
245                              (GSpawnFlags) G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL,
246                              &pid, NULL, &userdata->extcap_stdout_rd, &userdata->extcap_stderr_rd, NULL);
247 #endif
248
249     userdata->pid = pid;
250
251     return pid;
252 }
253
254 #ifdef _WIN32
255
256 typedef struct
257 {
258     HANDLE pipeHandle;
259     OVERLAPPED ol;
260     BOOL pendingIO;
261 } PIPEINTS;
262
263 gboolean
264 extcap_wait_for_pipe(HANDLE * pipe_handles, int num_pipe_handles, HANDLE pid)
265 {
266     PIPEINTS pipeinsts[3];
267     DWORD dw, cbRet;
268     HANDLE handles[4];
269     int error_code;
270     int num_waiting_to_connect = 0;
271     int num_handles = num_pipe_handles + 1; // PID handle is also added to list of handles.
272
273     if (num_pipe_handles == 0 || num_pipe_handles > 3)
274     {
275         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Invalid number of pipes given as argument.");
276         return FALSE;
277     }
278
279     for (int i = 0; i < num_pipe_handles; ++i)
280     {
281         pipeinsts[i].pipeHandle = pipe_handles[i];
282         pipeinsts[i].ol.Pointer = 0;
283         pipeinsts[i].ol.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
284         pipeinsts[i].pendingIO = FALSE;
285         handles[i] = pipeinsts[i].ol.hEvent;
286         BOOL connected = ConnectNamedPipe(pipeinsts[i].pipeHandle, &pipeinsts[i].ol);
287         if (connected)
288         {
289             error_code = GetLastError();
290             g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "ConnectNamedPipe failed with %d \n.", error_code);
291             return FALSE;
292         }
293
294         switch (GetLastError())
295         {
296         case ERROR_IO_PENDING:
297             num_waiting_to_connect++;
298             pipeinsts[i].pendingIO = TRUE;
299             break;
300
301         case ERROR_PIPE_CONNECTED:
302             if (SetEvent(pipeinsts[i].ol.hEvent))
303             {
304                 break;
305             } // Fallthrough if this fails.
306
307         default:
308             error_code = GetLastError();
309             g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "ConnectNamedPipe failed with %d \n.", error_code);
310             return FALSE;
311         }
312     }
313
314     // Store pid of extcap process so it can be monitored in case it fails before the pipes has connceted.
315     handles[num_pipe_handles] = pid;
316
317     while(num_waiting_to_connect > 0)
318     {
319         dw = WaitForMultipleObjects(num_handles, handles, FALSE, 30000);
320         int idx = dw - WAIT_OBJECT_0;
321         if (dw == WAIT_TIMEOUT)
322         {
323             g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "extcap didn't connect to pipe within 30 seconds.");
324             return FALSE;
325         }
326         // If index points to our handles array
327         else if (idx >= 0 && idx < num_handles)
328         {
329             if (idx < num_pipe_handles)  // Index of pipe handle
330             {
331                 if (pipeinsts[idx].pendingIO)
332                 {
333                     BOOL success = GetOverlappedResult(
334                         pipeinsts[idx].pipeHandle, // handle to pipe
335                         &pipeinsts[idx].ol,        // OVERLAPPED structure
336                         &cbRet,                    // bytes transferred
337                         FALSE);                    // do not wait
338
339                     if (!success)
340                     {
341                         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Error %d \n.", GetLastError());
342                         return FALSE;
343                     }
344                     else
345                     {
346                         pipeinsts[idx].pendingIO = FALSE;
347                         CloseHandle(pipeinsts[idx].ol.hEvent);
348                         num_waiting_to_connect--;
349                     }
350                 }
351             }
352             else // Index of PID
353             {
354                 // Fail since index of 'pid' indicates that the pid of the extcap process has terminated.
355                 g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "extcap terminated without connecting to pipe.");
356                 return FALSE;
357             }
358         }
359         else
360         {
361             g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "WaitForMultipleObjects returned 0x%08X. Error %d", dw, GetLastError());
362             return FALSE;
363         }
364     }
365
366     return TRUE;
367 }
368 #endif
369
370 /*
371  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
372  *
373  * Local variables:
374  * c-basic-offset: 4
375  * tab-width: 8
376  * indent-tabs-mode: nil
377  * End:
378  *
379  * vi: set shiftwidth=4 tabstop=8 expandtab:
380  * :indentSize=4:tabSize=8:noTabs=true:
381  */