1/*
2   +----------------------------------------------------------------------+
3   | PHP Version 5                                                        |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2014 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.01 of the PHP license,      |
8   | that is bundled with this package in the file LICENSE, and is        |
9   | available through the world-wide-web at the following url:           |
10   | http://www.php.net/license/3_01.txt                                  |
11   | If you did not receive a copy of the PHP license and are unable to   |
12   | obtain it through the world-wide-web, please send a note to          |
13   | license@php.net so we can mail you a copy immediately.               |
14   +----------------------------------------------------------------------+
15   | Author: Rasmus Lerdorf <rasmus@php.net>                              |
16   |         Ilia Alshanetsky <iliaa@php.net>                             |
17   +----------------------------------------------------------------------+
18 */
19/* $Id$ */
20
21#include <stdio.h>
22#include "php.h"
23#include <ctype.h>
24#include "php_string.h"
25#include "ext/standard/head.h"
26#include "ext/standard/file.h"
27#include "basic_functions.h"
28#include "exec.h"
29#include "php_globals.h"
30#include "SAPI.h"
31
32#if HAVE_SYS_WAIT_H
33#include <sys/wait.h>
34#endif
35#if HAVE_SIGNAL_H
36#include <signal.h>
37#endif
38
39#if HAVE_SYS_TYPES_H
40#include <sys/types.h>
41#endif
42#if HAVE_SYS_STAT_H
43#include <sys/stat.h>
44#endif
45#if HAVE_FCNTL_H
46#include <fcntl.h>
47#endif
48
49#if HAVE_NICE && HAVE_UNISTD_H
50#include <unistd.h>
51#endif
52
53/* {{{ php_exec
54 * If type==0, only last line of output is returned (exec)
55 * If type==1, all lines will be printed and last lined returned (system)
56 * If type==2, all lines will be saved to given array (exec with &$array)
57 * If type==3, output will be printed binary, no lines will be saved or returned (passthru)
58 *
59 */
60PHPAPI int php_exec(int type, char *cmd, zval *array, zval *return_value TSRMLS_DC)
61{
62    FILE *fp;
63    char *buf;
64    int l = 0, pclose_return;
65    char *b, *d=NULL;
66    php_stream *stream;
67    size_t buflen, bufl = 0;
68#if PHP_SIGCHILD
69    void (*sig_handler)() = NULL;
70#endif
71
72#if PHP_SIGCHILD
73    sig_handler = signal (SIGCHLD, SIG_DFL);
74#endif
75
76#ifdef PHP_WIN32
77    fp = VCWD_POPEN(cmd, "rb");
78#else
79    fp = VCWD_POPEN(cmd, "r");
80#endif
81    if (!fp) {
82        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to fork [%s]", cmd);
83        goto err;
84    }
85
86    stream = php_stream_fopen_from_pipe(fp, "rb");
87
88    buf = (char *) emalloc(EXEC_INPUT_BUF);
89    buflen = EXEC_INPUT_BUF;
90
91    if (type != 3) {
92        b = buf;
93
94        while (php_stream_get_line(stream, b, EXEC_INPUT_BUF, &bufl)) {
95            /* no new line found, let's read some more */
96            if (b[bufl - 1] != '\n' && !php_stream_eof(stream)) {
97                if (buflen < (bufl + (b - buf) + EXEC_INPUT_BUF)) {
98                    bufl += b - buf;
99                    buflen = bufl + EXEC_INPUT_BUF;
100                    buf = erealloc(buf, buflen);
101                    b = buf + bufl;
102                } else {
103                    b += bufl;
104                }
105                continue;
106            } else if (b != buf) {
107                bufl += b - buf;
108            }
109
110            if (type == 1) {
111                PHPWRITE(buf, bufl);
112                if (php_output_get_level(TSRMLS_C) < 1) {
113                    sapi_flush(TSRMLS_C);
114                }
115            } else if (type == 2) {
116                /* strip trailing whitespaces */
117                l = bufl;
118                while (l-- && isspace(((unsigned char *)buf)[l]));
119                if (l != (int)(bufl - 1)) {
120                    bufl = l + 1;
121                    buf[bufl] = '\0';
122                }
123                add_next_index_stringl(array, buf, bufl, 1);
124            }
125            b = buf;
126        }
127        if (bufl) {
128            /* strip trailing whitespaces if we have not done so already */
129            if ((type == 2 && buf != b) || type != 2) {
130                l = bufl;
131                while (l-- && isspace(((unsigned char *)buf)[l]));
132                if (l != (int)(bufl - 1)) {
133                    bufl = l + 1;
134                    buf[bufl] = '\0';
135                }
136                if (type == 2) {
137                    add_next_index_stringl(array, buf, bufl, 1);
138                }
139            }
140
141            /* Return last line from the shell command */
142            RETVAL_STRINGL(buf, bufl, 1);
143        } else { /* should return NULL, but for BC we return "" */
144            RETVAL_EMPTY_STRING();
145        }
146    } else {
147        while((bufl = php_stream_read(stream, buf, EXEC_INPUT_BUF)) > 0) {
148            PHPWRITE(buf, bufl);
149        }
150    }
151
152    pclose_return = php_stream_close(stream);
153    efree(buf);
154
155done:
156#if PHP_SIGCHILD
157    if (sig_handler) {
158        signal(SIGCHLD, sig_handler);
159    }
160#endif
161    if (d) {
162        efree(d);
163    }
164    return pclose_return;
165err:
166    pclose_return = -1;
167    goto done;
168}
169/* }}} */
170
171static void php_exec_ex(INTERNAL_FUNCTION_PARAMETERS, int mode) /* {{{ */
172{
173    char *cmd;
174    int cmd_len;
175    zval *ret_code=NULL, *ret_array=NULL;
176    int ret;
177
178    if (mode) {
179        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z/", &cmd, &cmd_len, &ret_code) == FAILURE) {
180            RETURN_FALSE;
181        }
182    } else {
183        if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z/z/", &cmd, &cmd_len, &ret_array, &ret_code) == FAILURE) {
184            RETURN_FALSE;
185        }
186    }
187    if (!cmd_len) {
188        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot execute a blank command");
189        RETURN_FALSE;
190    }
191
192    if (!ret_array) {
193        ret = php_exec(mode, cmd, NULL, return_value TSRMLS_CC);
194    } else {
195        if (Z_TYPE_P(ret_array) != IS_ARRAY) {
196            zval_dtor(ret_array);
197            array_init(ret_array);
198        }
199        ret = php_exec(2, cmd, ret_array, return_value TSRMLS_CC);
200    }
201    if (ret_code) {
202        zval_dtor(ret_code);
203        ZVAL_LONG(ret_code, ret);
204    }
205}
206/* }}} */
207
208/* {{{ proto string exec(string command [, array &output [, int &return_value]])
209   Execute an external program */
210PHP_FUNCTION(exec)
211{
212    php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
213}
214/* }}} */
215
216/* {{{ proto int system(string command [, int &return_value])
217   Execute an external program and display output */
218PHP_FUNCTION(system)
219{
220    php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
221}
222/* }}} */
223
224/* {{{ proto void passthru(string command [, int &return_value])
225   Execute an external program and display raw output */
226PHP_FUNCTION(passthru)
227{
228    php_exec_ex(INTERNAL_FUNCTION_PARAM_PASSTHRU, 3);
229}
230/* }}} */
231
232/* {{{ php_escape_shell_cmd
233   Escape all chars that could possibly be used to
234   break out of a shell command
235
236   This function emalloc's a string and returns the pointer.
237   Remember to efree it when done with it.
238
239   *NOT* safe for binary strings
240*/
241PHPAPI char *php_escape_shell_cmd(char *str)
242{
243    register int x, y, l = strlen(str);
244    char *cmd;
245    char *p = NULL;
246    size_t estimate = (2 * l) + 1;
247
248    TSRMLS_FETCH();
249
250    cmd = safe_emalloc(2, l, 1);
251
252    for (x = 0, y = 0; x < l; x++) {
253        int mb_len = php_mblen(str + x, (l - x));
254
255        /* skip non-valid multibyte characters */
256        if (mb_len < 0) {
257            continue;
258        } else if (mb_len > 1) {
259            memcpy(cmd + y, str + x, mb_len);
260            y += mb_len;
261            x += mb_len - 1;
262            continue;
263        }
264
265        switch (str[x]) {
266#ifndef PHP_WIN32
267            case '"':
268            case '\'':
269                if (!p && (p = memchr(str + x + 1, str[x], l - x - 1))) {
270                    /* noop */
271                } else if (p && *p == str[x]) {
272                    p = NULL;
273                } else {
274                    cmd[y++] = '\\';
275                }
276                cmd[y++] = str[x];
277                break;
278#else
279            /* % is Windows specific for enviromental variables, ^%PATH% will
280                output PATH whil ^%PATH^% not. escapeshellcmd will escape all %.
281            */
282            case '%':
283            case '"':
284            case '\'':
285#endif
286            case '#': /* This is character-set independent */
287            case '&':
288            case ';':
289            case '`':
290            case '|':
291            case '*':
292            case '?':
293            case '~':
294            case '<':
295            case '>':
296            case '^':
297            case '(':
298            case ')':
299            case '[':
300            case ']':
301            case '{':
302            case '}':
303            case '$':
304            case '\\':
305            case '\x0A': /* excluding these two */
306            case '\xFF':
307#ifdef PHP_WIN32
308                cmd[y++] = '^';
309#else
310                cmd[y++] = '\\';
311#endif
312                /* fall-through */
313            default:
314                cmd[y++] = str[x];
315
316        }
317    }
318    cmd[y] = '\0';
319
320    if ((estimate - y) > 4096) {
321        /* realloc if the estimate was way overill
322         * Arbitrary cutoff point of 4096 */
323        cmd = erealloc(cmd, y + 1);
324    }
325
326    return cmd;
327}
328/* }}} */
329
330/* {{{ php_escape_shell_arg
331 */
332PHPAPI char *php_escape_shell_arg(char *str)
333{
334    int x, y = 0, l = strlen(str);
335    char *cmd;
336    size_t estimate = (4 * l) + 3;
337
338    TSRMLS_FETCH();
339
340    cmd = safe_emalloc(4, l, 3); /* worst case */
341
342#ifdef PHP_WIN32
343    cmd[y++] = '"';
344#else
345    cmd[y++] = '\'';
346#endif
347
348    for (x = 0; x < l; x++) {
349        int mb_len = php_mblen(str + x, (l - x));
350
351        /* skip non-valid multibyte characters */
352        if (mb_len < 0) {
353            continue;
354        } else if (mb_len > 1) {
355            memcpy(cmd + y, str + x, mb_len);
356            y += mb_len;
357            x += mb_len - 1;
358            continue;
359        }
360
361        switch (str[x]) {
362#ifdef PHP_WIN32
363        case '"':
364        case '%':
365            cmd[y++] = ' ';
366            break;
367#else
368        case '\'':
369            cmd[y++] = '\'';
370            cmd[y++] = '\\';
371            cmd[y++] = '\'';
372#endif
373            /* fall-through */
374        default:
375            cmd[y++] = str[x];
376        }
377    }
378#ifdef PHP_WIN32
379    cmd[y++] = '"';
380#else
381    cmd[y++] = '\'';
382#endif
383    cmd[y] = '\0';
384
385    if ((estimate - y) > 4096) {
386        /* realloc if the estimate was way overill
387         * Arbitrary cutoff point of 4096 */
388        cmd = erealloc(cmd, y + 1);
389    }
390    return cmd;
391}
392/* }}} */
393
394/* {{{ proto string escapeshellcmd(string command)
395   Escape shell metacharacters */
396PHP_FUNCTION(escapeshellcmd)
397{
398    char *command;
399    int command_len;
400    char *cmd = NULL;
401
402    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &command, &command_len) == FAILURE) {
403        return;
404    }
405
406    if (command_len) {
407        cmd = php_escape_shell_cmd(command);
408        RETVAL_STRING(cmd, 0);
409    } else {
410        RETVAL_EMPTY_STRING();
411    }
412}
413/* }}} */
414
415/* {{{ proto string escapeshellarg(string arg)
416   Quote and escape an argument for use in a shell command */
417PHP_FUNCTION(escapeshellarg)
418{
419    char *argument;
420    int argument_len;
421    char *cmd = NULL;
422
423    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &argument, &argument_len) == FAILURE) {
424        return;
425    }
426
427    if (argument) {
428        cmd = php_escape_shell_arg(argument);
429        RETVAL_STRING(cmd, 0);
430    }
431}
432/* }}} */
433
434/* {{{ proto string shell_exec(string cmd)
435   Execute command via shell and return complete output as string */
436PHP_FUNCTION(shell_exec)
437{
438    FILE *in;
439    size_t total_readbytes;
440    char *command;
441    int command_len;
442    char *ret;
443    php_stream *stream;
444
445    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &command, &command_len) == FAILURE) {
446        return;
447    }
448
449#ifdef PHP_WIN32
450    if ((in=VCWD_POPEN(command, "rt"))==NULL) {
451#else
452    if ((in=VCWD_POPEN(command, "r"))==NULL) {
453#endif
454        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to execute '%s'", command);
455        RETURN_FALSE;
456    }
457
458    stream = php_stream_fopen_from_pipe(in, "rb");
459    total_readbytes = php_stream_copy_to_mem(stream, &ret, PHP_STREAM_COPY_ALL, 0);
460    php_stream_close(stream);
461
462    if (total_readbytes > 0) {
463        RETVAL_STRINGL(ret, total_readbytes, 0);
464    }
465}
466/* }}} */
467
468#ifdef HAVE_NICE
469/* {{{ proto bool proc_nice(int priority)
470   Change the priority of the current process */
471PHP_FUNCTION(proc_nice)
472{
473    long pri;
474
475    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &pri) == FAILURE) {
476        RETURN_FALSE;
477    }
478
479    errno = 0;
480    php_ignore_value(nice(pri));
481    if (errno) {
482        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Only a super user may attempt to increase the priority of a process");
483        RETURN_FALSE;
484    }
485
486    RETURN_TRUE;
487}
488/* }}} */
489#endif
490
491/*
492 * Local variables:
493 * tab-width: 4
494 * c-basic-offset: 4
495 * End:
496 * vim600: sw=4 ts=4 fdm=marker
497 * vim<600: sw=4 ts=4
498 */
499