[Automatic update for 2017-01-29]
[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  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License
12  * as published by the Free Software Foundation; either version 2
13  * of the License, or (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24
25 #include <config.h>
26
27 #include <stdio.h>
28 #include <glib.h>
29 #include <string.h>
30
31 #include <wsutil/file_util.h>
32 #include <wsutil/filesystem.h>
33 #ifdef _WIN32
34 #include <wsutil/win32-utils.h>
35 #endif
36
37 #include <log.h>
38
39 #include "extcap.h"
40 #include "extcap_spawn.h"
41
42 #ifdef _WIN32
43
44 void win32_readfrompipe(HANDLE read_pipe, gint32 max_buffer, gchar *buffer)
45 {
46     gboolean bSuccess = FALSE;
47     gint32 bytes_written = 0;
48     gint32 max_bytes = 0;
49
50     DWORD dwRead;
51     DWORD bytes_avail = 0;
52
53     for (;;)
54     {
55         if (!PeekNamedPipe(read_pipe, NULL, 0, NULL, &bytes_avail, NULL)) break;
56         if (bytes_avail <= 0) break;
57
58         max_bytes = max_buffer - bytes_written - 1;
59
60         bSuccess = ReadFile(read_pipe, &buffer[bytes_written], max_bytes, &dwRead, NULL);
61         if (!bSuccess || dwRead == 0) break;
62
63         bytes_written += dwRead;
64         if ((bytes_written + 1) >= max_buffer) break;
65     }
66
67     buffer[bytes_written] = '\0';
68 }
69 #endif
70
71 gboolean extcap_spawn_sync(gchar *dirname, gchar *command, gint argc, gchar **args, gchar **command_output)
72 {
73     gboolean status = FALSE;
74     gboolean result = FALSE;
75     gchar **argv = NULL;
76     gint cnt = 0;
77     gchar *local_output = NULL;
78 #ifdef _WIN32
79
80 #define BUFFER_SIZE 4096
81     gchar buffer[BUFFER_SIZE];
82
83     GString *winargs = g_string_sized_new(200);
84     gchar *quoted_arg;
85     gunichar2 *wcommandline;
86
87     STARTUPINFO info;
88     PROCESS_INFORMATION processInfo;
89
90     SECURITY_ATTRIBUTES sa;
91     HANDLE child_stdout_rd = NULL;
92     HANDLE child_stdout_wr = NULL;
93     HANDLE child_stderr_rd = NULL;
94     HANDLE child_stderr_wr = NULL;
95
96     const gchar *oldpath = g_getenv("PATH");
97     gchar *newpath = NULL;
98 #else
99     gint exit_status = 0;
100 #endif
101
102     argv = (gchar **) g_malloc0(sizeof(gchar *) * (argc + 2));
103
104 #ifdef _WIN32
105     newpath = g_strdup_printf("%s;%s", g_strescape(get_progfile_dir(), NULL), oldpath);
106     g_setenv("PATH", newpath, TRUE);
107
108     argv[0] = g_strescape(command, NULL);
109 #else
110     argv[0] = g_strdup(command);
111 #endif
112
113     for (cnt = 0; cnt < argc; cnt++)
114         argv[cnt + 1] = args[cnt];
115     argv[argc + 1] = NULL;
116
117 #ifdef _WIN32
118
119     sa.nLength = sizeof(SECURITY_ATTRIBUTES);
120     sa.bInheritHandle = TRUE;
121     sa.lpSecurityDescriptor = NULL;
122
123     if (!CreatePipe(&child_stdout_rd, &child_stdout_wr, &sa, 0))
124     {
125         g_free(argv[0]);
126         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Could not create stdout handle");
127         return FALSE;
128     }
129
130     if (!CreatePipe(&child_stderr_rd, &child_stderr_wr, &sa, 0))
131     {
132         CloseHandle(child_stdout_rd);
133         CloseHandle(child_stdout_wr);
134         g_free(argv[0]);
135         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Could not create stderr handle");
136         return FALSE;
137     }
138
139     /* convert args array into a single string */
140     /* XXX - could change sync_pipe_add_arg() instead */
141     /* there is a drawback here: the length is internally limited to 1024 bytes */
142     for (cnt = 0; argv[cnt] != 0; cnt++) {
143         if (cnt != 0) g_string_append_c(winargs, ' ');    /* don't prepend a space before the path!!! */
144         quoted_arg = protect_arg(argv[cnt]);
145         g_string_append(winargs, quoted_arg);
146         g_free(quoted_arg);
147     }
148
149     wcommandline = g_utf8_to_utf16(winargs->str, (glong)winargs->len, NULL, NULL, NULL);
150
151     memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
152     memset(&info, 0, sizeof(STARTUPINFO));
153
154     info.cb = sizeof(STARTUPINFO);
155     info.hStdError = child_stderr_wr;
156     info.hStdOutput = child_stdout_wr;
157     info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
158     info.wShowWindow = SW_HIDE;
159
160     if (CreateProcess(NULL, wcommandline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &info, &processInfo))
161     {
162         WaitForSingleObject(processInfo.hProcess, INFINITE);
163         win32_readfrompipe(child_stdout_rd, BUFFER_SIZE, buffer);
164         local_output = g_strdup_printf("%s", buffer);
165
166         CloseHandle(child_stdout_rd);
167         CloseHandle(child_stdout_wr);
168         CloseHandle(child_stderr_rd);
169         CloseHandle(child_stderr_wr);
170
171         CloseHandle(processInfo.hProcess);
172         CloseHandle(processInfo.hThread);
173         status = TRUE;
174     }
175     else
176         status = FALSE;
177
178     g_setenv("PATH", oldpath, TRUE);
179 #else
180
181     status = g_spawn_sync(dirname, argv, NULL,
182                           (GSpawnFlags) 0, NULL, NULL, &local_output, NULL, &exit_status, NULL);
183
184     if (status && exit_status != 0)
185         status = FALSE;
186 #endif
187
188     if (status)
189     {
190         if (command_output != NULL && local_output != NULL)
191             *command_output = g_strdup(local_output);
192
193         result = TRUE;
194     }
195
196     g_free(local_output);
197     g_free(argv[0]);
198     g_free(argv);
199
200     return result;
201 }
202
203 GPid extcap_spawn_async(extcap_userdata *userdata, GPtrArray *args)
204 {
205     GPid pid = INVALID_EXTCAP_PID;
206
207 #ifdef _WIN32
208     gint cnt = 0;
209     gchar **tmp = NULL;
210
211     GString *winargs = g_string_sized_new(200);
212     gchar *quoted_arg;
213     gunichar2 *wcommandline;
214
215     STARTUPINFO info;
216     PROCESS_INFORMATION processInfo;
217
218     SECURITY_ATTRIBUTES sa;
219     HANDLE child_stdout_rd = NULL;
220     HANDLE child_stdout_wr = NULL;
221     HANDLE child_stderr_rd = NULL;
222     HANDLE child_stderr_wr = NULL;
223
224     const gchar *oldpath = g_getenv("PATH");
225     gchar *newpath = NULL;
226
227     newpath = g_strdup_printf("%s;%s", g_strescape(get_progfile_dir(), NULL), oldpath);
228     g_setenv("PATH", newpath, TRUE);
229
230     sa.nLength = sizeof(SECURITY_ATTRIBUTES);
231     sa.bInheritHandle = TRUE;
232     sa.lpSecurityDescriptor = NULL;
233
234     if (!CreatePipe(&child_stdout_rd, &child_stdout_wr, &sa, 0))
235     {
236         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Could not create stdout handle");
237         return FALSE;
238     }
239
240     if (!CreatePipe(&child_stderr_rd, &child_stderr_wr, &sa, 0))
241     {
242         CloseHandle(child_stdout_rd);
243         CloseHandle(child_stdout_wr);
244         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "Could not create stderr handle");
245         return FALSE;
246     }
247
248     /* convert args array into a single string */
249     /* XXX - could change sync_pipe_add_arg() instead */
250     /* there is a drawback here: the length is internally limited to 1024 bytes */
251     for (tmp = (gchar **)args->pdata, cnt = 0; *tmp && **tmp; ++cnt, ++tmp) {
252         if (cnt != 0) g_string_append_c(winargs, ' ');    /* don't prepend a space before the path!!! */
253         quoted_arg = protect_arg(*tmp);
254         g_string_append(winargs, quoted_arg);
255         g_free(quoted_arg);
256     }
257
258     wcommandline = g_utf8_to_utf16(winargs->str, (glong)winargs->len, NULL, NULL, NULL);
259
260     memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
261     memset(&info, 0, sizeof(STARTUPINFO));
262
263     info.cb = sizeof(STARTUPINFO);
264     info.hStdError = child_stderr_wr;
265     info.hStdOutput = child_stdout_wr;
266     info.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
267     info.wShowWindow = SW_HIDE;
268
269     if (CreateProcess(NULL, wcommandline, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &info, &processInfo))
270     {
271         userdata->extcap_stderr_rd = _open_osfhandle((intptr_t)(child_stderr_rd), _O_BINARY);
272         userdata->extcap_stdout_rd = _open_osfhandle((intptr_t)(child_stdout_rd), _O_BINARY);
273         userdata->threadId = processInfo.hThread;
274         pid = processInfo.hProcess;
275     }
276
277     g_setenv("PATH", oldpath, TRUE);
278 #else
279     g_spawn_async_with_pipes(NULL, (gchar **)args->pdata, NULL,
280                              (GSpawnFlags) G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL,
281                              &pid, NULL, &userdata->extcap_stdout_rd, &userdata->extcap_stderr_rd, NULL);
282 #endif
283
284     userdata->pid = pid;
285
286     return pid;
287 }
288
289 #ifdef _WIN32
290 gboolean
291 extcap_wait_for_pipe(HANDLE pipe_h, HANDLE pid)
292 {
293     DWORD dw;
294     HANDLE handles[2];
295     OVERLAPPED ov;
296     ov.Pointer = 0;
297     gboolean success = FALSE;
298     ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
299
300     ConnectNamedPipe(pipe_h, &ov);
301     handles[0] = ov.hEvent;
302     handles[1] = pid;
303
304     if (GetLastError() == ERROR_PIPE_CONNECTED)
305     {
306         g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "extcap connected to pipe");
307     }
308     else
309     {
310         dw = WaitForMultipleObjects(2, handles, FALSE, 30000);
311         if (dw == WAIT_OBJECT_0)
312         {
313             /* ConnectNamedPipe finished. */
314             DWORD code;
315
316             code = GetLastError();
317             if (code == ERROR_IO_PENDING)
318             {
319                 DWORD dummy;
320                 if (!GetOverlappedResult(ov.hEvent, &ov, &dummy, TRUE))
321                 {
322                     code = GetLastError();
323                 }
324                 else
325                 {
326                     code = ERROR_SUCCESS;
327                     success = TRUE;
328                 }
329             }
330
331             g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "ConnectNamedPipe code: %d", code);
332         }
333         else if (dw == (WAIT_OBJECT_0 + 1))
334         {
335             /* extcap process terminated. */
336             g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "extcap terminated without connecting to pipe!");
337         }
338         else if (dw == WAIT_TIMEOUT)
339         {
340             g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "extcap didn't connect to pipe within 30 seconds!");
341         }
342         else
343         {
344             g_log(LOG_DOMAIN_CAPTURE, G_LOG_LEVEL_DEBUG, "WaitForMultipleObjects returned 0x%08X. Error %d", dw, GetLastError());
345         }
346     }
347
348     CloseHandle(ov.hEvent);
349
350     return success;
351 }
352 #endif
353
354 /*
355  * Editor modelines  -  http://www.wireshark.org/tools/modelines.html
356  *
357  * Local variables:
358  * c-basic-offset: 4
359  * tab-width: 8
360  * indent-tabs-mode: nil
361  * End:
362  *
363  * vi: set shiftwidth=4 tabstop=8 expandtab:
364  * :indentSize=4:tabSize=8:noTabs=true:
365  */