1 /*
2   +----------------------------------------------------------------------+
3   | PHP Version 5 and 7                                                  |
4   +----------------------------------------------------------------------+
5   | Copyright (c) 1997-2004 The PHP Group                                |
6   +----------------------------------------------------------------------+
7   | This source file is subject to version 3.0 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_0.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: Michael Spector <michael@php.net>                            |
16   +----------------------------------------------------------------------+
17 */
18 
19 /* $ Id: $ */
20 
21 #include "php_expect.h"
22 #include <string.h>
23 #include <errno.h>
24 
25 #if PHP_MAJOR_VERSION >= 8
26 #define TSRMLS_CC
27 #endif
28 
29 ZEND_BEGIN_ARG_INFO_EX(arginfo_expect_popen, 0, 0, 1)
30 	ZEND_ARG_INFO(0, command)
31 ZEND_END_ARG_INFO()
32 
33 ZEND_BEGIN_ARG_INFO_EX(arginfo_expect_expectl, 0, 0, 2)
34 	ZEND_ARG_INFO(0, stream)
35 	ZEND_ARG_INFO(0, expect_cases)
36 	ZEND_ARG_INFO(1, match)
37 ZEND_END_ARG_INFO()
38 
39 /* {{{ expect_functions[] */
40 zend_function_entry expect_functions[] = {
41 	PHP_FE(expect_popen,    arginfo_expect_popen)
42 	PHP_FE(expect_expectl,  arginfo_expect_expectl)
43 	{ NULL, NULL, NULL }
44 };
45 /* }}} */
46 
47 
48 ZEND_DECLARE_MODULE_GLOBALS(expect)
49 static PHP_GINIT_FUNCTION(expect);
50 static PHP_GSHUTDOWN_FUNCTION(expect);
51 
52 /* {{{ expect_module_entry
53  */
54 zend_module_entry expect_module_entry = {
55 	STANDARD_MODULE_HEADER,
56 	"expect",
57 	expect_functions,
58 	PHP_MINIT(expect),
59 	PHP_MSHUTDOWN(expect),
60 	NULL,
61 	NULL,
62 	PHP_MINFO(expect),
63 	PHP_EXPECT_VERSION,
64 	PHP_MODULE_GLOBALS(expect),
65 	PHP_GINIT(expect),
66 	PHP_GSHUTDOWN(expect),
67 	NULL,
68 	STANDARD_MODULE_PROPERTIES_EX
69 };
70 /* }}} */
71 
72 #ifdef COMPILE_DL_EXPECT
73 ZEND_GET_MODULE(expect)
74 #endif
75 
76 /* {{{ php_expect_init_globals
77  */
PHP_GINIT_FUNCTION(expect)78 static PHP_GINIT_FUNCTION(expect)
79 {
80 	expect_globals->logfile_stream = NULL;
81 }
82 /* }}} */
83 
84 /* {{{ php_expect_destroy_globals
85  */
PHP_GSHUTDOWN_FUNCTION(expect)86 static PHP_GSHUTDOWN_FUNCTION(expect)
87 {
88 	if (expect_globals->logfile_stream) {
89 		php_stream_close(expect_globals->logfile_stream);
90 	}
91 }
92 /* }}} */
93 
94 /* {{{ PHP_INI_MH
95  *  */
PHP_INI_MH(OnSetExpectTimeout)96 static PHP_INI_MH(OnSetExpectTimeout)
97 {
98 	if (new_value) {
99 #if PHP_MAJOR_VERSION >= 7
100 		exp_timeout = atoi(ZSTR_VAL(new_value));
101 #else
102         exp_timeout = atoi(new_value);
103 #endif
104 		return SUCCESS;
105 	}
106 	return FAILURE;
107 }
108 /* }}} */
109 
110 /* {{{ PHP_INI_MH
111  *  */
PHP_INI_MH(OnSetExpectMatchMax)112 static PHP_INI_MH(OnSetExpectMatchMax)
113 {
114 	if (new_value) {
115 #if PHP_MAJOR_VERSION >= 7
116 		exp_match_max = atoi(ZSTR_VAL(new_value));
117 #else
118 		exp_match_max = atoi(new_value);
119 #endif
120 		return SUCCESS;
121 	}
122 	return FAILURE;
123 }
124 /* }}} */
125 
126 
127 /* {{{ PHP_INI_MH
128  *  */
PHP_INI_MH(OnSetExpectLogUser)129 static PHP_INI_MH(OnSetExpectLogUser)
130 {
131 	if (new_value) {
132 #if PHP_MAJOR_VERSION >= 7
133 		if (strncasecmp("on", ZSTR_VAL(new_value), sizeof("on")) == 0
134 			|| strncasecmp("true", ZSTR_VAL(new_value), sizeof("true")) == 0
135 			|| strncasecmp("yes", ZSTR_VAL(new_value), sizeof("yes")) == 0
136 			|| strncasecmp("1", ZSTR_VAL(new_value), sizeof("1")) == 0) {
137 #else
138 		if (strncasecmp("on", new_value, sizeof("on")) == 0
139 			|| strncasecmp("true", new_value, sizeof("true")) == 0
140 			|| strncasecmp("yes", new_value, sizeof("yes")) == 0
141 			|| strncasecmp("1", new_value, sizeof("1")) == 0) {
142 #endif
143 			exp_loguser = 1;
144 		} else {
145 			exp_loguser = 0;
146 		}
147 		return SUCCESS;
148 	}
149 	return FAILURE;
150 }
151 /* }}} */
152 
153 
154 /* {{{ PHP_INI_MH
155  *  */
156 static PHP_INI_MH(OnSetExpectLogFile)
157 {
158 	if (EXPECT_G(logfile_stream)) {
159 		php_stream_close(EXPECT_G(logfile_stream));
160 	}
161 #if PHP_MAJOR_VERSION >= 7
162    if (ZSTR_LEN(new_value) > 0) {
163        php_stream* stream = php_stream_open_wrapper (ZSTR_VAL(new_value), "a", 0, NULL);
164 #else
165 	if (new_value_length > 0) {
166 		php_stream* stream = php_stream_open_wrapper (new_value, "a", 0, NULL);
167 #endif
168 		if (!stream) {
169 			php_error_docref (NULL TSRMLS_CC, E_ERROR, "could not open log file for writing");
170 			return FAILURE;
171 		}
172 		stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
173 		if (php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void **) &exp_logfile, REPORT_ERRORS) != SUCCESS) {
174 			return FAILURE;
175 		}
176 		EXPECT_G(logfile_stream) = stream;
177 		exp_logfile_all = 1;
178 	} else {
179 		EXPECT_G(logfile_stream) = NULL;
180 		exp_logfile = NULL;
181 		exp_logfile_all = 0;
182 	}
183 	return SUCCESS;
184 }
185 /* }}} */
186 
187 
188 PHP_INI_BEGIN()
189 	PHP_INI_ENTRY("expect.timeout", "10", PHP_INI_ALL, OnSetExpectTimeout)
190 	PHP_INI_ENTRY_EX("expect.loguser", "1", PHP_INI_ALL, OnSetExpectLogUser, php_ini_boolean_displayer_cb)
191 	PHP_INI_ENTRY("expect.logfile", "", PHP_INI_ALL, OnSetExpectLogFile)
192 	PHP_INI_ENTRY("expect.match_max", "5000", PHP_INI_ALL, OnSetExpectMatchMax)
193 PHP_INI_END()
194 
195 
196 /* {{{ PHP_MINIT_FUNCTION */
197 PHP_MINIT_FUNCTION(expect)
198 {
199 	php_register_url_stream_wrapper("expect", &php_expect_wrapper TSRMLS_CC);
200 
201 	REGISTER_LONG_CONSTANT("EXP_GLOB", exp_glob, CONST_CS | CONST_PERSISTENT);
202 	REGISTER_LONG_CONSTANT("EXP_EXACT", exp_exact, CONST_CS | CONST_PERSISTENT);
203 	REGISTER_LONG_CONSTANT("EXP_REGEXP", exp_regexp, CONST_CS | CONST_PERSISTENT);
204 	REGISTER_LONG_CONSTANT("EXP_EOF", EXP_EOF, CONST_CS | CONST_PERSISTENT);
205 	REGISTER_LONG_CONSTANT("EXP_TIMEOUT", EXP_TIMEOUT, CONST_CS | CONST_PERSISTENT);
206 	REGISTER_LONG_CONSTANT("EXP_FULLBUFFER", EXP_FULLBUFFER, CONST_CS | CONST_PERSISTENT);
207 
208 	REGISTER_INI_ENTRIES();
209 
210 	Tcl_Interp *interp = Tcl_CreateInterp();
211 	if (Tcl_Init(interp) == TCL_ERROR) {
212 		php_error_docref (NULL TSRMLS_CC, E_ERROR,
213 			"Unable to initialize TCL interpreter: %s", Tcl_GetStringResult (interp));
214 		return FAILURE;
215 	}
216 	if (Expect_Init(interp) == TCL_ERROR) {
217 		php_error_docref (NULL TSRMLS_CC, E_ERROR,
218 			"Unable to initialize Expect: %s", Tcl_GetStringResult (interp));
219 		return FAILURE;
220 	}
221 
222 	return SUCCESS;
223 }
224 /* }}} */
225 
226 
227 /* {{{ PHP_MSHUTDOWN_FUNCTION */
228 PHP_MSHUTDOWN_FUNCTION(expect)
229 {
230 	php_unregister_url_stream_wrapper("expect" TSRMLS_CC);
231 
232 	UNREGISTER_INI_ENTRIES();
233 
234 	return SUCCESS;
235 }
236 /* }}} */
237 
238 
239 /* {{{ PHP_MINFO_FUNCTION */
240 PHP_MINFO_FUNCTION(expect)
241 {
242 	php_info_print_table_start();
243 	php_info_print_table_header(2, "Expect support", "enabled");
244 	php_info_print_table_row(2, "Version", PHP_EXPECT_VERSION);
245 	php_info_print_table_row(2, "Stream wrapper support", "expect://");
246 	php_info_print_table_end();
247 
248 	DISPLAY_INI_ENTRIES();
249 }
250 /* }}} */
251 
252 
253 /* {{{
254  * proto resource expect_popen (string command)
255  */
256 PHP_FUNCTION(expect_popen)
257 {
258 #if PHP_MAJOR_VERSION >= 7
259     zend_string *command = NULL;
260 #else
261 	char *command = NULL;
262 #endif
263 	int command_len;
264 	FILE *fp;
265 	php_stream *stream = NULL;
266 #if PHP_MAJOR_VERSION >= 7
267     zval z_pid;
268 #else
269 	zval *z_pid;
270 #endif
271 
272 	if (ZEND_NUM_ARGS() != 1) { WRONG_PARAM_COUNT; }
273 
274 #if PHP_MAJOR_VERSION >= 7
275     if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, "S", &command) == FAILURE) {
276 #else
277 	if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, "s", &command, &command_len) == FAILURE) {
278 #endif
279 		return;
280 	}
281 
282 #if PHP_MAJOR_VERSION >= 7
283     if ((fp = exp_popen (ZSTR_VAL(command))) != NULL) {
284 #else
285 	if ((fp = exp_popen (command)) != NULL) {
286 #endif
287 		stream = php_stream_fopen_from_pipe (fp, "");
288 	}
289 	if (!stream) {
290 		RETURN_FALSE;
291 	}
292 
293 	stream->flags |= PHP_STREAM_FLAG_NO_SEEK;
294 
295 #if PHP_MAJOR_VERSION >= 7
296     ZVAL_LONG (&z_pid, exp_pid);
297 #else
298 	MAKE_STD_ZVAL (z_pid);
299 	ZVAL_LONG (z_pid, exp_pid);
300 #endif
301 	stream->wrapperdata = z_pid;
302 
303 	php_stream_to_zval(stream, return_value);
304 }
305 /* }}} */
306 
307 
308 /* {{{
309  * proto mixed expect_expectl (resource stream, array expect_cases [, array match])
310  */
311 PHP_FUNCTION(expect_expectl)
312 {
313 	struct exp_case *ecases, *ecases_ptr, matchedcase;
314 #if PHP_MAJOR_VERSION >= 7
315     zval *z_stream, *z_cases, *z_match=NULL, *z_case, *z_value;
316 #else
317 	zval *z_stream, *z_cases, *z_match=NULL, **z_case, **z_value;
318 #endif
319 	php_stream *stream;
320 	int fd, argc;
321 	zend_ulong key;
322 
323 	if (ZEND_NUM_ARGS() < 2 || ZEND_NUM_ARGS() > 3) { WRONG_PARAM_COUNT; }
324 
325 	if (zend_parse_parameters (ZEND_NUM_ARGS() TSRMLS_CC, "ra|z", &z_stream, &z_cases, &z_match) == FAILURE) {
326 		return;
327 	}
328 
329 #if PHP_MAJOR_VERSION >= 7
330    php_stream_from_zval (stream, z_stream);
331 #else
332 	php_stream_from_zval (stream, &z_stream);
333 #endif
334 
335 #if PHP_MAJOR_VERSION >= 7
336     if (!&(stream->wrapperdata)) {
337 #else
338 	if (!stream->wrapperdata) {
339 #endif
340 		php_error_docref (NULL TSRMLS_CC, E_ERROR, "supplied argument is not a valid stream resource");
341 		return;
342 	}
343 
344 	if (php_stream_cast (stream, PHP_STREAM_AS_FD, (void*)&fd, REPORT_ERRORS) != SUCCESS || fd < 0) {
345 		return;
346 	}
347 
348 	argc = zend_hash_num_elements (Z_ARRVAL_P(z_cases));
349 	ecases = (struct exp_case*) safe_emalloc (argc + 1, sizeof(struct exp_case), 0);
350 	ecases_ptr = ecases;
351 
352 	zend_hash_internal_pointer_reset (Z_ARRVAL_P(z_cases));
353 
354 #if PHP_MAJOR_VERSION >= 7
355     while ((z_case = zend_hash_get_current_data (Z_ARRVAL_P(z_cases))) != NULL)
356     {
357         zval *z_pattern, *z_exp_type;
358         zend_hash_get_current_key(Z_ARRVAL_P(z_cases), NULL, &key);
359 
360         if (Z_TYPE_P(z_case) != IS_ARRAY) {
361 #else
362 	while (zend_hash_get_current_data (Z_ARRVAL_P(z_cases), (void **)&z_case) == SUCCESS)
363 	{
364 		zval **z_pattern, **z_exp_type;
365 		zend_hash_get_current_key(Z_ARRVAL_P(z_cases), NULL, &key, 0);
366 
367 		if (Z_TYPE_PP(z_case) != IS_ARRAY) {
368 #endif
369 			efree (ecases);
370 			php_error_docref (NULL TSRMLS_CC, E_ERROR, "expect case must be an array");
371 			return;
372 		}
373 
374 		ecases_ptr->re = NULL;
375 		ecases_ptr->type = exp_glob;
376 
377 		/* Gather pattern */
378 #if PHP_MAJOR_VERSION >= 7
379         if ((z_pattern = zend_hash_index_find(Z_ARRVAL_P(z_case), 0)) == NULL) {
380 #else
381 		if (zend_hash_index_find(Z_ARRVAL_PP(z_case), 0, (void **)&z_pattern) != SUCCESS) {
382 #endif
383 			efree (ecases);
384 			php_error_docref (NULL TSRMLS_CC, E_ERROR, "missing parameter for pattern at index: 0");
385 			return;
386 		}
387 #if PHP_MAJOR_VERSION >= 7
388         if (Z_TYPE_P(z_pattern) != IS_STRING) {
389 #else
390 		if (Z_TYPE_PP(z_pattern) != IS_STRING) {
391 #endif
392 			efree (ecases);
393 			php_error_docref (NULL TSRMLS_CC, E_ERROR, "pattern must be of string type");
394 			return;
395 		}
396 #if PHP_MAJOR_VERSION >= 7
397         ecases_ptr->pattern = Z_STRVAL_P(z_pattern);
398 #else
399 		ecases_ptr->pattern = Z_STRVAL_PP(z_pattern);
400 #endif
401 
402 		/* Gather value */
403 #if PHP_MAJOR_VERSION >= 7
404         if (zend_hash_index_find(Z_ARRVAL_P(z_case), 1) == NULL) {
405 #else
406 		if (zend_hash_index_find(Z_ARRVAL_PP(z_case), 1, (void **)&z_value) != SUCCESS) {
407 #endif
408 			efree (ecases);
409 			php_error_docref (NULL TSRMLS_CC, E_ERROR, "missing parameter for value at index: 1");
410 			return;
411 		}
412 		ecases_ptr->value = key;
413 
414 		/* Gather expression type (optional, default: EXPECT_GLOB) */
415 #if PHP_MAJOR_VERSION >= 7
416         if ((z_exp_type = zend_hash_index_find(Z_ARRVAL_P(z_case), 2)) != NULL) {
417             if (Z_TYPE_P(z_exp_type) != IS_LONG) {
418 #else
419 		if (zend_hash_index_find(Z_ARRVAL_PP(z_case), 2, (void **)&z_exp_type) == SUCCESS) {
420 			if (Z_TYPE_PP(z_exp_type) != IS_LONG) {
421 #endif
422 				efree (ecases);
423 				php_error_docref (NULL TSRMLS_CC, E_ERROR, "expression type must be an integer constant");
424 				return;
425 			}
426 #if PHP_MAJOR_VERSION >= 7
427             if (Z_LVAL_P(z_exp_type) != exp_glob && Z_LVAL_P(z_exp_type) != exp_exact && Z_LVAL_P(z_exp_type) != exp_regexp) {
428 #else
429 			if (Z_LVAL_PP(z_exp_type) != exp_glob && Z_LVAL_PP(z_exp_type) != exp_exact && Z_LVAL_PP(z_exp_type) != exp_regexp) {
430 #endif
431 				efree (ecases);
432 				php_error_docref (NULL TSRMLS_CC, E_ERROR, "expression type must be either EXPECT_GLOB, EXPECT_EXACT or EXPECT_REGEXP");
433 				return;
434 			}
435 #if PHP_MAJOR_VERSION >= 7
436             ecases_ptr->type = Z_LVAL_P(z_exp_type);
437 #else
438 			ecases_ptr->type = Z_LVAL_PP(z_exp_type);
439 #endif
440 		}
441 
442 		ecases_ptr++;
443 		zend_hash_move_forward(Z_ARRVAL_P(z_cases));
444 	}
445 	ecases_ptr->pattern = NULL;
446 	ecases_ptr->re = NULL;
447 	ecases_ptr->value = 0;
448 	ecases_ptr->type = exp_end;
449 
450 	int exp_retval = exp_expectv (fd, ecases);
451 	int case_found = 0;
452 	if (exp_retval >= 0) {
453 		key = exp_retval;
454 
455 		int exp_match_len = exp_match_end - exp_match;
456 		if (z_match && exp_match && exp_match_len > 0) {
457 			char *tmp = (char *)emalloc (sizeof(char) * (exp_match_len + 1));
458 			strlcpy (tmp, exp_match, exp_match_len + 1);
459 			zval_dtor (z_match);
460 			array_init(z_match);
461 #if PHP_MAJOR_VERSION >= 7
462             add_index_string(z_match, 0, tmp);
463 #else
464 			add_index_string(z_match, 0, tmp, 1);
465 #endif
466 			/* Get case that was matched */
467 			matchedcase = ecases[key];
468 			/* If there are subpattern matches ... */
469 			if (matchedcase.re != NULL && matchedcase.re->startp != NULL) {
470 				int i;
471 				/* iterate across all possible 9 subpatterns (a limitation of libexpect)
472 				   and add matching substring to matches array */
473 				for (i = 1; i <= 9; i++) {
474 					if (matchedcase.re->startp[i] != NULL) {
475 						int sub_match_len = matchedcase.re->endp[i] - matchedcase.re->startp[i];
476 						char *sub_match = (char *)emalloc (sizeof(char) * (sub_match_len + 1));
477 						strlcpy (sub_match, matchedcase.re->startp[i], sub_match_len + 1);
478 #if PHP_MAJOR_VERSION >= 7
479                         add_next_index_string(z_match, sub_match);
480 #else
481 						add_next_index_string(z_match, sub_match, 1);
482 #endif
483 						efree (sub_match);
484 					}
485 				}
486 			}
487 			efree (tmp);
488 		}
489 
490 #if PHP_MAJOR_VERSION >= 7
491         if ((z_case = zend_hash_index_find (Z_ARRVAL_P(z_cases), key)) != NULL) {
492             if ((z_value = zend_hash_index_find(Z_ARRVAL_P(z_case), 1)) != NULL) {
493                 *return_value = *z_value;
494 #else
495 		if (zend_hash_index_find (Z_ARRVAL_P(z_cases), key, (void **)&z_case) == SUCCESS) {
496 			if (zend_hash_index_find(Z_ARRVAL_PP(z_case), 1, (void **)&z_value) == SUCCESS) {
497 				*return_value = **z_value;
498 #endif
499 				zval_copy_ctor (return_value);
500 				case_found = 1;
501 			}
502 		}
503 	}
504 
505 	// Free compiled patterns:
506 	ecases_ptr = ecases;
507 	while (ecases_ptr != NULL && ecases_ptr->type != exp_end) {
508 		if (ecases_ptr->re != NULL) {
509 			free(ecases_ptr->re);
510 		}
511 		ecases_ptr++;
512 	}
513 	efree (ecases);
514 
515 	if (!case_found) {
516 		RETURN_LONG(exp_retval);
517 	}
518 }
519 /* }}} */
520 
521 
522 /*
523  * Local variables:
524  * tab-width: 4
525  * c-basic-offset: 4
526  * End:
527  * vim600: noet sw=4 ts=4 fdm=marker
528  * vim<600: noet sw=4 ts=4
529  */
530