/*******************************************************
 * This is a new version of the wrapper originally written
 * by me and then slightly modified by Simon 'janus' Dassow.
 * The new version fixes memory allocation problems that 
 * leas to sigfaults sometimes. It also introduces
 * debug information output to apache's error logs and the
 * output is made more "apache-like".
 *
 *                         Alexander Amelkin <spirit@reactor.ru>
 *                         23.05.2003
 *******************************************************
*/

#define VERSION "1.5"

/* Previous info:
 * 
 * This is a modified version of the PHP wrapper from
 * Alexander Amelkin.
 * 
 * I modified it to run under conditions where php is
 * running outside cgi-bin.
 * 
 * Now the wrapper fixes the environmentvariables
 * SCRIPT_NAME and SCRIPT_FILENAME.
 *
 * Wed Aug 21 16:46:46 CEST 2002
 * Simon "janus" Dassow, janus@linux-de.org
 *
 * 
 * (c) 2002, Alexander Amelkin, spirit@reactor.ru
 *
 * -----------------------------------------------------
 *                 Installation Manual
 * -----------------------------------------------------
 * To enable suexec php scripts, first of all you must
 * disable php processing by mod_php. To do so, comment
 * out all php-related strings in your httpd.conf
 *
 * Add the following strings to httpd.conf:
 *  AddType application/x-httpd-php .php .php4 .php3 .phtml
 *  Action application/x-httpd-php /cgi-bin/php_cgi        
 *
 * Do the following:
 * gcc php_cgi.c -o php_cgi
 * chmod 755 php_cgi
 *
 * Copy (do not use ln) php_cgi to the ScriptAlias /cgi-bin/ directory
 * of each VirtualHost, where php must be enabled. chown php_cgi
 * to whatever is set by User and Group directives for each
 * VirtualHost
 *
 * You're done. No modification to the existing php scripts
 * is required.
 *
 * To enable debug information output, create a file called
 * 'php_debug' in the directory where php_cgi resides.
 *  
*******************************************************/


#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libgen.h>
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>

// PHP binary file
//#define PHPPATH "/usr/local/bin/php"
#define PHPPATH "/usr/bin/php"

// Allow per-user PHP config files in user ScriptAliased directories?
// It's a good idea to set those files owned and writable by root and nobody else
// and also set them immutable (chattr +i) if you're using ext2fs
// On other filesystems you may want some trickery with sticky bit on the ScriptAliased
// directory to avoid the php.ini file modification by the user.
#define ALLOWINI 1
// Default PHP config file (php.ini) location
// #define PHPCONFIG "/usr/local/etc"
#define PHPCONFIG "/etc"

// Limit execution time by this number of seconds:
#define CPU_LIMIT 30

// Limit the number of child processes by this number:
#define PROC_LIMIT 5

// Limit address space (virtual memory) by this number of KBytes:
#define AS_LIMIT 16384

// Apache 2.x by default adds time stamps and other stuff to stderr output of scripts,
// so it's not needed for us to do so. If you use Apache 2.x, set this to 1
#define USEAPACHE2 1

void report(char *s)
{
 char ss[2048];
#ifndef USEAPACHE2
 time_t t;
 char *tm;
 time(&t);
 tm=ctime(&t);
 tm[strlen(tm)-1]=0;
 fprintf(stderr, "[%s] [debug] php_cgi %s\n", tm, s);
#else
 fprintf(stderr, "php_cgi %s\n", s);
#endif
}

void reperror(char *s)
{
 char ss[2048];
#ifndef USEAPACHE2
 time_t t;
 char *tm;
 time(&t);
 tm=ctime(&t);
 tm[strlen(tm)-1]=0;
 sprintf(ss, "[%s] [error] php_cgi %s", tm, s );
#else
 sprintf(ss, "php_cgi %s", s );
#endif
 perror(ss); 
}

extern char **environ;

