xref: /PHP-5.3/sapi/nsapi/nsapi.c (revision a2045ff3)
1 /*
2    +----------------------------------------------------------------------+
3    | PHP Version 5                                                        |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 1997-2013 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: Jayakumar Muthukumarasamy <jk@kasenna.com>                   |
16    |         Uwe Schindler <uwe@thetaphi.de>                              |
17    +----------------------------------------------------------------------+
18 */
19 
20 /* $Id: ff336548b99ce55a0cca091c63e3d8d4da02a170 $ */
21 
22 /*
23  * PHP includes
24  */
25 #define NSAPI 1
26 
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30 
31 #include "php.h"
32 #include "php_variables.h"
33 #include "ext/standard/info.h"
34 #include "php_ini.h"
35 #include "php_globals.h"
36 #include "SAPI.h"
37 #include "php_main.h"
38 #include "php_version.h"
39 #include "TSRM.h"
40 #include "ext/standard/php_standard.h"
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 
44 #ifndef RTLD_DEFAULT
45 #define RTLD_DEFAULT NULL
46 #endif
47 
48 /*
49  * If neither XP_UNIX not XP_WIN32 is defined use PHP_WIN32
50  */
51 #if !defined(XP_UNIX) && !defined(XP_WIN32)
52 #ifdef PHP_WIN32
53 #define XP_WIN32
54 #else
55 #define XP_UNIX
56 #endif
57 #endif
58 
59 /*
60  * The manual define of HPUX is to fix bug #46020, nsapi.h needs this to detect HPUX
61  */
62 #ifdef __hpux
63 #define HPUX
64 #endif
65 
66 /*
67  * NSAPI includes
68  */
69 #include "nsapi.h"
70 
71 /* fix for gcc4 visibility issue */
72 #ifndef PHP_WIN32
73 # undef NSAPI_PUBLIC
74 # define NSAPI_PUBLIC PHPAPI
75 #endif
76 
77 #define NSLS_D		struct nsapi_request_context *request_context
78 #define NSLS_DC		, NSLS_D
79 #define NSLS_C		request_context
80 #define NSLS_CC		, NSLS_C
81 #define NSG(v)		(request_context->v)
82 
83 /*
84  * ZTS needs to be defined for NSAPI to work
85  */
86 #if !defined(ZTS)
87 #error "NSAPI module needs ZTS to be defined"
88 #endif
89 
90 /*
91  * Structure to encapsulate the NSAPI request in SAPI
92  */
93 typedef struct nsapi_request_context {
94 	pblock	*pb;
95 	Session	*sn;
96 	Request	*rq;
97 	int	read_post_bytes;
98 	char *path_info;
99 	int fixed_script; /* 0 if script is from URI, 1 if script is from "script" parameter */
100 	short http_error; /* 0 in normal mode; for errors the HTTP error code */
101 } nsapi_request_context;
102 
103 /*
104  * Mappings between NSAPI names and environment variables. This
105  * mapping was obtained from the sample programs at the iplanet
106  * website.
107  */
108 typedef struct nsapi_equiv {
109 	const char *env_var;
110 	const char *nsapi_eq;
111 } nsapi_equiv;
112 
113 static nsapi_equiv nsapi_reqpb[] = {
114 	{ "QUERY_STRING",		"query" },
115 	{ "REQUEST_LINE",		"clf-request" },
116 	{ "REQUEST_METHOD",		"method" },
117 	{ "PHP_SELF",			"uri" },
118 	{ "SERVER_PROTOCOL",	"protocol" }
119 };
120 static size_t nsapi_reqpb_size = sizeof(nsapi_reqpb)/sizeof(nsapi_reqpb[0]);
121 
122 static nsapi_equiv nsapi_vars[] = {
123 	{ "AUTH_TYPE",			"auth-type" },
124 	{ "CLIENT_CERT",		"auth-cert" },
125 	{ "REMOTE_USER",		"auth-user" }
126 };
127 static size_t nsapi_vars_size = sizeof(nsapi_vars)/sizeof(nsapi_vars[0]);
128 
129 static nsapi_equiv nsapi_client[] = {
130 	{ "HTTPS_KEYSIZE",		"keysize" },
131 	{ "HTTPS_SECRETSIZE",	"secret-keysize" },
132 	{ "REMOTE_ADDR",		"ip" },
133 	{ "REMOTE_HOST",		"ip" }
134 };
135 static size_t nsapi_client_size = sizeof(nsapi_client)/sizeof(nsapi_client[0]);
136 
137 /* this parameters to "Service"/"Error" are NSAPI ones which should not be php.ini keys and are excluded */
138 static char *nsapi_exclude_from_ini_entries[] = { "fn", "type", "method", "directive", "code", "reason", "script", "bucket", NULL };
139 
nsapi_free(void * addr)140 static void nsapi_free(void *addr)
141 {
142 	if (addr != NULL) {
143 		FREE(addr);
144 	}
145 }
146 
147 
148 /*******************/
149 /* PHP module part */
150 /*******************/
151 
152 PHP_MINIT_FUNCTION(nsapi);
153 PHP_MSHUTDOWN_FUNCTION(nsapi);
154 PHP_RINIT_FUNCTION(nsapi);
155 PHP_RSHUTDOWN_FUNCTION(nsapi);
156 PHP_MINFO_FUNCTION(nsapi);
157 
158 PHP_FUNCTION(nsapi_virtual);
159 PHP_FUNCTION(nsapi_request_headers);
160 PHP_FUNCTION(nsapi_response_headers);
161 
ZEND_BEGIN_MODULE_GLOBALS(nsapi)162 ZEND_BEGIN_MODULE_GLOBALS(nsapi)
163 	long read_timeout;
164 ZEND_END_MODULE_GLOBALS(nsapi)
165 
166 ZEND_DECLARE_MODULE_GLOBALS(nsapi)
167 
168 #define NSAPI_G(v) TSRMG(nsapi_globals_id, zend_nsapi_globals *, v)
169 
170 
171 /* {{{ arginfo */
172 ZEND_BEGIN_ARG_INFO_EX(arginfo_nsapi_virtual, 0, 0, 1)
173 	ZEND_ARG_INFO(0, uri)
174 ZEND_END_ARG_INFO()
175 
176 ZEND_BEGIN_ARG_INFO(arginfo_nsapi_request_headers, 0)
177 ZEND_END_ARG_INFO()
178 
179 ZEND_BEGIN_ARG_INFO(arginfo_nsapi_response_headers, 0)
180 ZEND_END_ARG_INFO()
181 /* }}} */
182 
183 /* {{{ nsapi_functions[]
184  *
185  * Every user visible function must have an entry in nsapi_functions[].
186  */
187 const zend_function_entry nsapi_functions[] = {
188 	PHP_FE(nsapi_virtual,	arginfo_nsapi_virtual)						/* Make subrequest */
189 	PHP_FALIAS(virtual, nsapi_virtual, arginfo_nsapi_virtual)			/* compatibility */
190 	PHP_FE(nsapi_request_headers, arginfo_nsapi_request_headers)		/* get request headers */
191 	PHP_FALIAS(getallheaders, nsapi_request_headers, arginfo_nsapi_request_headers)	/* compatibility */
192 	PHP_FALIAS(apache_request_headers, nsapi_request_headers, arginfo_nsapi_request_headers)	/* compatibility */
193 	PHP_FE(nsapi_response_headers, arginfo_nsapi_response_headers)		/* get response headers */
194 	PHP_FALIAS(apache_response_headers, nsapi_response_headers, arginfo_nsapi_response_headers)	/* compatibility */
195 	{NULL, NULL, NULL}
196 };
197 /* }}} */
198 
199 /* {{{ nsapi_module_entry
200  */
201 zend_module_entry nsapi_module_entry = {
202 	STANDARD_MODULE_HEADER,
203 	"nsapi",
204 	nsapi_functions,
205 	PHP_MINIT(nsapi),
206 	PHP_MSHUTDOWN(nsapi),
207 	NULL,
208 	NULL,
209 	PHP_MINFO(nsapi),
210 	NO_VERSION_YET,
211 	STANDARD_MODULE_PROPERTIES
212 };
213 /* }}} */
214 
215 /* {{{ PHP_INI
216  */
217 PHP_INI_BEGIN()
218     STD_PHP_INI_ENTRY("nsapi.read_timeout", "60", PHP_INI_ALL, OnUpdateLong, read_timeout, zend_nsapi_globals, nsapi_globals)
219 PHP_INI_END()
220 /* }}} */
221 
222 /* newer servers hide this functions from the programmer so redefine the functions dynamically
223    thanks to Chris Elving from Sun for the function declarations */
224 typedef int (*nsapi_servact_prototype)(Session *sn, Request *rq);
225 nsapi_servact_prototype nsapi_servact_uri2path = NULL;
226 nsapi_servact_prototype nsapi_servact_pathchecks = NULL;
227 nsapi_servact_prototype nsapi_servact_fileinfo = NULL;
228 nsapi_servact_prototype nsapi_servact_service = NULL;
229 
230 #ifdef PHP_WIN32
231 /* The following dll-names for nsapi are in use at this time. The undocumented
232  * servact_* functions are always in the newest one, older ones are supported by
233  * the server only by wrapping the function table nothing else. So choose
234  * the newest one found in process space for dynamic linking */
235 static char *nsapi_dlls[] = { "ns-httpd40.dll", "ns-httpd36.dll", "ns-httpd35.dll", "ns-httpd30.dll", NULL };
236 /* if user specifies an other dll name by server_lib parameter
237  * it is placed in the following variable and only this DLL is
238  * checked for the servact_* functions */
239 char *nsapi_dll = NULL;
240 #endif
241 
242 /* {{{ php_nsapi_init_dynamic_symbols
243  */
php_nsapi_init_dynamic_symbols(void)244 static void php_nsapi_init_dynamic_symbols(void)
245 {
246 	/* find address of internal NSAPI functions */
247 #ifdef PHP_WIN32
248 	register int i;
249 	DL_HANDLE module = NULL;
250 	if (nsapi_dll) {
251 		/* try user specified server_lib */
252 		module = GetModuleHandle(nsapi_dll);
253 		if (!module) {
254 			log_error(LOG_WARN, "php5_init", NULL, NULL, "Cannot find DLL specified by server_lib parameter: %s", nsapi_dll);
255 		}
256 	} else {
257 		/* find a LOADED dll module from nsapi_dlls */
258 		for (i=0; nsapi_dlls[i]; i++) {
259 			if (module = GetModuleHandle(nsapi_dlls[i])) {
260 				break;
261 			}
262 		}
263 	}
264 	if (!module) return;
265 #else
266 	DL_HANDLE module = RTLD_DEFAULT;
267 #endif
268 	nsapi_servact_uri2path = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_uri2path");
269 	nsapi_servact_pathchecks = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_pathchecks");
270 	nsapi_servact_fileinfo = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_fileinfo");
271 	nsapi_servact_service = (nsapi_servact_prototype)DL_FETCH_SYMBOL(module, "INTservact_service");
272 	if (!(nsapi_servact_uri2path && nsapi_servact_pathchecks && nsapi_servact_fileinfo && nsapi_servact_service)) {
273 		/* not found - could be cause they are undocumented */
274 		nsapi_servact_uri2path = NULL;
275 		nsapi_servact_pathchecks = NULL;
276 		nsapi_servact_fileinfo = NULL;
277 		nsapi_servact_service = NULL;
278 	}
279 }
280 /* }}} */
281 
282 /* {{{ php_nsapi_init_globals
283  */
php_nsapi_init_globals(zend_nsapi_globals * nsapi_globals)284 static void php_nsapi_init_globals(zend_nsapi_globals *nsapi_globals)
285 {
286 	nsapi_globals->read_timeout = 60;
287 }
288 /* }}} */
289 
290 /* {{{ PHP_MINIT_FUNCTION
291  */
PHP_MINIT_FUNCTION(nsapi)292 PHP_MINIT_FUNCTION(nsapi)
293 {
294 	php_nsapi_init_dynamic_symbols();
295 	ZEND_INIT_MODULE_GLOBALS(nsapi, php_nsapi_init_globals, NULL);
296 	REGISTER_INI_ENTRIES();
297 	return SUCCESS;
298 }
299 /* }}} */
300 
301 /* {{{ PHP_MSHUTDOWN_FUNCTION
302  */
PHP_MSHUTDOWN_FUNCTION(nsapi)303 PHP_MSHUTDOWN_FUNCTION(nsapi)
304 {
305 	UNREGISTER_INI_ENTRIES();
306 	return SUCCESS;
307 }
308 /* }}} */
309 
310 /* {{{ PHP_MINFO_FUNCTION
311  */
PHP_MINFO_FUNCTION(nsapi)312 PHP_MINFO_FUNCTION(nsapi)
313 {
314 	php_info_print_table_start();
315 	php_info_print_table_row(2, "NSAPI Module Revision", "$Id: ff336548b99ce55a0cca091c63e3d8d4da02a170 $");
316 	php_info_print_table_row(2, "Server Software", system_version());
317 	php_info_print_table_row(2, "Sub-requests with nsapi_virtual()",
318 	 (nsapi_servact_service)?((zend_ini_long("zlib.output_compression", sizeof("zlib.output_compression"), 0))?"not supported with zlib.output_compression":"enabled"):"not supported on this platform" );
319 	php_info_print_table_end();
320 
321 	DISPLAY_INI_ENTRIES();
322 }
323 /* }}} */
324 
325 /* {{{ proto bool nsapi_virtual(string uri)
326    Perform an NSAPI sub-request */
327 /* This function is equivalent to <!--#include virtual...-->
328  * in SSI. It does an NSAPI sub-request. It is useful
329  * for including CGI scripts or .shtml files, or anything else
330  * that you'd parse through webserver.
331  */
PHP_FUNCTION(nsapi_virtual)332 PHP_FUNCTION(nsapi_virtual)
333 {
334 	int uri_len,rv;
335 	char *uri,*value;
336 	Request *rq;
337 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
338 
339 	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &uri, &uri_len) == FAILURE) {
340 		return;
341 	}
342 
343 	if (!nsapi_servact_service) {
344 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - Sub-requests not supported on this platform", uri);
345 		RETURN_FALSE;
346 	} else if (zend_ini_long("zlib.output_compression", sizeof("zlib.output_compression"), 0)) {
347 		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - Sub-requests do not work with zlib.output_compression", uri);
348 		RETURN_FALSE;
349 	} else {
350 		php_end_ob_buffers(1 TSRMLS_CC);
351 		php_header(TSRMLS_C);
352 
353 		/* do the sub-request */
354 		/* thanks to Chris Elving from Sun for this code sniplet */
355 		if ((rq = request_restart_internal(uri, NULL)) == NULL) {
356 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - Internal request creation failed", uri);
357 			RETURN_FALSE;
358 		}
359 
360 		/* insert host of current request to get page from same vhost */
361 		param_free(pblock_remove("host", rq->headers));
362 		if (value = pblock_findval("host", rc->rq->headers)) {
363 			pblock_nvinsert("host", value, rq->headers);
364 		}
365 
366 		/* go through the normal request stages as given in obj.conf,
367 		   but leave out the logging/error section */
368 		do {
369 			rv = (*nsapi_servact_uri2path)(rc->sn, rq);
370 			if (rv != REQ_PROCEED) {
371 				continue;
372 			}
373 
374 			rv = (*nsapi_servact_pathchecks)(rc->sn, rq);
375 			if (rv != REQ_PROCEED) {
376 				continue;
377 			}
378 
379 			rv = (*nsapi_servact_fileinfo)(rc->sn, rq);
380 			if (rv != REQ_PROCEED) {
381 				continue;
382 			}
383 
384 			rv = (*nsapi_servact_service)(rc->sn, rq);
385 		} while (rv == REQ_RESTART);
386 
387 		if (rq->status_num != 200) {
388 			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to include uri '%s' - HTTP status code %d during subrequest", uri, rq->status_num);
389 			request_free(rq);
390 			RETURN_FALSE;
391 		}
392 
393 		request_free(rq);
394 
395 		RETURN_TRUE;
396 	}
397 }
398 /* }}} */
399 
400 /* {{{ proto array nsapi_request_headers(void)
401    Get all headers from the request */
PHP_FUNCTION(nsapi_request_headers)402 PHP_FUNCTION(nsapi_request_headers)
403 {
404 	register int i;
405 	struct pb_entry *entry;
406 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
407 
408 	if (zend_parse_parameters_none() == FAILURE) {
409 		return;
410 	}
411 
412 	array_init(return_value);
413 
414 	for (i=0; i < rc->rq->headers->hsize; i++) {
415 		entry=rc->rq->headers->ht[i];
416 		while (entry) {
417 			if (!PG(safe_mode) || strncasecmp(entry->param->name, "authorization", 13)) {
418 				add_assoc_string(return_value, entry->param->name, entry->param->value, 1);
419 			}
420 			entry=entry->next;
421 		}
422   	}
423 }
424 /* }}} */
425 
426 /* {{{ proto array nsapi_response_headers(void)
427    Get all headers from the response */
PHP_FUNCTION(nsapi_response_headers)428 PHP_FUNCTION(nsapi_response_headers)
429 {
430 	register int i;
431 	struct pb_entry *entry;
432 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
433 
434 	if (zend_parse_parameters_none() == FAILURE) {
435 		return;
436 	}
437 
438 	array_init(return_value);
439 
440 	for (i=0; i < rc->rq->srvhdrs->hsize; i++) {
441 		entry=rc->rq->srvhdrs->ht[i];
442 		while (entry) {
443 			add_assoc_string(return_value, entry->param->name, entry->param->value, 1);
444 			entry=entry->next;
445 		}
446   	}
447 }
448 /* }}} */
449 
450 
451 /*************/
452 /* SAPI part */
453 /*************/
454 
sapi_nsapi_ub_write(const char * str,unsigned int str_length TSRMLS_DC)455 static int sapi_nsapi_ub_write(const char *str, unsigned int str_length TSRMLS_DC)
456 {
457 	int retval;
458 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
459 
460 	if (!SG(headers_sent)) {
461 		sapi_send_headers(TSRMLS_C);
462 	}
463 
464 	retval = net_write(rc->sn->csd, (char *)str, str_length);
465 	if (retval == IO_ERROR /* -1 */ || retval == IO_EOF /* 0 */) {
466 		php_handle_aborted_connection();
467 	}
468 	return retval;
469 }
470 
471 /* modified version of apache2 */
sapi_nsapi_flush(void * server_context)472 static void sapi_nsapi_flush(void *server_context)
473 {
474 	nsapi_request_context *rc = (nsapi_request_context *)server_context;
475 	TSRMLS_FETCH();
476 
477 	if (!rc) {
478 		/* we have no context, so no flushing needed. This fixes a SIGSEGV on shutdown */
479 		return;
480 	}
481 
482 	if (!SG(headers_sent)) {
483 		sapi_send_headers(TSRMLS_C);
484 	}
485 
486 	/* flushing is only supported in iPlanet servers from version 6.1 on, make it conditional */
487 #if NSAPI_VERSION >= 302
488 	if (net_flush(rc->sn->csd) < 0) {
489 		php_handle_aborted_connection();
490 	}
491 #endif
492 }
493 
494 /* callback for zend_llist_apply on SAPI_HEADER_DELETE_ALL operation */
php_nsapi_remove_header(sapi_header_struct * sapi_header TSRMLS_DC)495 static int php_nsapi_remove_header(sapi_header_struct *sapi_header TSRMLS_DC)
496 {
497 	char *header_name, *p;
498 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
499 
500 	/* copy the header, because NSAPI needs reformatting and we do not want to change the parameter */
501 	header_name = pool_strdup(rc->sn->pool, sapi_header->header);
502 
503 	/* extract name, this works, if only the header without ':' is given, too */
504 	if (p = strchr(header_name, ':')) {
505 		*p = 0;
506 	}
507 
508 	/* header_name to lower case because NSAPI reformats the headers and wants lowercase */
509 	for (p=header_name; *p; p++) {
510 		*p=tolower(*p);
511 	}
512 
513 	/* remove the header */
514 	param_free(pblock_remove(header_name, rc->rq->srvhdrs));
515 	pool_free(rc->sn->pool, header_name);
516 
517 	return ZEND_HASH_APPLY_KEEP;
518 }
519 
sapi_nsapi_header_handler(sapi_header_struct * sapi_header,sapi_header_op_enum op,sapi_headers_struct * sapi_headers TSRMLS_DC)520 static int sapi_nsapi_header_handler(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC)
521 {
522 	char *header_name, *header_content, *p;
523 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
524 
525 	switch(op) {
526 		case SAPI_HEADER_DELETE_ALL:
527 			/* this only deletes headers set or overwritten by PHP, headers previously set by NSAPI are left intact */
528 			zend_llist_apply(&sapi_headers->headers, (llist_apply_func_t) php_nsapi_remove_header TSRMLS_CC);
529 			return 0;
530 
531 		case SAPI_HEADER_DELETE:
532 			/* reuse the zend_llist_apply callback function for this, too */
533 			php_nsapi_remove_header(sapi_header TSRMLS_CC);
534 			return 0;
535 
536 		case SAPI_HEADER_ADD:
537 		case SAPI_HEADER_REPLACE:
538 			/* copy the header, because NSAPI needs reformatting and we do not want to change the parameter */
539 			header_name = pool_strdup(rc->sn->pool, sapi_header->header);
540 
541 			/* split header and align pointer for content */
542 			header_content = strchr(header_name, ':');
543 			if (header_content) {
544 				*header_content = 0;
545 				do {
546 					header_content++;
547 				} while (*header_content==' ');
548 
549 				/* header_name to lower case because NSAPI reformats the headers and wants lowercase */
550 				for (p=header_name; *p; p++) {
551 					*p=tolower(*p);
552 				}
553 
554 				/* if REPLACE, remove first.  "Content-type" is always removed, as SAPI has a bug according to this */
555 				if (op==SAPI_HEADER_REPLACE || strcmp(header_name, "content-type")==0) {
556 					param_free(pblock_remove(header_name, rc->rq->srvhdrs));
557 				}
558 				/* ADD header to nsapi table */
559 				pblock_nvinsert(header_name, header_content, rc->rq->srvhdrs);
560 			}
561 
562 			pool_free(rc->sn->pool, header_name);
563 			return SAPI_HEADER_ADD;
564 
565 		default:
566 			return 0;
567 	}
568 }
569 
sapi_nsapi_send_headers(sapi_headers_struct * sapi_headers TSRMLS_DC)570 static int sapi_nsapi_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
571 {
572 	int retval;
573 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
574 
575 	if (SG(sapi_headers).send_default_content_type) {
576 		char *hd;
577 		param_free(pblock_remove("content-type", rc->rq->srvhdrs));
578 		hd = sapi_get_default_content_type(TSRMLS_C);
579 		pblock_nvinsert("content-type", hd, rc->rq->srvhdrs);
580 		efree(hd);
581 	}
582 
583 	protocol_status(rc->sn, rc->rq, SG(sapi_headers).http_response_code, NULL);
584 	retval = protocol_start_response(rc->sn, rc->rq);
585 
586 	if (retval == REQ_PROCEED || retval == REQ_NOACTION) {
587 		return SAPI_HEADER_SENT_SUCCESSFULLY;
588 	} else {
589 		return SAPI_HEADER_SEND_FAILED;
590 	}
591 }
592 
sapi_nsapi_read_post(char * buffer,uint count_bytes TSRMLS_DC)593 static int sapi_nsapi_read_post(char *buffer, uint count_bytes TSRMLS_DC)
594 {
595 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
596 	char *read_ptr = buffer, *content_length_str = NULL;
597 	uint bytes_read = 0;
598 	int length, content_length = 0;
599 	netbuf *nbuf = rc->sn->inbuf;
600 
601 	/*
602 	 *	Yesss!
603 	 */
604 	count_bytes = MIN(count_bytes, SG(request_info).content_length-rc->read_post_bytes);
605 	content_length = SG(request_info).content_length;
606 
607 	if (content_length <= 0) {
608 		return 0;
609 	}
610 
611 	/*
612 	 * Gobble any pending data in the netbuf.
613 	 */
614 	length = nbuf->cursize - nbuf->pos;
615 	length = MIN(count_bytes, length);
616 	if (length > 0) {
617 		memcpy(read_ptr, nbuf->inbuf + nbuf->pos, length);
618 		bytes_read += length;
619 		read_ptr += length;
620 		content_length -= length;
621 		nbuf->pos += length;
622 	}
623 
624 	/*
625 	 * Read the remaining from the socket.
626 	 */
627 	while (content_length > 0 && bytes_read < count_bytes) {
628 		int bytes_to_read = count_bytes - bytes_read;
629 
630 		if (content_length < bytes_to_read) {
631 			bytes_to_read = content_length;
632 		}
633 
634 		length = net_read(rc->sn->csd, read_ptr, bytes_to_read, NSAPI_G(read_timeout));
635 
636 		if (length == IO_ERROR || length == IO_EOF) {
637 			break;
638 		}
639 
640 		bytes_read += length;
641 		read_ptr += length;
642 		content_length -= length;
643 	}
644 
645 	if ( bytes_read > 0 ) {
646 		rc->read_post_bytes += bytes_read;
647 	}
648 	return bytes_read;
649 }
650 
sapi_nsapi_read_cookies(TSRMLS_D)651 static char *sapi_nsapi_read_cookies(TSRMLS_D)
652 {
653 	char *cookie_string;
654 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
655 
656 	cookie_string = pblock_findval("cookie", rc->rq->headers);
657 	return cookie_string;
658 }
659 
sapi_nsapi_register_server_variables(zval * track_vars_array TSRMLS_DC)660 static void sapi_nsapi_register_server_variables(zval *track_vars_array TSRMLS_DC)
661 {
662 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
663 	register size_t i;
664 	int pos;
665 	char *value,*p;
666 	char buf[32];
667 	struct pb_entry *entry;
668 
669 	for (i = 0; i < nsapi_reqpb_size; i++) {
670 		value = pblock_findval(nsapi_reqpb[i].nsapi_eq, rc->rq->reqpb);
671 		if (value) {
672 			php_register_variable((char *)nsapi_reqpb[i].env_var, value, track_vars_array TSRMLS_CC);
673 		}
674 	}
675 
676 	for (i=0; i < rc->rq->headers->hsize; i++) {
677 		entry=rc->rq->headers->ht[i];
678 		while (entry) {
679 			if (!PG(safe_mode) || strncasecmp(entry->param->name, "authorization", 13)) {
680 				if (strcasecmp(entry->param->name, "content-length")==0 || strcasecmp(entry->param->name, "content-type")==0) {
681 					value=estrdup(entry->param->name);
682 					pos = 0;
683 				} else {
684 					spprintf(&value, 0, "HTTP_%s", entry->param->name);
685 					pos = 5;
686 				}
687 				if (value) {
688 					for(p = value + pos; *p; p++) {
689 						*p = toupper(*p);
690 						if (!isalnum(*p)) {
691 							*p = '_';
692 						}
693 					}
694 					php_register_variable(value, entry->param->value, track_vars_array TSRMLS_CC);
695 					efree(value);
696 				}
697 			}
698 			entry=entry->next;
699 		}
700   	}
701 
702 	for (i = 0; i < nsapi_vars_size; i++) {
703 		value = pblock_findval(nsapi_vars[i].nsapi_eq, rc->rq->vars);
704 		if (value) {
705 			php_register_variable((char *)nsapi_vars[i].env_var, value, track_vars_array TSRMLS_CC);
706 		}
707 	}
708 
709 	for (i = 0; i < nsapi_client_size; i++) {
710 		value = pblock_findval(nsapi_client[i].nsapi_eq, rc->sn->client);
711 		if (value) {
712 			php_register_variable((char *)nsapi_client[i].env_var, value, track_vars_array TSRMLS_CC);
713 		}
714 	}
715 
716 	if (value = session_dns(rc->sn)) {
717 		php_register_variable("REMOTE_HOST", value, track_vars_array TSRMLS_CC);
718 		nsapi_free(value);
719 	}
720 
721 	slprintf(buf, sizeof(buf), "%d", conf_getglobals()->Vport);
722 	php_register_variable("SERVER_PORT", buf, track_vars_array TSRMLS_CC);
723 	php_register_variable("SERVER_NAME", conf_getglobals()->Vserver_hostname, track_vars_array TSRMLS_CC);
724 
725 	value = http_uri2url_dynamic("", "", rc->sn, rc->rq);
726 	php_register_variable("SERVER_URL", value, track_vars_array TSRMLS_CC);
727 	nsapi_free(value);
728 
729 	php_register_variable("SERVER_SOFTWARE", system_version(), track_vars_array TSRMLS_CC);
730 	if (security_active) {
731 		php_register_variable("HTTPS", "ON", track_vars_array TSRMLS_CC);
732 	}
733 	php_register_variable("GATEWAY_INTERFACE", "CGI/1.1", track_vars_array TSRMLS_CC);
734 
735 	/* DOCUMENT_ROOT */
736 	if (value = request_translate_uri("/", rc->sn)) {
737 		pos = strlen(value);
738 		php_register_variable_safe("DOCUMENT_ROOT", value, pos-1, track_vars_array TSRMLS_CC);
739 		nsapi_free(value);
740 	}
741 
742 	/* PATH_INFO / PATH_TRANSLATED */
743 	if (rc->path_info) {
744 		if (value = request_translate_uri(rc->path_info, rc->sn)) {
745 			php_register_variable("PATH_TRANSLATED", value, track_vars_array TSRMLS_CC);
746 			nsapi_free(value);
747 		}
748 		php_register_variable("PATH_INFO", rc->path_info, track_vars_array TSRMLS_CC);
749 	}
750 
751 	/* Create full Request-URI & Script-Name */
752 	if (SG(request_info).request_uri) {
753 		pos = strlen(SG(request_info).request_uri);
754 
755 		if (SG(request_info).query_string) {
756 			spprintf(&value, 0, "%s?%s", SG(request_info).request_uri, SG(request_info).query_string);
757 			if (value) {
758 				php_register_variable("REQUEST_URI", value, track_vars_array TSRMLS_CC);
759 				efree(value);
760 			}
761 		} else {
762 			php_register_variable_safe("REQUEST_URI", SG(request_info).request_uri, pos, track_vars_array TSRMLS_CC);
763 		}
764 
765 		if (rc->path_info) {
766 			pos -= strlen(rc->path_info);
767 			if (pos<0) {
768 				pos = 0;
769 			}
770 		}
771 		php_register_variable_safe("SCRIPT_NAME", SG(request_info).request_uri, pos, track_vars_array TSRMLS_CC);
772 	}
773 	php_register_variable("SCRIPT_FILENAME", SG(request_info).path_translated, track_vars_array TSRMLS_CC);
774 
775 	/* special variables in error mode */
776 	if (rc->http_error) {
777 		slprintf(buf, sizeof(buf), "%d", rc->http_error);
778 		php_register_variable("ERROR_TYPE", buf, track_vars_array TSRMLS_CC);
779 	}
780 }
781 
nsapi_log_message(char * message)782 static void nsapi_log_message(char *message)
783 {
784 	TSRMLS_FETCH();
785 	nsapi_request_context *rc = (nsapi_request_context *)SG(server_context);
786 
787 	if (rc) {
788 		log_error(LOG_INFORM, pblock_findval("fn", rc->pb), rc->sn, rc->rq, "%s", message);
789 	} else {
790 		log_error(LOG_INFORM, "php5", NULL, NULL, "%s", message);
791 	}
792 }
793 
sapi_nsapi_get_request_time(TSRMLS_D)794 static time_t sapi_nsapi_get_request_time(TSRMLS_D)
795 {
796 	return REQ_TIME( ((nsapi_request_context *)SG(server_context))->rq );
797 }
798 
php_nsapi_startup(sapi_module_struct * sapi_module)799 static int php_nsapi_startup(sapi_module_struct *sapi_module)
800 {
801 	if (php_module_startup(sapi_module, &nsapi_module_entry, 1)==FAILURE) {
802 		return FAILURE;
803 	}
804 	return SUCCESS;
805 }
806 
sapi_nsapi_get_stat(TSRMLS_D)807 static struct stat* sapi_nsapi_get_stat(TSRMLS_D)
808 {
809 	return request_stat_path(
810 		SG(request_info).path_translated,
811 		((nsapi_request_context *)SG(server_context))->rq
812 	);
813 }
814 
815 static sapi_module_struct nsapi_sapi_module = {
816 	"nsapi",                                /* name */
817 	"NSAPI",                                /* pretty name */
818 
819 	php_nsapi_startup,                      /* startup */
820 	php_module_shutdown_wrapper,            /* shutdown */
821 
822 	NULL,                                   /* activate */
823 	NULL,                                   /* deactivate */
824 
825 	sapi_nsapi_ub_write,                    /* unbuffered write */
826 	sapi_nsapi_flush,                       /* flush */
827 	sapi_nsapi_get_stat,                    /* get uid/stat */
828 	NULL,                                   /* getenv */
829 
830 	php_error,                              /* error handler */
831 
832 	sapi_nsapi_header_handler,              /* header handler */
833 	sapi_nsapi_send_headers,                /* send headers handler */
834 	NULL,                                   /* send header handler */
835 
836 	sapi_nsapi_read_post,                   /* read POST data */
837 	sapi_nsapi_read_cookies,                /* read Cookies */
838 
839 	sapi_nsapi_register_server_variables,   /* register server variables */
840 	nsapi_log_message,                      /* Log message */
841 	sapi_nsapi_get_request_time,			/* Get request time */
842 	NULL,									/* Child terminate */
843 
844 	NULL,                                   /* Block interruptions */
845 	NULL,                                   /* Unblock interruptions */
846 
847 	STANDARD_SAPI_MODULE_PROPERTIES
848 };
849 
nsapi_php_ini_entries(NSLS_D TSRMLS_DC)850 static void nsapi_php_ini_entries(NSLS_D TSRMLS_DC)
851 {
852 	struct pb_entry *entry;
853 	register int i,j,ok;
854 
855 	for (i=0; i < NSG(pb)->hsize; i++) {
856 		entry=NSG(pb)->ht[i];
857 		while (entry) {
858 			/* exclude standard entries given to "Service" which should not go into ini entries */
859 			ok=1;
860 			for (j=0; nsapi_exclude_from_ini_entries[j]; j++) {
861 				ok&=(strcasecmp(entry->param->name, nsapi_exclude_from_ini_entries[j])!=0);
862 			}
863 
864 			if (ok) {
865 				/* change the ini entry */
866 				if (zend_alter_ini_entry(entry->param->name, strlen(entry->param->name)+1,
867 				 entry->param->value, strlen(entry->param->value),
868 				 PHP_INI_SYSTEM, PHP_INI_STAGE_ACTIVATE)==FAILURE) {
869 					log_error(LOG_WARN, pblock_findval("fn", NSG(pb)), NSG(sn), NSG(rq), "Cannot change php.ini key \"%s\" to \"%s\"", entry->param->name, entry->param->value);
870 				}
871 			}
872 			entry=entry->next;
873 		}
874   	}
875 }
876 
php5_close(void * vparam)877 void NSAPI_PUBLIC php5_close(void *vparam)
878 {
879 	if (nsapi_sapi_module.shutdown) {
880 		nsapi_sapi_module.shutdown(&nsapi_sapi_module);
881 	}
882 
883 	if (nsapi_sapi_module.php_ini_path_override) {
884 		free(nsapi_sapi_module.php_ini_path_override);
885 	}
886 
887 #ifdef PHP_WIN32
888 	if (nsapi_dll) {
889 		free(nsapi_dll);
890 		nsapi_dll = NULL;
891 	}
892 #endif
893 
894 	sapi_shutdown();
895 	tsrm_shutdown();
896 
897 	log_error(LOG_INFORM, "php5_close", NULL, NULL, "Shutdown PHP Module");
898 }
899 
900 /*********************************************************
901 / init SAF
902 /
903 / Init fn="php5_init" [php_ini="/path/to/php.ini"] [server_lib="ns-httpdXX.dll"]
904 /   Initialize the NSAPI module in magnus.conf
905 /
906 / php_ini: gives path to php.ini file
907 / server_lib: (only Win32) gives name of DLL (without path) to look for
908 /  servact_* functions
909 /
910 /*********************************************************/
php5_init(pblock * pb,Session * sn,Request * rq)911 int NSAPI_PUBLIC php5_init(pblock *pb, Session *sn, Request *rq)
912 {
913 	php_core_globals *core_globals;
914 	char *strval;
915 	int threads=128; /* default for server */
916 
917 	/* fetch max threads from NSAPI and initialize TSRM with it */
918 	threads=conf_getglobals()->Vpool_maxthreads;
919 	if (threads<1) {
920 		threads=128; /* default for server */
921 	}
922 	tsrm_startup(threads, 1, 0, NULL);
923 
924 	core_globals = ts_resource(core_globals_id);
925 
926 	/* look if php_ini parameter is given to php5_init */
927 	if (strval = pblock_findval("php_ini", pb)) {
928 		nsapi_sapi_module.php_ini_path_override = strdup(strval);
929 	}
930 
931 #ifdef PHP_WIN32
932 	/* look if server_lib parameter is given to php5_init
933 	 * (this disables the automatic search for the newest ns-httpdXX.dll) */
934 	if (strval = pblock_findval("server_lib", pb)) {
935 		nsapi_dll = strdup(strval);
936 	}
937 #endif
938 
939 	/* start SAPI */
940 	sapi_startup(&nsapi_sapi_module);
941 	nsapi_sapi_module.startup(&nsapi_sapi_module);
942 
943 	daemon_atrestart(&php5_close, NULL);
944 
945 	log_error(LOG_INFORM, pblock_findval("fn", pb), sn, rq, "Initialized PHP Module (%d threads expected)", threads);
946 	return REQ_PROCEED;
947 }
948 
949 /*********************************************************
950 / normal use in Service directive:
951 /
952 / Service fn="php5_execute" type=... method=... [inikey=inivalue inikey=inivalue...]
953 /
954 / use in Service for a directory to supply a php-made directory listing instead of server default:
955 /
956 / Service fn="php5_execute" type="magnus-internal/directory" script="/path/to/script.php" [inikey=inivalue inikey=inivalue...]
957 /
958 / use in Error SAF to display php script as error page:
959 /
960 / Error fn="php5_execute" code=XXX script="/path/to/script.php" [inikey=inivalue inikey=inivalue...]
961 / Error fn="php5_execute" reason="Reason" script="/path/to/script.php" [inikey=inivalue inikey=inivalue...]
962 /
963 /*********************************************************/
php5_execute(pblock * pb,Session * sn,Request * rq)964 int NSAPI_PUBLIC php5_execute(pblock *pb, Session *sn, Request *rq)
965 {
966 	int retval;
967 	nsapi_request_context *request_context;
968 	zend_file_handle file_handle = {0};
969 	struct stat *fst;
970 
971 	char *path_info;
972 	char *query_string    = pblock_findval("query", rq->reqpb);
973 	char *uri             = pblock_findval("uri", rq->reqpb);
974 	char *request_method  = pblock_findval("method", rq->reqpb);
975 	char *content_type    = pblock_findval("content-type", rq->headers);
976 	char *content_length  = pblock_findval("content-length", rq->headers);
977 	char *directive       = pblock_findval("Directive", pb);
978 	int error_directive   = (directive && !strcasecmp(directive, "error"));
979 	int fixed_script      = 1;
980 
981 	/* try to use script parameter -> Error or Service for directory listing */
982 	char *path_translated = pblock_findval("script", pb);
983 
984 	TSRMLS_FETCH();
985 
986 	/* if script parameter is missing: normal use as Service SAF  */
987 	if (!path_translated) {
988 		path_translated = pblock_findval("path", rq->vars);
989 		path_info       = pblock_findval("path-info", rq->vars);
990 		fixed_script = 0;
991 		if (error_directive) {
992 			/* go to next error directive if script parameter is missing */
993 			log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Missing 'script' parameter");
994 			return REQ_NOACTION;
995 		}
996 	} else {
997 		/* in error the path_info is the uri to the requested page */
998 		path_info = pblock_findval("uri", rq->reqpb);
999 	}
1000 
1001 	/* check if this uri was included in an other PHP script with nsapi_virtual()
1002 	   by looking for a request context in the current thread */
1003 	if (SG(server_context)) {
1004 		/* send 500 internal server error */
1005 		log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Cannot make nesting PHP requests with nsapi_virtual()");
1006 		if (error_directive) {
1007 			return REQ_NOACTION;
1008 		} else {
1009 			protocol_status(sn, rq, 500, NULL);
1010 			return REQ_ABORTED;
1011 		}
1012 	}
1013 
1014 	request_context = (nsapi_request_context *)pool_malloc(sn->pool, sizeof(nsapi_request_context));
1015 	if (!request_context) {
1016 		log_error(LOG_CATASTROPHE, pblock_findval("fn", pb), sn, rq, "Insufficient memory to process PHP request!");
1017 		return REQ_ABORTED;
1018 	}
1019 	request_context->pb = pb;
1020 	request_context->sn = sn;
1021 	request_context->rq = rq;
1022 	request_context->read_post_bytes = 0;
1023 	request_context->fixed_script = fixed_script;
1024 	request_context->http_error = (error_directive) ? rq->status_num : 0;
1025 	request_context->path_info = path_info;
1026 
1027 	SG(server_context) = request_context;
1028 	SG(request_info).query_string = query_string;
1029 	SG(request_info).request_uri = uri;
1030 	SG(request_info).request_method = request_method;
1031 	SG(request_info).path_translated = path_translated;
1032 	SG(request_info).content_type = content_type;
1033 	SG(request_info).content_length = (content_length == NULL) ? 0 : strtoul(content_length, 0, 0);
1034 	SG(sapi_headers).http_response_code = (error_directive) ? rq->status_num : 200;
1035 
1036 	nsapi_php_ini_entries(NSLS_C TSRMLS_CC);
1037 
1038 	if (!PG(safe_mode)) php_handle_auth_data(pblock_findval("authorization", rq->headers) TSRMLS_CC);
1039 
1040 	file_handle.type = ZEND_HANDLE_FILENAME;
1041 	file_handle.filename = SG(request_info).path_translated;
1042 	file_handle.free_filename = 0;
1043 	file_handle.opened_path = NULL;
1044 
1045 	fst = request_stat_path(SG(request_info).path_translated, rq);
1046 	if (fst && S_ISREG(fst->st_mode)) {
1047 		if (php_request_startup(TSRMLS_C) == SUCCESS) {
1048 			php_execute_script(&file_handle TSRMLS_CC);
1049 			php_request_shutdown(NULL);
1050 			retval=REQ_PROCEED;
1051 		} else {
1052 			/* send 500 internal server error */
1053 			log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Cannot prepare PHP engine!");
1054 			if (error_directive) {
1055 				retval=REQ_NOACTION;
1056 			} else {
1057 				protocol_status(sn, rq, 500, NULL);
1058 				retval=REQ_ABORTED;
1059 			}
1060 		}
1061 	} else {
1062 		/* send 404 because file not found */
1063 		log_error(LOG_WARN, pblock_findval("fn", pb), sn, rq, "Cannot execute PHP script: %s (File not found)", SG(request_info).path_translated);
1064 		if (error_directive) {
1065 			retval=REQ_NOACTION;
1066 		} else {
1067 			protocol_status(sn, rq, 404, NULL);
1068 			retval=REQ_ABORTED;
1069 		}
1070 	}
1071 
1072 	pool_free(sn->pool, request_context);
1073 	SG(server_context) = NULL;
1074 
1075 	return retval;
1076 }
1077 
1078 /*********************************************************
1079 / authentication
1080 /
1081 / we have to make a 'fake' authenticator for netscape so it
1082 / will pass authentication through to php, and allow us to
1083 / check authentication with our scripts.
1084 /
1085 / php5_auth_trans
1086 /   main function called from netscape server to authenticate
1087 /   a line in obj.conf:
1088 /		funcs=php5_auth_trans shlib="path/to/this/phpnsapi.dll"
1089 /	and:
1090 /		<Object ppath="path/to/be/authenticated/by/php/*">
1091 /		AuthTrans fn="php5_auth_trans"
1092 /*********************************************************/
php5_auth_trans(pblock * pb,Session * sn,Request * rq)1093 int NSAPI_PUBLIC php5_auth_trans(pblock * pb, Session * sn, Request * rq)
1094 {
1095 	/* This is a DO NOTHING function that allows authentication
1096 	 * information
1097 	 * to be passed through to PHP scripts.
1098 	 */
1099 	return REQ_PROCEED;
1100 }
1101 
1102 /*
1103  * Local variables:
1104  * tab-width: 4
1105  * c-basic-offset: 4
1106  * End:
1107  * vim600: sw=4 ts=4 fdm=marker
1108  * vim<600: sw=4 ts=4
1109  */
1110