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