int main(int argc, char * argv[]) 
{
      char ss[2048];
      char mypath[PATH_MAX], scriptpath[PATH_MAX];
      char *buf;
      char *script_name;
      char *redirect_uri;
      char *script_filename;
      char *document_root;
      int i=0;
      int debug=0, iniexists=0;
      struct stat flag;
      struct rlimit rl;
      time_t t;
	
      if( realpath(dirname(argv[0]), mypath) == NULL ) {
		      reperror(strerror(errno));
		      return -1;
      }

      sprintf(ss,"%s/php_debug",mypath);
      if( ! stat(ss,&flag) ) // File exists
	   debug=1;

      if(debug) { 
	      sprintf(ss, "PHP wrapper version %s started", VERSION);
	      report(ss);
      }
      
      sprintf(ss,"%s/php.ini",mypath);
      iniexists = ! stat(ss,&flag);

      if(debug)
	 {
	  sprintf(ss, "Wrapper location directory: %s", mypath);
	  report(ss);
          report("Reporting environment variables:");
          while( environ[i] != NULL )
		report(environ[i++]); 
	  report("End of environment");
	 }
	
	// Check if a script name was supplied
	if(getenv("PATH_TRANSLATED")!=NULL) {
		// Allocate some memory for a temporary buffer
		if(debug) report("Allocating memory for PATH_TRANSLATED");
		buf=strdup(getenv("PATH_TRANSLATED"));
		if(buf==NULL) {
			printf("Content-Type: text/plain\n\n");
			reperror("step 1 memory allocation error");
			return 1;
		}

		// Get the script's working directory
		dirname(buf);
		realpath(buf,scriptpath);
		// We don't need the buffer anymore
		free(buf);
		
		// chdir there
		if(debug) 
		 { 
		  sprintf(ss,"Changing working directory to %s",scriptpath);
       		  report(ss);
		 }


		if(chdir(scriptpath)==-1) {
			printf("Content-Type: text/plain\n\n");
			reperror("can't chdir");
			return 1;
		}


		// Reset broken SCRIPT_* vars
		// Allocate memory
		if(debug) report("Allocating memory for REQUEST_URI and DOCUMENT_ROOT");
		redirect_uri=strdup(getenv("REDIRECT_URL"));
		if( redirect_uri == NULL || redirect_uri[0]==0 ) {
			if(debug) report("The URI is empty, using REQUEST_URI");
			script_name=strdup(getenv("REQUEST_URI"));
		} else
			script_name=redirect_uri;

		document_root=strdup(getenv("DOCUMENT_ROOT"));

		if((script_name==NULL)||(document_root==NULL)) {
			printf("Content-Type: text/plain\n\n");
			reperror("step 2 memory allocation error");
			return 1;
		}

		if(debug) report("Resetting REQUEST_URI and DOCUMENT_ROOT");


		setenv("REQUEST_URI",script_name,1);
		if(strchr(script_name,'?')) rindex(script_name,'?')[0]=0;
		setenv("SCRIPT_NAME",script_name,1);

		if(debug) report("Allocating memory for SCRIPT_FILENAME");

		script_filename=(char*)malloc((strlen(document_root)+strlen(script_name)+1));

		if(script_filename==NULL) {
			printf("Content-Type: text/plain\n\n");
			reperror("step 3 memory allocation error");
			return 1;
		}

		sprintf(script_filename,"%s%s",document_root,script_name);
		if(debug) 
		  {
		   sprintf(ss,"SCRIPT_FILENAME=\"%s\"\n",script_filename);
		   report(ss);
		  }

		// Invoke php to run the script, all CGI parameters are left
		// untouched in the system environment
		setenv("SCRIPT_FILENAME",script_filename,1);

		if(debug) report("Freeing script_name...");
		free(script_name);
		if(debug) report("Freeing script_filename...");
		free(script_filename);
		if(debug) report("Freeing document_root...");
		free(document_root);

		if( ! stat( getenv("PATH_TRANSLATED"), &flag ) ) {
			sprintf(ss,"[%s] executing [%s]...", getenv("REMOTE_ADDR"), getenv("PATH_TRANSLATED"));
			report(ss);
			if(debug) {
				sprintf(ss,"ALLOWINI: %d, ini file %s/php.ini %s", 
					ALLOWINI, mypath, iniexists?"exists":"does not exist");
				report(ss);
			}

			// Enforce limits
			getrlimit(RLIMIT_CPU, &rl);
			rl.rlim_max = CPU_LIMIT;
			setrlimit(RLIMIT_CPU, &rl);

			getrlimit(RLIMIT_NPROC, &rl);
			rl.rlim_max = PROC_LIMIT;
			setrlimit(RLIMIT_NPROC, &rl);

			getrlimit(RLIMIT_AS, &rl);
			rl.rlim_max = AS_LIMIT;
			setrlimit(RLIMIT_AS, &rl);

			if(debug) { 
				sprintf(ss,"Resource Limits are set");
				report(ss);			
			}

			sprintf(ss,"%s", (ALLOWINI && iniexists)?mypath:PHPCONFIG);
			setenv("PHPRC",ss,1); // Tell PHP cgi binary where to look for php.ini
			// Usually php CGI binary will ignore all command line parameters
			// if variables like PATH_TRANSLATED exist in the environment,
			// but we will still pass them, just in case...
			execl(PHPPATH,PHPPATH,"-c", ss, "-f", getenv("PATH_TRANSLATED"),NULL);
			return 0;
		}
	}
	printf("Content-Type: text/plain\n\n");
	sprintf(ss,"Can not exec the requested script [%s]",getenv("PATH_TRANSLATED"));
	printf("%s: %s",ss, strerror(errno) );
	reperror(ss);
	return 1;
}

