1 /*
2 +----------------------------------------------------------------------+
3 | Copyright (c) The PHP Group |
4 +----------------------------------------------------------------------+
5 | This source file is subject to version 3.01 of the PHP license, |
6 | that is bundled with this package in the file LICENSE, and is |
7 | available through the world-wide-web at the following url: |
8 | https://www.php.net/license/3_01.txt |
9 | If you did not receive a copy of the PHP license and are unable to |
10 | obtain it through the world-wide-web, please send a note to |
11 | license@php.net so we can mail you a copy immediately. |
12 +----------------------------------------------------------------------+
13 | Author: Wez Furlong <wez@thebrainroom.com> |
14 +----------------------------------------------------------------------+
15 */
16
17 #include "php.h"
18 #include <stdio.h>
19 #include <ctype.h>
20 #include <signal.h>
21 #include "php_string.h"
22 #include "ext/standard/head.h"
23 #include "ext/standard/basic_functions.h"
24 #include "ext/standard/file.h"
25 #include "exec.h"
26 #include "php_globals.h"
27 #include "SAPI.h"
28 #include "main/php_network.h"
29 #include "zend_smart_str.h"
30
31 #ifdef HAVE_SYS_WAIT_H
32 #include <sys/wait.h>
33 #endif
34
35 #ifdef HAVE_FCNTL_H
36 #include <fcntl.h>
37 #endif
38
39 /* This symbol is defined in ext/standard/config.m4.
40 * Essentially, it is set if you HAVE_FORK || PHP_WIN32
41 * Other platforms may modify that configure check and add suitable #ifdefs
42 * around the alternate code. */
43 #ifdef PHP_CAN_SUPPORT_PROC_OPEN
44
45 #ifdef HAVE_OPENPTY
46 # ifdef HAVE_PTY_H
47 # include <pty.h>
48 # elif defined(__FreeBSD__)
49 /* FreeBSD defines `openpty` in <libutil.h> */
50 # include <libutil.h>
51 # elif defined(__NetBSD__) || defined(__DragonFly__)
52 /* On recent NetBSD/DragonFlyBSD releases the emalloc, estrdup ... calls had been introduced in libutil */
53 # if defined(__NetBSD__)
54 # include <sys/termios.h>
55 # else
56 # include <termios.h>
57 # endif
58 extern int openpty(int *, int *, char *, struct termios *, struct winsize *);
59 # elif defined(__sun)
60 # include <termios.h>
61 # else
62 /* Mac OS X (and some BSDs) define `openpty` in <util.h> */
63 # include <util.h>
64 # endif
65 #elif defined(__sun)
66 # include <fcntl.h>
67 # include <stropts.h>
68 # include <termios.h>
69 # define HAVE_OPENPTY 1
70
71 /* Solaris before version 11.4 and Illumos do not have any openpty implementation */
openpty(int * master,int * slave,char * name,struct termios * termp,struct winsize * winp)72 int openpty(int *master, int *slave, char *name, struct termios *termp, struct winsize *winp)
73 {
74 int fd, sd;
75 const char *slaveid;
76
77 assert(master);
78 assert(slave);
79
80 sd = *master = *slave = -1;
81 fd = open("/dev/ptmx", O_NOCTTY|O_RDWR);
82 if (fd == -1) {
83 return -1;
84 }
85 /* Checking if we can have to the pseudo terminal */
86 if (grantpt(fd) != 0 || unlockpt(fd) != 0) {
87 goto fail;
88 }
89 slaveid = ptsname(fd);
90 if (!slaveid) {
91 goto fail;
92 }
93
94 /* Getting the slave path and pushing pseudo terminal */
95 sd = open(slaveid, O_NOCTTY|O_RDONLY);
96 if (sd == -1 || ioctl(sd, I_PUSH, "ptem") == -1) {
97 goto fail;
98 }
99 if (termp) {
100 if (tcgetattr(sd, termp) < 0) {
101 goto fail;
102 }
103 }
104 if (winp) {
105 if (ioctl(sd, TIOCSWINSZ, winp) == -1) {
106 goto fail;
107 }
108 }
109
110 *slave = sd;
111 *master = fd;
112 return 0;
113 fail:
114 if (sd != -1) {
115 close(sd);
116 }
117 if (fd != -1) {
118 close(fd);
119 }
120 return -1;
121 }
122 #endif
123
124 #include "proc_open.h"
125
126 static int le_proc_open; /* Resource number for `proc` resources */
127
128 /* {{{ _php_array_to_envp
129 * Process the `environment` argument to `proc_open`
130 * Convert into data structures which can be passed to underlying OS APIs like `exec` on POSIX or
131 * `CreateProcessW` on Win32 */
_php_array_to_envp(zval * environment)132 static php_process_env _php_array_to_envp(zval *environment)
133 {
134 zval *element;
135 php_process_env env;
136 zend_string *key, *str;
137 #ifndef PHP_WIN32
138 char **ep;
139 #endif
140 char *p;
141 size_t sizeenv = 0;
142 HashTable *env_hash; /* temporary PHP array used as helper */
143
144 memset(&env, 0, sizeof(env));
145
146 if (!environment) {
147 return env;
148 }
149
150 uint32_t cnt = zend_hash_num_elements(Z_ARRVAL_P(environment));
151
152 if (cnt < 1) {
153 #ifndef PHP_WIN32
154 env.envarray = (char **) ecalloc(1, sizeof(char *));
155 #endif
156 env.envp = (char *) ecalloc(4, 1);
157 return env;
158 }
159
160 ALLOC_HASHTABLE(env_hash);
161 zend_hash_init(env_hash, cnt, NULL, NULL, 0);
162
163 /* first, we have to get the size of all the elements in the hash */
164 ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(environment), key, element) {
165 str = zval_get_string(element);
166
167 if (ZSTR_LEN(str) == 0) {
168 zend_string_release_ex(str, 0);
169 continue;
170 }
171
172 sizeenv += ZSTR_LEN(str) + 1;
173
174 if (key && ZSTR_LEN(key)) {
175 sizeenv += ZSTR_LEN(key) + 1;
176 zend_hash_add_ptr(env_hash, key, str);
177 } else {
178 zend_hash_next_index_insert_ptr(env_hash, str);
179 }
180 } ZEND_HASH_FOREACH_END();
181
182 #ifndef PHP_WIN32
183 ep = env.envarray = (char **) ecalloc(cnt + 1, sizeof(char *));
184 #endif
185 p = env.envp = (char *) ecalloc(sizeenv + 4, 1);
186
187 ZEND_HASH_FOREACH_STR_KEY_PTR(env_hash, key, str) {
188 #ifndef PHP_WIN32
189 *ep = p;
190 ++ep;
191 #endif
192
193 if (key) {
194 memcpy(p, ZSTR_VAL(key), ZSTR_LEN(key));
195 p += ZSTR_LEN(key);
196 *p++ = '=';
197 }
198
199 memcpy(p, ZSTR_VAL(str), ZSTR_LEN(str));
200 p += ZSTR_LEN(str);
201 *p++ = '\0';
202 zend_string_release_ex(str, 0);
203 } ZEND_HASH_FOREACH_END();
204
205 assert((uint32_t)(p - env.envp) <= sizeenv);
206
207 zend_hash_destroy(env_hash);
208 FREE_HASHTABLE(env_hash);
209
210 return env;
211 }
212 /* }}} */
213
214 /* {{{ _php_free_envp
215 * Free the structures allocated by `_php_array_to_envp` */
_php_free_envp(php_process_env env)216 static void _php_free_envp(php_process_env env)
217 {
218 #ifndef PHP_WIN32
219 if (env.envarray) {
220 efree(env.envarray);
221 }
222 #endif
223 if (env.envp) {
224 efree(env.envp);
225 }
226 }
227 /* }}} */
228
229 /* {{{ proc_open_rsrc_dtor
230 * Free `proc` resource, either because all references to it were dropped or because `pclose` or
231 * `proc_close` were called */
proc_open_rsrc_dtor(zend_resource * rsrc)232 static void proc_open_rsrc_dtor(zend_resource *rsrc)
233 {
234 php_process_handle *proc = (php_process_handle*)rsrc->ptr;
235 #ifdef PHP_WIN32
236 DWORD wstatus;
237 #elif HAVE_SYS_WAIT_H
238 int wstatus;
239 int waitpid_options = 0;
240 pid_t wait_pid;
241 #endif
242
243 /* Close all handles to avoid a deadlock */
244 for (int i = 0; i < proc->npipes; i++) {
245 if (proc->pipes[i] != NULL) {
246 GC_DELREF(proc->pipes[i]);
247 zend_list_close(proc->pipes[i]);
248 proc->pipes[i] = NULL;
249 }
250 }
251
252 /* `pclose_wait` tells us: Are we freeing this resource because `pclose` or `proc_close` were
253 * called? If so, we need to wait until the child process exits, because its exit code is
254 * needed as the return value of those functions.
255 * But if we're freeing the resource because of GC, don't wait. */
256 #ifdef PHP_WIN32
257 if (FG(pclose_wait)) {
258 WaitForSingleObject(proc->childHandle, INFINITE);
259 }
260 GetExitCodeProcess(proc->childHandle, &wstatus);
261 if (wstatus == STILL_ACTIVE) {
262 FG(pclose_ret) = -1;
263 } else {
264 FG(pclose_ret) = wstatus;
265 }
266 CloseHandle(proc->childHandle);
267
268 #elif HAVE_SYS_WAIT_H
269 if (!FG(pclose_wait)) {
270 waitpid_options = WNOHANG;
271 }
272 do {
273 wait_pid = waitpid(proc->child, &wstatus, waitpid_options);
274 } while (wait_pid == -1 && errno == EINTR);
275
276 if (wait_pid <= 0) {
277 FG(pclose_ret) = -1;
278 } else {
279 if (WIFEXITED(wstatus)) {
280 wstatus = WEXITSTATUS(wstatus);
281 }
282 FG(pclose_ret) = wstatus;
283 }
284
285 #else
286 FG(pclose_ret) = -1;
287 #endif
288
289 _php_free_envp(proc->env);
290 efree(proc->pipes);
291 zend_string_release_ex(proc->command, false);
292 efree(proc);
293 }
294 /* }}} */
295
296 /* {{{ PHP_MINIT_FUNCTION(proc_open) */
PHP_MINIT_FUNCTION(proc_open)297 PHP_MINIT_FUNCTION(proc_open)
298 {
299 le_proc_open = zend_register_list_destructors_ex(proc_open_rsrc_dtor, NULL, "process",
300 module_number);
301 return SUCCESS;
302 }
303 /* }}} */
304
305 /* {{{ Kill a process opened by `proc_open` */
PHP_FUNCTION(proc_terminate)306 PHP_FUNCTION(proc_terminate)
307 {
308 zval *zproc;
309 php_process_handle *proc;
310 zend_long sig_no = SIGTERM;
311
312 ZEND_PARSE_PARAMETERS_START(1, 2)
313 Z_PARAM_RESOURCE(zproc)
314 Z_PARAM_OPTIONAL
315 Z_PARAM_LONG(sig_no)
316 ZEND_PARSE_PARAMETERS_END();
317
318 proc = (php_process_handle*)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open);
319 if (proc == NULL) {
320 RETURN_THROWS();
321 }
322
323 #ifdef PHP_WIN32
324 RETURN_BOOL(TerminateProcess(proc->childHandle, 255));
325 #else
326 RETURN_BOOL(kill(proc->child, sig_no) == 0);
327 #endif
328 }
329 /* }}} */
330
331 /* {{{ Close a process opened by `proc_open` */
PHP_FUNCTION(proc_close)332 PHP_FUNCTION(proc_close)
333 {
334 zval *zproc;
335 php_process_handle *proc;
336
337 ZEND_PARSE_PARAMETERS_START(1, 1)
338 Z_PARAM_RESOURCE(zproc)
339 ZEND_PARSE_PARAMETERS_END();
340
341 proc = (php_process_handle*)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open);
342 if (proc == NULL) {
343 RETURN_THROWS();
344 }
345
346 FG(pclose_wait) = 1; /* See comment in `proc_open_rsrc_dtor` */
347 zend_list_close(Z_RES_P(zproc));
348 FG(pclose_wait) = 0;
349 RETURN_LONG(FG(pclose_ret));
350 }
351 /* }}} */
352
353 /* {{{ Get information about a process opened by `proc_open` */
PHP_FUNCTION(proc_get_status)354 PHP_FUNCTION(proc_get_status)
355 {
356 zval *zproc;
357 php_process_handle *proc;
358 #ifdef PHP_WIN32
359 DWORD wstatus;
360 #elif HAVE_SYS_WAIT_H
361 int wstatus;
362 pid_t wait_pid;
363 #endif
364 bool running = 1, signaled = 0, stopped = 0;
365 int exitcode = -1, termsig = 0, stopsig = 0;
366
367 ZEND_PARSE_PARAMETERS_START(1, 1)
368 Z_PARAM_RESOURCE(zproc)
369 ZEND_PARSE_PARAMETERS_END();
370
371 proc = (php_process_handle*)zend_fetch_resource(Z_RES_P(zproc), "process", le_proc_open);
372 if (proc == NULL) {
373 RETURN_THROWS();
374 }
375
376 array_init(return_value);
377 add_assoc_str(return_value, "command", zend_string_copy(proc->command));
378 add_assoc_long(return_value, "pid", (zend_long)proc->child);
379
380 #ifdef PHP_WIN32
381 GetExitCodeProcess(proc->childHandle, &wstatus);
382 running = wstatus == STILL_ACTIVE;
383 exitcode = running ? -1 : wstatus;
384
385 #elif HAVE_SYS_WAIT_H
386 wait_pid = waitpid(proc->child, &wstatus, WNOHANG|WUNTRACED);
387
388 if (wait_pid == proc->child) {
389 if (WIFEXITED(wstatus)) {
390 running = 0;
391 exitcode = WEXITSTATUS(wstatus);
392 }
393 if (WIFSIGNALED(wstatus)) {
394 running = 0;
395 signaled = 1;
396 termsig = WTERMSIG(wstatus);
397 }
398 if (WIFSTOPPED(wstatus)) {
399 stopped = 1;
400 stopsig = WSTOPSIG(wstatus);
401 }
402 } else if (wait_pid == -1) {
403 /* The only error which could occur here is ECHILD, which means that the PID we were
404 * looking for either does not exist or is not a child of this process */
405 running = 0;
406 }
407 #endif
408
409 add_assoc_bool(return_value, "running", running);
410 add_assoc_bool(return_value, "signaled", signaled);
411 add_assoc_bool(return_value, "stopped", stopped);
412 add_assoc_long(return_value, "exitcode", exitcode);
413 add_assoc_long(return_value, "termsig", termsig);
414 add_assoc_long(return_value, "stopsig", stopsig);
415 }
416 /* }}} */
417
418 #ifdef PHP_WIN32
419
420 /* We use this to allow child processes to inherit handles
421 * One static instance can be shared and used for all calls to `proc_open`, since the values are
422 * never changed */
423 SECURITY_ATTRIBUTES php_proc_open_security = {
424 .nLength = sizeof(SECURITY_ATTRIBUTES),
425 .lpSecurityDescriptor = NULL,
426 .bInheritHandle = TRUE
427 };
428
429 # define pipe(pair) (CreatePipe(&pair[0], &pair[1], &php_proc_open_security, 0) ? 0 : -1)
430
431 # define COMSPEC_NT "cmd.exe"
432
dup_handle(HANDLE src,BOOL inherit,BOOL closeorig)433 static inline HANDLE dup_handle(HANDLE src, BOOL inherit, BOOL closeorig)
434 {
435 HANDLE copy, self = GetCurrentProcess();
436
437 if (!DuplicateHandle(self, src, self, ©, 0, inherit, DUPLICATE_SAME_ACCESS |
438 (closeorig ? DUPLICATE_CLOSE_SOURCE : 0)))
439 return NULL;
440 return copy;
441 }
442
dup_fd_as_handle(int fd)443 static inline HANDLE dup_fd_as_handle(int fd)
444 {
445 return dup_handle((HANDLE)_get_osfhandle(fd), TRUE, FALSE);
446 }
447
448 # define close_descriptor(fd) CloseHandle(fd)
449 #else /* !PHP_WIN32 */
450 # define close_descriptor(fd) close(fd)
451 #endif
452
453 /* Determines the type of a descriptor item. */
454 typedef enum _descriptor_type {
455 DESCRIPTOR_TYPE_STD,
456 DESCRIPTOR_TYPE_PIPE,
457 DESCRIPTOR_TYPE_SOCKET
458 } descriptor_type;
459
460 /* One instance of this struct is created for each item in `$descriptorspec` argument to `proc_open`
461 * They are used within `proc_open` and freed before it returns */
462 typedef struct _descriptorspec_item {
463 int index; /* desired FD # in child process */
464 descriptor_type type;
465 php_file_descriptor_t childend; /* FD # opened for use in child
466 * (will be copied to `index` in child) */
467 php_file_descriptor_t parentend; /* FD # opened for use in parent
468 * (for pipes only; will be 0 otherwise) */
469 int mode_flags; /* mode for opening FDs: r/o, r/w, binary (on Win32), etc */
470 } descriptorspec_item;
471
get_valid_arg_string(zval * zv,int elem_num)472 static zend_string *get_valid_arg_string(zval *zv, int elem_num) {
473 zend_string *str = zval_get_string(zv);
474 if (!str) {
475 return NULL;
476 }
477
478 if (strlen(ZSTR_VAL(str)) != ZSTR_LEN(str)) {
479 zend_value_error("Command array element %d contains a null byte", elem_num);
480 zend_string_release(str);
481 return NULL;
482 }
483
484 return str;
485 }
486
487 #ifdef PHP_WIN32
append_backslashes(smart_str * str,size_t num_bs)488 static void append_backslashes(smart_str *str, size_t num_bs)
489 {
490 for (size_t i = 0; i < num_bs; i++) {
491 smart_str_appendc(str, '\\');
492 }
493 }
494
495 const char *special_chars = "()!^\"<>&|%";
496
is_special_character_present(const zend_string * arg)497 static bool is_special_character_present(const zend_string *arg)
498 {
499 for (size_t i = 0; i < ZSTR_LEN(arg); ++i) {
500 if (strchr(special_chars, ZSTR_VAL(arg)[i]) != NULL) {
501 return true;
502 }
503 }
504 return false;
505 }
506
507 /* See https://docs.microsoft.com/en-us/cpp/cpp/parsing-cpp-command-line-arguments and
508 * https://learn.microsoft.com/en-us/archive/blogs/twistylittlepassagesallalike/everyone-quotes-command-line-arguments-the-wrong-way */
append_win_escaped_arg(smart_str * str,zend_string * arg,bool is_cmd_argument)509 static void append_win_escaped_arg(smart_str *str, zend_string *arg, bool is_cmd_argument)
510 {
511 size_t num_bs = 0;
512 bool has_special_character = false;
513
514 if (is_cmd_argument) {
515 has_special_character = is_special_character_present(arg);
516 if (has_special_character) {
517 /* Escape double quote with ^ if executed by cmd.exe. */
518 smart_str_appendc(str, '^');
519 }
520 }
521 smart_str_appendc(str, '"');
522 for (size_t i = 0; i < ZSTR_LEN(arg); ++i) {
523 char c = ZSTR_VAL(arg)[i];
524 if (c == '\\') {
525 num_bs++;
526 continue;
527 }
528
529 if (c == '"') {
530 /* Backslashes before " need to be doubled. */
531 num_bs = num_bs * 2 + 1;
532 }
533 append_backslashes(str, num_bs);
534 if (has_special_character && strchr(special_chars, c) != NULL) {
535 /* Escape special chars with ^ if executed by cmd.exe. */
536 smart_str_appendc(str, '^');
537 }
538 smart_str_appendc(str, c);
539 num_bs = 0;
540 }
541 append_backslashes(str, num_bs * 2);
542 if (has_special_character) {
543 /* Escape double quote with ^ if executed by cmd.exe. */
544 smart_str_appendc(str, '^');
545 }
546 smart_str_appendc(str, '"');
547 }
548
is_executed_by_cmd(const char * prog_name,size_t prog_name_length)549 static bool is_executed_by_cmd(const char *prog_name, size_t prog_name_length)
550 {
551 size_t out_len;
552 WCHAR long_name[MAX_PATH];
553 WCHAR full_name[MAX_PATH];
554 LPWSTR file_part = NULL;
555
556 wchar_t *prog_name_wide = php_win32_cp_conv_any_to_w(prog_name, prog_name_length, &out_len);
557
558 if (GetLongPathNameW(prog_name_wide, long_name, MAX_PATH) == 0) {
559 /* This can fail for example with ERROR_FILE_NOT_FOUND (short path resolution only works for existing files)
560 * in which case we'll pass the path verbatim to the FullPath transformation. */
561 lstrcpynW(long_name, prog_name_wide, MAX_PATH);
562 }
563
564 free(prog_name_wide);
565 prog_name_wide = NULL;
566
567 if (GetFullPathNameW(long_name, MAX_PATH, full_name, &file_part) == 0 || file_part == NULL) {
568 return false;
569 }
570
571 bool uses_cmd = false;
572 if (_wcsicmp(file_part, L"cmd.exe") == 0 || _wcsicmp(file_part, L"cmd") == 0) {
573 uses_cmd = true;
574 } else {
575 const WCHAR *extension_dot = wcsrchr(file_part, L'.');
576 if (extension_dot && (_wcsicmp(extension_dot, L".bat") == 0 || _wcsicmp(extension_dot, L".cmd") == 0)) {
577 uses_cmd = true;
578 }
579 }
580
581 return uses_cmd;
582 }
583
create_win_command_from_args(HashTable * args)584 static zend_string *create_win_command_from_args(HashTable *args)
585 {
586 smart_str str = {0};
587 zval *arg_zv;
588 bool is_prog_name = true;
589 bool is_cmd_execution = false;
590 int elem_num = 0;
591
592 ZEND_HASH_FOREACH_VAL(args, arg_zv) {
593 zend_string *arg_str = get_valid_arg_string(arg_zv, ++elem_num);
594 if (!arg_str) {
595 smart_str_free(&str);
596 return NULL;
597 }
598
599 if (is_prog_name) {
600 is_cmd_execution = is_executed_by_cmd(ZSTR_VAL(arg_str), ZSTR_LEN(arg_str));
601 } else {
602 smart_str_appendc(&str, ' ');
603 }
604
605 append_win_escaped_arg(&str, arg_str, !is_prog_name && is_cmd_execution);
606
607 is_prog_name = 0;
608 zend_string_release(arg_str);
609 } ZEND_HASH_FOREACH_END();
610 smart_str_0(&str);
611 return str.s;
612 }
613
614 /* Get a boolean option from the `other_options` array which can be passed to `proc_open`.
615 * (Currently, all options apply on Windows only.) */
get_option(zval * other_options,char * opt_name,size_t opt_name_len)616 static bool get_option(zval *other_options, char *opt_name, size_t opt_name_len)
617 {
618 HashTable *opt_ary = Z_ARRVAL_P(other_options);
619 zval *item = zend_hash_str_find_deref(opt_ary, opt_name, opt_name_len);
620 return item != NULL &&
621 (Z_TYPE_P(item) == IS_TRUE ||
622 ((Z_TYPE_P(item) == IS_LONG) && Z_LVAL_P(item)));
623 }
624
625 /* Initialize STARTUPINFOW struct, used on Windows when spawning a process.
626 * Ref: https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/ns-processthreadsapi-startupinfow */
init_startup_info(STARTUPINFOW * si,descriptorspec_item * descriptors,int ndesc)627 static void init_startup_info(STARTUPINFOW *si, descriptorspec_item *descriptors, int ndesc)
628 {
629 memset(si, 0, sizeof(STARTUPINFOW));
630 si->cb = sizeof(STARTUPINFOW);
631 si->dwFlags = STARTF_USESTDHANDLES;
632
633 si->hStdInput = GetStdHandle(STD_INPUT_HANDLE);
634 si->hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
635 si->hStdError = GetStdHandle(STD_ERROR_HANDLE);
636
637 /* redirect stdin/stdout/stderr if requested */
638 for (int i = 0; i < ndesc; i++) {
639 switch (descriptors[i].index) {
640 case 0:
641 si->hStdInput = descriptors[i].childend;
642 break;
643 case 1:
644 si->hStdOutput = descriptors[i].childend;
645 break;
646 case 2:
647 si->hStdError = descriptors[i].childend;
648 break;
649 }
650 }
651 }
652
init_process_info(PROCESS_INFORMATION * pi)653 static void init_process_info(PROCESS_INFORMATION *pi)
654 {
655 memset(&pi, 0, sizeof(pi));
656 }
657
convert_command_to_use_shell(wchar_t ** cmdw,size_t cmdw_len)658 static zend_result convert_command_to_use_shell(wchar_t **cmdw, size_t cmdw_len)
659 {
660 size_t len = sizeof(COMSPEC_NT) + sizeof(" /s /c ") + cmdw_len + 3;
661 wchar_t *cmdw_shell = (wchar_t *)malloc(len * sizeof(wchar_t));
662
663 if (cmdw_shell == NULL) {
664 php_error_docref(NULL, E_WARNING, "Command conversion failed");
665 return FAILURE;
666 }
667
668 if (_snwprintf(cmdw_shell, len, L"%hs /s /c \"%s\"", COMSPEC_NT, *cmdw) == -1) {
669 free(cmdw_shell);
670 php_error_docref(NULL, E_WARNING, "Command conversion failed");
671 return FAILURE;
672 }
673
674 free(*cmdw);
675 *cmdw = cmdw_shell;
676
677 return SUCCESS;
678 }
679 #endif
680
681 /* Convert command parameter array passed as first argument to `proc_open` into command string */
get_command_from_array(HashTable * array,char *** argv,int num_elems)682 static zend_string* get_command_from_array(HashTable *array, char ***argv, int num_elems)
683 {
684 zval *arg_zv;
685 zend_string *command = NULL;
686 int i = 0;
687
688 *argv = safe_emalloc(sizeof(char *), num_elems + 1, 0);
689
690 ZEND_HASH_FOREACH_VAL(array, arg_zv) {
691 zend_string *arg_str = get_valid_arg_string(arg_zv, i + 1);
692 if (!arg_str) {
693 /* Terminate with NULL so exit_fail code knows how many entries to free */
694 (*argv)[i] = NULL;
695 if (command != NULL) {
696 zend_string_release_ex(command, false);
697 }
698 return NULL;
699 }
700
701 if (i == 0) {
702 command = zend_string_copy(arg_str);
703 }
704
705 (*argv)[i++] = estrdup(ZSTR_VAL(arg_str));
706 zend_string_release(arg_str);
707 } ZEND_HASH_FOREACH_END();
708
709 (*argv)[i] = NULL;
710 return command;
711 }
712
alloc_descriptor_array(HashTable * descriptorspec)713 static descriptorspec_item* alloc_descriptor_array(HashTable *descriptorspec)
714 {
715 uint32_t ndescriptors = zend_hash_num_elements(descriptorspec);
716 return ecalloc(ndescriptors, sizeof(descriptorspec_item));
717 }
718
get_string_parameter(zval * array,int index,char * param_name)719 static zend_string* get_string_parameter(zval *array, int index, char *param_name)
720 {
721 zval *array_item;
722 if ((array_item = zend_hash_index_find(Z_ARRVAL_P(array), index)) == NULL) {
723 zend_value_error("Missing %s", param_name);
724 return NULL;
725 }
726 return zval_try_get_string(array_item);
727 }
728
set_proc_descriptor_to_blackhole(descriptorspec_item * desc)729 static zend_result set_proc_descriptor_to_blackhole(descriptorspec_item *desc)
730 {
731 #ifdef PHP_WIN32
732 desc->childend = CreateFileA("nul", GENERIC_READ | GENERIC_WRITE,
733 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
734 if (desc->childend == NULL) {
735 php_error_docref(NULL, E_WARNING, "Failed to open nul");
736 return FAILURE;
737 }
738 #else
739 desc->childend = open("/dev/null", O_RDWR);
740 if (desc->childend < 0) {
741 php_error_docref(NULL, E_WARNING, "Failed to open /dev/null: %s", strerror(errno));
742 return FAILURE;
743 }
744 #endif
745 return SUCCESS;
746 }
747
set_proc_descriptor_to_pty(descriptorspec_item * desc,int * master_fd,int * slave_fd)748 static zend_result set_proc_descriptor_to_pty(descriptorspec_item *desc, int *master_fd, int *slave_fd)
749 {
750 #ifdef HAVE_OPENPTY
751 /* All FDs set to PTY in the child process will go to the slave end of the same PTY.
752 * Likewise, all the corresponding entries in `$pipes` in the parent will all go to the master
753 * end of the same PTY.
754 * If this is the first descriptorspec set to 'pty', find an available PTY and get master and
755 * slave FDs. */
756 if (*master_fd == -1) {
757 if (openpty(master_fd, slave_fd, NULL, NULL, NULL)) {
758 php_error_docref(NULL, E_WARNING, "Could not open PTY (pseudoterminal): %s", strerror(errno));
759 return FAILURE;
760 }
761 }
762
763 desc->type = DESCRIPTOR_TYPE_PIPE;
764 desc->childend = dup(*slave_fd);
765 desc->parentend = dup(*master_fd);
766 desc->mode_flags = O_RDWR;
767 return SUCCESS;
768 #else
769 php_error_docref(NULL, E_WARNING, "PTY (pseudoterminal) not supported on this system");
770 return FAILURE;
771 #endif
772 }
773
774 /* Mark the descriptor close-on-exec, so it won't be inherited by children */
make_descriptor_cloexec(php_file_descriptor_t fd)775 static php_file_descriptor_t make_descriptor_cloexec(php_file_descriptor_t fd)
776 {
777 #ifdef PHP_WIN32
778 return dup_handle(fd, FALSE, TRUE);
779 #else
780 #if defined(F_SETFD) && defined(FD_CLOEXEC)
781 fcntl(fd, F_SETFD, FD_CLOEXEC);
782 #endif
783 return fd;
784 #endif
785 }
786
set_proc_descriptor_to_pipe(descriptorspec_item * desc,zend_string * zmode)787 static zend_result set_proc_descriptor_to_pipe(descriptorspec_item *desc, zend_string *zmode)
788 {
789 php_file_descriptor_t newpipe[2];
790
791 if (pipe(newpipe)) {
792 php_error_docref(NULL, E_WARNING, "Unable to create pipe %s", strerror(errno));
793 return FAILURE;
794 }
795
796 desc->type = DESCRIPTOR_TYPE_PIPE;
797
798 if (!zend_string_starts_with_literal(zmode, "w")) {
799 desc->parentend = newpipe[1];
800 desc->childend = newpipe[0];
801 desc->mode_flags = O_WRONLY;
802 } else {
803 desc->parentend = newpipe[0];
804 desc->childend = newpipe[1];
805 desc->mode_flags = O_RDONLY;
806 }
807
808 desc->parentend = make_descriptor_cloexec(desc->parentend);
809
810 #ifdef PHP_WIN32
811 if (ZSTR_LEN(zmode) >= 2 && ZSTR_VAL(zmode)[1] == 'b')
812 desc->mode_flags |= O_BINARY;
813 #endif
814
815 return SUCCESS;
816 }
817
818 #ifdef PHP_WIN32
819 #define create_socketpair(socks) socketpair_win32(AF_INET, SOCK_STREAM, 0, (socks), 0)
820 #else
821 #define create_socketpair(socks) socketpair(AF_UNIX, SOCK_STREAM, 0, (socks))
822 #endif
823
set_proc_descriptor_to_socket(descriptorspec_item * desc)824 static zend_result set_proc_descriptor_to_socket(descriptorspec_item *desc)
825 {
826 php_socket_t sock[2];
827
828 if (create_socketpair(sock)) {
829 zend_string *err = php_socket_error_str(php_socket_errno());
830 php_error_docref(NULL, E_WARNING, "Unable to create socket pair: %s", ZSTR_VAL(err));
831 zend_string_release(err);
832 return FAILURE;
833 }
834
835 desc->type = DESCRIPTOR_TYPE_SOCKET;
836 desc->parentend = make_descriptor_cloexec((php_file_descriptor_t) sock[0]);
837
838 /* Pass sock[1] to child because it will never use overlapped IO on Windows. */
839 desc->childend = (php_file_descriptor_t) sock[1];
840
841 return SUCCESS;
842 }
843
set_proc_descriptor_to_file(descriptorspec_item * desc,zend_string * file_path,zend_string * file_mode)844 static zend_result set_proc_descriptor_to_file(descriptorspec_item *desc, zend_string *file_path,
845 zend_string *file_mode)
846 {
847 php_socket_t fd;
848
849 /* try a wrapper */
850 php_stream *stream = php_stream_open_wrapper(ZSTR_VAL(file_path), ZSTR_VAL(file_mode),
851 REPORT_ERRORS|STREAM_WILL_CAST, NULL);
852 if (stream == NULL) {
853 return FAILURE;
854 }
855
856 /* force into an fd */
857 if (php_stream_cast(stream, PHP_STREAM_CAST_RELEASE|PHP_STREAM_AS_FD, (void **)&fd,
858 REPORT_ERRORS) == FAILURE) {
859 return FAILURE;
860 }
861
862 #ifdef PHP_WIN32
863 desc->childend = dup_fd_as_handle((int)fd);
864 _close((int)fd);
865
866 /* Simulate the append mode by fseeking to the end of the file
867 * This introduces a potential race condition, but it is the best we can do */
868 if (strchr(ZSTR_VAL(file_mode), 'a')) {
869 SetFilePointer(desc->childend, 0, NULL, FILE_END);
870 }
871 #else
872 desc->childend = fd;
873 #endif
874 return SUCCESS;
875 }
876
dup_proc_descriptor(php_file_descriptor_t from,php_file_descriptor_t * to,zend_ulong nindex)877 static zend_result dup_proc_descriptor(php_file_descriptor_t from, php_file_descriptor_t *to,
878 zend_ulong nindex)
879 {
880 #ifdef PHP_WIN32
881 *to = dup_handle(from, TRUE, FALSE);
882 if (*to == NULL) {
883 php_error_docref(NULL, E_WARNING, "Failed to dup() for descriptor " ZEND_LONG_FMT, nindex);
884 return FAILURE;
885 }
886 #else
887 *to = dup(from);
888 if (*to < 0) {
889 php_error_docref(NULL, E_WARNING, "Failed to dup() for descriptor " ZEND_LONG_FMT ": %s",
890 nindex, strerror(errno));
891 return FAILURE;
892 }
893 #endif
894 return SUCCESS;
895 }
896
redirect_proc_descriptor(descriptorspec_item * desc,int target,descriptorspec_item * descriptors,int ndesc,int nindex)897 static zend_result redirect_proc_descriptor(descriptorspec_item *desc, int target,
898 descriptorspec_item *descriptors, int ndesc, int nindex)
899 {
900 php_file_descriptor_t redirect_to = PHP_INVALID_FD;
901
902 for (int i = 0; i < ndesc; i++) {
903 if (descriptors[i].index == target) {
904 redirect_to = descriptors[i].childend;
905 break;
906 }
907 }
908
909 if (redirect_to == PHP_INVALID_FD) { /* Didn't find the index we wanted */
910 if (target < 0 || target > 2) {
911 php_error_docref(NULL, E_WARNING, "Redirection target %d not found", target);
912 return FAILURE;
913 }
914
915 /* Support referring to a stdin/stdout/stderr pipe adopted from the parent,
916 * which happens whenever an explicit override is not provided. */
917 #ifndef PHP_WIN32
918 redirect_to = target;
919 #else
920 switch (target) {
921 case 0: redirect_to = GetStdHandle(STD_INPUT_HANDLE); break;
922 case 1: redirect_to = GetStdHandle(STD_OUTPUT_HANDLE); break;
923 case 2: redirect_to = GetStdHandle(STD_ERROR_HANDLE); break;
924 EMPTY_SWITCH_DEFAULT_CASE()
925 }
926 #endif
927 }
928
929 return dup_proc_descriptor(redirect_to, &desc->childend, nindex);
930 }
931
932 /* Process one item from `$descriptorspec` argument to `proc_open` */
set_proc_descriptor_from_array(zval * descitem,descriptorspec_item * descriptors,int ndesc,int nindex,int * pty_master_fd,int * pty_slave_fd)933 static zend_result set_proc_descriptor_from_array(zval *descitem, descriptorspec_item *descriptors,
934 int ndesc, int nindex, int *pty_master_fd, int *pty_slave_fd) {
935 zend_string *ztype = get_string_parameter(descitem, 0, "handle qualifier");
936 if (!ztype) {
937 return FAILURE;
938 }
939
940 zend_string *zmode = NULL, *zfile = NULL;
941 zend_result retval = FAILURE;
942
943 if (zend_string_equals_literal(ztype, "pipe")) {
944 /* Set descriptor to pipe */
945 zmode = get_string_parameter(descitem, 1, "mode parameter for 'pipe'");
946 if (zmode == NULL) {
947 goto finish;
948 }
949 retval = set_proc_descriptor_to_pipe(&descriptors[ndesc], zmode);
950 } else if (zend_string_equals_literal(ztype, "socket")) {
951 /* Set descriptor to socketpair */
952 retval = set_proc_descriptor_to_socket(&descriptors[ndesc]);
953 } else if (zend_string_equals_literal(ztype, "file")) {
954 /* Set descriptor to file */
955 if ((zfile = get_string_parameter(descitem, 1, "file name parameter for 'file'")) == NULL) {
956 goto finish;
957 }
958 if ((zmode = get_string_parameter(descitem, 2, "mode parameter for 'file'")) == NULL) {
959 goto finish;
960 }
961 retval = set_proc_descriptor_to_file(&descriptors[ndesc], zfile, zmode);
962 } else if (zend_string_equals_literal(ztype, "redirect")) {
963 /* Redirect descriptor to whatever another descriptor is set to */
964 zval *ztarget = zend_hash_index_find_deref(Z_ARRVAL_P(descitem), 1);
965 if (!ztarget) {
966 zend_value_error("Missing redirection target");
967 goto finish;
968 }
969 if (Z_TYPE_P(ztarget) != IS_LONG) {
970 zend_value_error("Redirection target must be of type int, %s given", zend_zval_type_name(ztarget));
971 goto finish;
972 }
973
974 retval = redirect_proc_descriptor(
975 &descriptors[ndesc], (int)Z_LVAL_P(ztarget), descriptors, ndesc, nindex);
976 } else if (zend_string_equals_literal(ztype, "null")) {
977 /* Set descriptor to blackhole (discard all data written) */
978 retval = set_proc_descriptor_to_blackhole(&descriptors[ndesc]);
979 } else if (zend_string_equals_literal(ztype, "pty")) {
980 /* Set descriptor to slave end of PTY */
981 retval = set_proc_descriptor_to_pty(&descriptors[ndesc], pty_master_fd, pty_slave_fd);
982 } else {
983 php_error_docref(NULL, E_WARNING, "%s is not a valid descriptor spec/mode", ZSTR_VAL(ztype));
984 goto finish;
985 }
986
987 finish:
988 if (zmode) zend_string_release(zmode);
989 if (zfile) zend_string_release(zfile);
990 zend_string_release(ztype);
991 return retval;
992 }
993
set_proc_descriptor_from_resource(zval * resource,descriptorspec_item * desc,int nindex)994 static zend_result set_proc_descriptor_from_resource(zval *resource, descriptorspec_item *desc, int nindex)
995 {
996 /* Should be a stream - try and dup the descriptor */
997 php_stream *stream = (php_stream*)zend_fetch_resource(Z_RES_P(resource), "stream",
998 php_file_le_stream());
999 if (stream == NULL) {
1000 return FAILURE;
1001 }
1002
1003 php_socket_t fd;
1004 zend_result status = php_stream_cast(stream, PHP_STREAM_AS_FD, (void **)&fd, REPORT_ERRORS);
1005 if (status == FAILURE) {
1006 return FAILURE;
1007 }
1008
1009 #ifdef PHP_WIN32
1010 php_file_descriptor_t fd_t = (php_file_descriptor_t)_get_osfhandle(fd);
1011 #else
1012 php_file_descriptor_t fd_t = fd;
1013 #endif
1014 return dup_proc_descriptor(fd_t, &desc->childend, nindex);
1015 }
1016
1017 #ifndef PHP_WIN32
close_parentends_of_pipes(descriptorspec_item * descriptors,int ndesc)1018 static zend_result close_parentends_of_pipes(descriptorspec_item *descriptors, int ndesc)
1019 {
1020 /* We are running in child process
1021 * Close the 'parent end' of pipes which were opened before forking/spawning
1022 * Also, dup() the child end of all pipes as necessary so they will use the FD
1023 * number which the user requested */
1024 for (int i = 0; i < ndesc; i++) {
1025 if (descriptors[i].type != DESCRIPTOR_TYPE_STD) {
1026 close(descriptors[i].parentend);
1027 }
1028 if (descriptors[i].childend != descriptors[i].index) {
1029 if (dup2(descriptors[i].childend, descriptors[i].index) < 0) {
1030 php_error_docref(NULL, E_WARNING, "Unable to copy file descriptor %d (for pipe) into " \
1031 "file descriptor %d: %s", descriptors[i].childend, descriptors[i].index, strerror(errno));
1032 return FAILURE;
1033 }
1034 close(descriptors[i].childend);
1035 }
1036 }
1037
1038 return SUCCESS;
1039 }
1040 #endif
1041
close_all_descriptors(descriptorspec_item * descriptors,int ndesc)1042 static void close_all_descriptors(descriptorspec_item *descriptors, int ndesc)
1043 {
1044 for (int i = 0; i < ndesc; i++) {
1045 close_descriptor(descriptors[i].childend);
1046 if (descriptors[i].parentend)
1047 close_descriptor(descriptors[i].parentend);
1048 }
1049 }
1050
efree_argv(char ** argv)1051 static void efree_argv(char **argv)
1052 {
1053 if (argv) {
1054 char **arg = argv;
1055 while (*arg != NULL) {
1056 efree(*arg);
1057 arg++;
1058 }
1059 efree(argv);
1060 }
1061 }
1062
1063 /* {{{ Execute a command, with specified files used for input/output */
PHP_FUNCTION(proc_open)1064 PHP_FUNCTION(proc_open)
1065 {
1066 zend_string *command_str;
1067 HashTable *command_ht;
1068 HashTable *descriptorspec; /* Mandatory argument */
1069 zval *pipes; /* Mandatory argument */
1070 char *cwd = NULL; /* Optional argument */
1071 size_t cwd_len = 0; /* Optional argument */
1072 zval *environment = NULL, *other_options = NULL; /* Optional arguments */
1073
1074 php_process_env env;
1075 int ndesc = 0;
1076 int i;
1077 zval *descitem = NULL;
1078 zend_string *str_index;
1079 zend_ulong nindex;
1080 descriptorspec_item *descriptors = NULL;
1081 #ifdef PHP_WIN32
1082 PROCESS_INFORMATION pi;
1083 HANDLE childHandle;
1084 STARTUPINFOW si;
1085 BOOL newprocok;
1086 DWORD dwCreateFlags = 0;
1087 UINT old_error_mode;
1088 char cur_cwd[MAXPATHLEN];
1089 wchar_t *cmdw = NULL, *cwdw = NULL, *envpw = NULL;
1090 size_t cmdw_len;
1091 bool suppress_errors = 0;
1092 bool bypass_shell = 0;
1093 bool blocking_pipes = 0;
1094 bool create_process_group = 0;
1095 bool create_new_console = 0;
1096 #else
1097 char **argv = NULL;
1098 #endif
1099 int pty_master_fd = -1, pty_slave_fd = -1;
1100 php_process_id_t child;
1101 php_process_handle *proc;
1102
1103 ZEND_PARSE_PARAMETERS_START(3, 6)
1104 Z_PARAM_ARRAY_HT_OR_STR(command_ht, command_str)
1105 Z_PARAM_ARRAY_HT(descriptorspec)
1106 Z_PARAM_ZVAL(pipes)
1107 Z_PARAM_OPTIONAL
1108 Z_PARAM_STRING_OR_NULL(cwd, cwd_len)
1109 Z_PARAM_ARRAY_OR_NULL(environment)
1110 Z_PARAM_ARRAY_OR_NULL(other_options)
1111 ZEND_PARSE_PARAMETERS_END();
1112
1113 memset(&env, 0, sizeof(env));
1114
1115 if (command_ht) {
1116 uint32_t num_elems = zend_hash_num_elements(command_ht);
1117 if (num_elems == 0) {
1118 zend_argument_value_error(1, "must have at least one element");
1119 RETURN_THROWS();
1120 }
1121
1122 #ifdef PHP_WIN32
1123 /* Automatically bypass shell if command is given as an array */
1124 bypass_shell = 1;
1125 command_str = create_win_command_from_args(command_ht);
1126 #else
1127 command_str = get_command_from_array(command_ht, &argv, num_elems);
1128 #endif
1129
1130 if (!command_str) {
1131 #ifndef PHP_WIN32
1132 efree_argv(argv);
1133 #endif
1134 RETURN_FALSE;
1135 }
1136 } else {
1137 zend_string_addref(command_str);
1138 }
1139
1140 #ifdef PHP_WIN32
1141 if (other_options) {
1142 suppress_errors = get_option(other_options, "suppress_errors", strlen("suppress_errors"));
1143 /* TODO: Deprecate in favor of array command? */
1144 bypass_shell = bypass_shell || get_option(other_options, "bypass_shell", strlen("bypass_shell"));
1145 blocking_pipes = get_option(other_options, "blocking_pipes", strlen("blocking_pipes"));
1146 create_process_group = get_option(other_options, "create_process_group", strlen("create_process_group"));
1147 create_new_console = get_option(other_options, "create_new_console", strlen("create_new_console"));
1148 }
1149 #endif
1150
1151 if (environment) {
1152 env = _php_array_to_envp(environment);
1153 }
1154
1155 descriptors = alloc_descriptor_array(descriptorspec);
1156
1157 /* Walk the descriptor spec and set up files/pipes */
1158 ZEND_HASH_FOREACH_KEY_VAL(descriptorspec, nindex, str_index, descitem) {
1159 if (str_index) {
1160 zend_argument_value_error(2, "must be an integer indexed array");
1161 goto exit_fail;
1162 }
1163
1164 descriptors[ndesc].index = (int)nindex;
1165
1166 ZVAL_DEREF(descitem);
1167 if (Z_TYPE_P(descitem) == IS_RESOURCE) {
1168 if (set_proc_descriptor_from_resource(descitem, &descriptors[ndesc], ndesc) == FAILURE) {
1169 goto exit_fail;
1170 }
1171 } else if (Z_TYPE_P(descitem) == IS_ARRAY) {
1172 if (set_proc_descriptor_from_array(descitem, descriptors, ndesc, (int)nindex,
1173 &pty_master_fd, &pty_slave_fd) == FAILURE) {
1174 goto exit_fail;
1175 }
1176 } else {
1177 zend_argument_value_error(2, "must only contain arrays and streams");
1178 goto exit_fail;
1179 }
1180 ndesc++;
1181 } ZEND_HASH_FOREACH_END();
1182
1183 #ifdef PHP_WIN32
1184 if (cwd == NULL) {
1185 char *getcwd_result = VCWD_GETCWD(cur_cwd, MAXPATHLEN);
1186 if (!getcwd_result) {
1187 php_error_docref(NULL, E_WARNING, "Cannot get current directory");
1188 goto exit_fail;
1189 }
1190 cwd = cur_cwd;
1191 }
1192 cwdw = php_win32_cp_any_to_w(cwd);
1193 if (!cwdw) {
1194 php_error_docref(NULL, E_WARNING, "CWD conversion failed");
1195 goto exit_fail;
1196 }
1197
1198 init_startup_info(&si, descriptors, ndesc);
1199 init_process_info(&pi);
1200
1201 if (suppress_errors) {
1202 old_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS|SEM_NOGPFAULTERRORBOX);
1203 }
1204
1205 dwCreateFlags = NORMAL_PRIORITY_CLASS;
1206 if(strcmp(sapi_module.name, "cli") != 0) {
1207 dwCreateFlags |= CREATE_NO_WINDOW;
1208 }
1209 if (create_process_group) {
1210 dwCreateFlags |= CREATE_NEW_PROCESS_GROUP;
1211 }
1212 if (create_new_console) {
1213 dwCreateFlags |= CREATE_NEW_CONSOLE;
1214 }
1215 envpw = php_win32_cp_env_any_to_w(env.envp);
1216 if (envpw) {
1217 dwCreateFlags |= CREATE_UNICODE_ENVIRONMENT;
1218 } else {
1219 if (env.envp) {
1220 php_error_docref(NULL, E_WARNING, "ENV conversion failed");
1221 goto exit_fail;
1222 }
1223 }
1224
1225 cmdw = php_win32_cp_conv_any_to_w(ZSTR_VAL(command_str), ZSTR_LEN(command_str), &cmdw_len);
1226 if (!cmdw) {
1227 php_error_docref(NULL, E_WARNING, "Command conversion failed");
1228 goto exit_fail;
1229 }
1230
1231 if (!bypass_shell) {
1232 if (convert_command_to_use_shell(&cmdw, cmdw_len) == FAILURE) {
1233 goto exit_fail;
1234 }
1235 }
1236 newprocok = CreateProcessW(NULL, cmdw, &php_proc_open_security,
1237 &php_proc_open_security, TRUE, dwCreateFlags, envpw, cwdw, &si, &pi);
1238
1239 if (suppress_errors) {
1240 SetErrorMode(old_error_mode);
1241 }
1242
1243 if (newprocok == FALSE) {
1244 DWORD dw = GetLastError();
1245 close_all_descriptors(descriptors, ndesc);
1246 php_error_docref(NULL, E_WARNING, "CreateProcess failed, error code: %u", dw);
1247 goto exit_fail;
1248 }
1249
1250 childHandle = pi.hProcess;
1251 child = pi.dwProcessId;
1252 CloseHandle(pi.hThread);
1253 #elif HAVE_FORK
1254 /* the Unix way */
1255 child = fork();
1256
1257 if (child == 0) {
1258 /* This is the child process */
1259
1260 if (close_parentends_of_pipes(descriptors, ndesc) == FAILURE) {
1261 /* We are already in child process and can't do anything to make
1262 * `proc_open` return an error in the parent
1263 * All we can do is exit with a non-zero (error) exit code */
1264 _exit(127);
1265 }
1266
1267 if (cwd) {
1268 php_ignore_value(chdir(cwd));
1269 }
1270
1271 if (argv) {
1272 /* execvpe() is non-portable, use environ instead. */
1273 if (env.envarray) {
1274 environ = env.envarray;
1275 }
1276 execvp(ZSTR_VAL(command_str), argv);
1277 } else {
1278 if (env.envarray) {
1279 execle("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL, env.envarray);
1280 } else {
1281 execl("/bin/sh", "sh", "-c", ZSTR_VAL(command_str), NULL);
1282 }
1283 }
1284
1285 /* If execvp/execle/execl are successful, we will never reach here
1286 * Display error and exit with non-zero (error) status code */
1287 php_error_docref(NULL, E_WARNING, "Exec failed: %s", strerror(errno));
1288 _exit(127);
1289 } else if (child < 0) {
1290 /* Failed to fork() */
1291 close_all_descriptors(descriptors, ndesc);
1292 php_error_docref(NULL, E_WARNING, "Fork failed: %s", strerror(errno));
1293 goto exit_fail;
1294 }
1295 #else
1296 # error You lose (configure should not have let you get here)
1297 #endif
1298
1299 /* We forked/spawned and this is the parent */
1300
1301 pipes = zend_try_array_init(pipes);
1302 if (!pipes) {
1303 goto exit_fail;
1304 }
1305
1306 proc = (php_process_handle*) emalloc(sizeof(php_process_handle));
1307 proc->command = zend_string_copy(command_str);
1308 proc->pipes = emalloc(sizeof(zend_resource *) * ndesc);
1309 proc->npipes = ndesc;
1310 proc->child = child;
1311 #ifdef PHP_WIN32
1312 proc->childHandle = childHandle;
1313 #endif
1314 proc->env = env;
1315
1316 /* Clean up all the child ends and then open streams on the parent
1317 * ends, where appropriate */
1318 for (i = 0; i < ndesc; i++) {
1319 php_stream *stream = NULL;
1320
1321 close_descriptor(descriptors[i].childend);
1322
1323 if (descriptors[i].type == DESCRIPTOR_TYPE_PIPE) {
1324 char *mode_string = NULL;
1325
1326 switch (descriptors[i].mode_flags) {
1327 #ifdef PHP_WIN32
1328 case O_WRONLY|O_BINARY:
1329 mode_string = "wb";
1330 break;
1331 case O_RDONLY|O_BINARY:
1332 mode_string = "rb";
1333 break;
1334 #endif
1335 case O_WRONLY:
1336 mode_string = "w";
1337 break;
1338 case O_RDONLY:
1339 mode_string = "r";
1340 break;
1341 case O_RDWR:
1342 mode_string = "r+";
1343 break;
1344 }
1345
1346 #ifdef PHP_WIN32
1347 stream = php_stream_fopen_from_fd(_open_osfhandle((zend_intptr_t)descriptors[i].parentend,
1348 descriptors[i].mode_flags), mode_string, NULL);
1349 php_stream_set_option(stream, PHP_STREAM_OPTION_PIPE_BLOCKING, blocking_pipes, NULL);
1350 #else
1351 stream = php_stream_fopen_from_fd(descriptors[i].parentend, mode_string, NULL);
1352 #endif
1353 } else if (descriptors[i].type == DESCRIPTOR_TYPE_SOCKET) {
1354 stream = php_stream_sock_open_from_socket((php_socket_t) descriptors[i].parentend, NULL);
1355 } else {
1356 proc->pipes[i] = NULL;
1357 }
1358
1359 if (stream) {
1360 zval retfp;
1361
1362 /* nasty hack; don't copy it */
1363 stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
1364
1365 php_stream_to_zval(stream, &retfp);
1366 add_index_zval(pipes, descriptors[i].index, &retfp);
1367
1368 proc->pipes[i] = Z_RES(retfp);
1369 Z_ADDREF(retfp);
1370 }
1371 }
1372
1373 if (1) {
1374 RETVAL_RES(zend_register_resource(proc, le_proc_open));
1375 } else {
1376 exit_fail:
1377 _php_free_envp(env);
1378 RETVAL_FALSE;
1379 }
1380
1381 zend_string_release_ex(command_str, false);
1382 #ifdef PHP_WIN32
1383 free(cwdw);
1384 free(cmdw);
1385 free(envpw);
1386 #else
1387 efree_argv(argv);
1388 #endif
1389 #ifdef HAVE_OPENPTY
1390 if (pty_master_fd != -1) {
1391 close(pty_master_fd);
1392 }
1393 if (pty_slave_fd != -1) {
1394 close(pty_slave_fd);
1395 }
1396 #endif
1397 if (descriptors) {
1398 efree(descriptors);
1399 }
1400 }
1401 /* }}} */
1402
1403 #endif /* PHP_CAN_SUPPORT_PROC_OPEN */
1404