#!/usr/bin/php
<?php
/**
 * eGroupWare - RPM post install: automatic install or update EGroupware
 *
 * @link http://www.egroupware.org
 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
 * @author RalfBecker@outdoor-training.de
 * @version $Id$
 */

if (isset($_SERVER['HTTP_HOST']))	// security precaution: forbit calling post_install as web-page
{
	die('<h1>rpm_post_install.php must NOT be called as web-page --> exiting !!!</h1>');
}
$verbose = false;
$config = array(
	'php'         => '/usr/bin/php',
	'source_dir'  => '/usr/share/egroupware',
	'data_dir'    => '/var/lib/egroupware',
	'header'      => '$data_dir/header.inc.php',	// symlinked to source_dir by rpm
	'setup-cli'   => '$source_dir/setup/setup-cli.php',
	'domain'      => 'default',
	'config_user' => 'admin',
	'config_passwd'   => randomstring(),
	'db_type'     => 'mysql',
	'db_host'     => 'localhost',
	'db_port'     => 3306,
	'db_name'     => 'egroupware',
	'db_user'     => 'egroupware',
	'db_pass'     => randomstring(),
	'db_grant_host' => 'localhost',
	'db_root'     => 'root',	// mysql root user/pw to create database
	'db_root_pw'  => '',
	'backup'      => '',
	'admin_user'  => 'sysop',
	'admin_passwd'=> randomstring(),
	'lang'        => 'en',	// languages for admin user and extra lang to install
	'charset'     => 'utf-8',
	'start_db'    => '/sbin/service mysqld',
	'autostart_db' => '/sbin/chkconfig --level 345 mysqld on',
	'start_webserver' => '/sbin/service httpd',
	'autostart_webserver' => '/sbin/chkconfig --level 345 httpd on',
	'distro'      => 'rh',
	'account-auth'  => 'sql',
	'account_min_id' => '',
	'ldap_suffix'   => 'dc=local',
	'ldap_host'     => 'localhost',
	'ldap_admin'    => 'cn=admin,$suffix',
	'ldap_admin_pw' => '',
	'ldap_base'     => 'o=$domain,$suffix',
	'ldap_root_dn'  => 'cn=admin,$base',
	'ldap_root_pw'  => randomstring(),
	'ldap_context'  => 'ou=accounts,$base',
	'ldap_search_filter' => '(uid=%user)',
	'ldap_group_context' => 'ou=groups,$base',
	'mailserver'    => '',
	'smtpserver'    => 'localhost,25',
	'postfix'       => '',	// see setup-cli.php --help config
	'cyrus'         => '',
	'sieve'         => '',
);

// read language from LANG enviroment variable
if (($lang = isset($_ENV['LANG']) ? $_ENV['LANG'] : $_SERVER['LANG']))
{
	@list($lang,$nat) = preg_split('/[_.]/',$lang);
	if (in_array($lang.'-'.strtolower($nat),array('es-es','pt-br','zh-tw')))
	{
		$lang .= '-'.strtolower($nat);
	}
	$config['lang'] = $lang;
}
$config['source_dir'] = dirname(dirname(dirname(__FILE__)));

/**
 * Set distribution spezific defaults
 *
 * @param string $distro=null default autodetect
 */
function set_distro_defaults($distro=null)
{
	global $config;
	if (is_null($distro))
	{
		$distro = file_exists('/etc/SuSE-release') ? 'suse' : (file_exists('/etc/debian_version') ? 'debian' :
			(file_exists('/etc/mandriva-release') ? 'mandriva' : 'rh'));
	}
	switch (($config['distro'] = $distro))
	{
		case 'suse':
			$config['php'] = '/usr/bin/php5';
			$config['start_db'] = '/sbin/service mysql';
			$config['autostart_db'] = '/sbin/chkconfig --level 345 mysql on';
			$config['start_webserver'] = '/sbin/service apache2';
			$config['autostart_webserver'] = '/sbin/chkconfig --level 345 apache2 on';
			$config['ldap_suffix'] = 'dc=site';
			$config['ldap_admin'] = $config['ldap_root_dn'] = 'cn=Administrator,$suffix';
			$config['ldap_root_pw'] = '$admin_pw';
			$config['ldap_base'] = '$suffix';
			$config['ldap_context'] = 'ou=people,$base';
			$config['ldap_group_context'] = 'ou=group,$base';
			break;
		case 'debian':
			// service not in Debian5, only newer Ubuntu, which complains about /etc/init.d/xx
			if (file_exists('/usr/sbin/service'))
			{
				$config['start_db'] = '/usr/sbin/service mysql';
				$config['start_webserver'] = '/usr/sbin/service apache2';
			}
			else
			{	
				$config['start_db'] = '/etc/init.d/mysql';
				$config['start_webserver'] = '/etc/init.d/apache2';
			}
			$config['autostart_db'] = '/usr/sbin/update-rc.d mysql defaults';
			$config['autostart_webserver'] = '/usr/sbin/update-rc.d apache2 defaults';
			break;
		case 'mandriva':
			$config['ldap_suffix'] = 'dc=site';
			$config['ldap_admin'] = $config['ldap_root_dn'] = 'uid=LDAP Admin,ou=System Accounts,$suffix';
			$config['ldap_root_pw'] = '$admin_pw';
			$config['ldap_base'] = '$suffix';
			$config['ldap_context'] = 'ou=People,$base';
			$config['ldap_group_context'] = 'ou=Group,$base';
			break;
		default:
			$config['distro'] = 'rh';
			// fall through
		case 'rh':	// nothing to do, defaults are already set
			break;
	}
}
set_distro_defaults();

// read config from command line
$argv = $_SERVER['argv'];
$prog = array_shift($argv);

// check if we have EGW_POST_INSTALL set and prepend it to the command line (command line has precedence)
if (($config_set = isset($_ENV['EGW_POST_INSTALL']) ? $_ENV['EGW_POST_INSTALL'] : @$_SERVER['EGW_POST_INSTALL']))
{
	$conf = array();
	$config_set = preg_split('/[ \t]+/',trim($config_set));
	while($config_set)
	{
		$val = array_shift($config_set);
		if (($quote = $val[0]) == "'" || $quote == '"')	// arguments might be quoted with ' or "
		{
			while (substr($val,-1) != $quote)
			{
				if (!$config_set) throw new Exception('Invalid EGW_POST_INSTALL enviroment variable!');
				$val .= ' '.array_shift($config_set);
			}
			$val = substr($val,1,-1);
		}
		$conf[] = $val;
	}
	$argv = array_merge($conf,$argv);
}

$auth_type_given = false;
while(($arg = array_shift($argv)))
{
	if ($arg == '-v' || $arg == '--verbose')
	{
		$verbose = true;
	}
	elseif($arg == '-h' || $arg == '--help')
	{
		usage();
	}
	elseif($arg == '--suse')
	{
		set_distro_defaults('suse');
	}
	elseif($arg == '--distro')
	{
		set_distro_defaults(array_shift($argv));
	}
	elseif(substr($arg,0,2) == '--' && isset($config[$name=substr($arg,2)]))
	{
		$config[$name] = array_shift($argv);

		switch($name)
		{
			case 'auth_type':
				$auth_type_given = true;
				break;

			case 'account_repository':	// auth-type defaults to account-repository
				if (!$auth_type_given)
				{
					$config['auth_type'] = $config[$name];
				}
				break;
		}
	}
	else
	{
		usage("Unknown argument '$arg'!");
	}
}

$replace = array();
foreach($config as $name => $value)
{
	$replace['$'.$name] = $value;
	if (strpos($value,'$') !== false)
	{
		$config[$name] = strtr($value,$replace);
	}
}
// basic config checks
foreach(array('php','source_dir','data_dir','setup-cli') as $name)
{
	if (!file_exists($config[$name])) bail_out(1,$config[$name].' not found!');
}
$setup_cli = $config['php'].' -d memory_limit=64M '.$config['setup-cli'];

if (!file_exists($config['header']) || filesize($config['header']) < 200)	// default header redirecting to setup is 147 bytes
{
	// --> new install
	$extra_config = '';

	// check for localhost if database server is started and start it (permanent) if not
	if ($config['db_host'] == 'localhost' && $config['start_db'])
	{
		if (exec($config['start_db'].' status',$dummy,$ret) && $ret)
		{
			system($config['start_db'].' start');
			system($config['autostart_db']);
		}
	}
	// create database
	$setup_db = $setup_cli.' --setup-cmd-database sub_command=create_db';
	foreach(array('domain','db_type','db_host','db_port','db_name','db_user','db_pass','db_root','db_root_pw','db_grant_host') as $name)
	{
		$setup_db .= ' '.escapeshellarg($name.'='.$config[$name]);
	}
	run_cmd($setup_db);

	// check if ldap is required and initialise it
	// we need to specify account_repository and auth_type to --install as extra config, otherwise install happens for sql!
	@list($config['account_repository'],$config['auth_type'],$rest) = explode(',',$config['account-auth'],3);
	$extra_config .= ' '.escapeshellarg('account_repository='.$config['account_repository']);
	$extra_config .= ' '.escapeshellarg('auth_type='.(empty($config['auth_type']) ? $config['account_repository'] : $config['auth_type']));
	if (empty($rest)) unset($config['account-auth']);
	if ($config['account_repository'] == 'ldap' || $config['auth_type'] == 'ldap')
	{
		// set account_min_id to 1100 if not specified to NOT clash with system accounts
		$extra_config .= ' '.escapeshellarg('account_min_id='.(!empty($config['account_min_id']) ? $config['account_min_id'] : 1100));

		$setup_ldap = $setup_cli.' --setup-cmd-ldap sub_command='.
			($config['account_repository'] == 'ldap' ? 'create_ldap' : 'test_ldap');
		foreach(array(
			'domain','ldap_suffix','ldap_host','ldap_admin','ldap_admin_pw',	// non-egw params: only used for create
			'ldap_base','ldap_root_dn','ldap_root_pw','ldap_context','ldap_search_filter','ldap_group_context',	// egw params
		) as $name)
		{
			if (strpos($value=$config[$name],'$') !== false)
			{
				$config[$name] = $value = strtr($value,array(
					'$suffix' => $config['ldap_suffix'],
					'$base' => $config['ldap_base'],
					'$admin_pw' => $config['ldap_admin_pw'],
				));
			}
			$setup_ldap .= ' '.escapeshellarg($name.'='.$value);

			if (!in_array($name,array('domain','ldap_suffix','ldap_admin','ldap_admin_pw')))
			{
				$extra_config .= ' '.escapeshellarg($name.'='.$value);
			}
		}
		run_cmd($setup_ldap);
	}

	// create header
	$setup_header = $setup_cli.' --create-header '.escapeshellarg($config['config_passwd'].','.$config['config_user']).
		' --domain '.escapeshellarg($config['domain'].','.$config['db_name'].','.$config['db_user'].','.$config['db_pass'].
			','.$config['db_type'].','.$config['db_host'].','.$config['db_port']);
	run_cmd($setup_header);

	// install egroupware
	$setup_install = $setup_cli.' --install '.escapeshellarg($config['domain'].','.$config['config_user'].','.$config['config_passwd'].','.$config['backup'].','.$config['charset'].','.$config['lang'])
		.$extra_config;
	run_cmd($setup_install);

	if ($config['data_dir'] != '/var/lib/egroupware')
	{
		// set files dir different from default
		$setup_config = $setup_cli.' --config '.escapeshellarg($config['domain'].','.$config['config_user'].','.$config['config_passwd']).
			' --files-dir '.escapeshellarg($config['data_dir'].'/files').' --backup-dir '.escapeshellarg($config['data_dir'].'/backup');
		run_cmd($setup_config);
	}
	// create mailserver config (fmail requires at least minimal config given as default, otherwise fatal error)
	$setup_mailserver = $setup_cli.' --config '.escapeshellarg($config['domain'].','.$config['config_user'].','.$config['config_passwd']);
	foreach(array('account-auth','smtpserver','postfix','mailserver','cyrus','sieve') as $name)
	{
		if (!empty($config[$name])) $setup_mailserver .= ' --'.$name.' '.escapeshellarg($config[$name]);
	}
	run_cmd($setup_mailserver);

	// create first user
	$setup_admin = $setup_cli.' --admin '.escapeshellarg($config['domain'].','.$config['config_user'].','.$config['config_passwd'].','.
		$config['admin_user'].','.$config['admin_passwd'].',,,,'.$config['lang']);
	run_cmd($setup_admin);

	// check if webserver is started and start it (permanent) if not
	if ($config['start_webserver'])
	{
		if (exec($config['start_webserver'].' status',$dummy,$ret) && $ret)
		{
			system($config['start_webserver'].' start');
			system($config['autostart_webserver']);
		}
		else
		{
			system($config['start_webserver'].' reload');
		}
	}
	echo "\n";
	echo "EGroupware successful installed\n";
	echo "===============================\n";
	echo "\n";
	echo "Please note the following user names and passwords:\n";
	echo "\n";
	echo "Setup username:      $config[config_user]\n";
	echo "      password:      $config[config_passwd]\n";
	echo "\n";
	echo "EGroupware username: $config[admin_user]\n";
	echo "           password: $config[admin_passwd]\n";
	echo "\n";
	echo "You can log into EGroupware by pointing your browser to http://localhost/egroupware/\n";
	echo "Please replace localhost with the appropriate hostname, if you connect remote.\n\n";

	if (empty($config['db_root_pw']))
	{
		echo "*** Database has no root password set, please fix that immediatly: mysqladmin -u root password NEWPASSWORD\n\n";
	}
}
else
{
	// --> existing install --> update

	// get user from header and replace password, as we dont know it
	$old_password = patch_header($config['header'],$config['config_user'],$config['config_passwd']);
	// register a shutdown function to put old password back in any case
	register_shutdown_function('patch_header',$config['header'],$config['config_user'],$old_password);

	// update egroupware
	$setup_update = $setup_cli.' --update '.escapeshellarg('all,'.$config['config_user'].','.$config['config_passwd']);
	$ret = run_cmd($setup_update,$output,array(4,15));

	switch($ret)
	{
		case 4:		// header needs an update
			$header_update = $setup_cli.' --update-header '.escapeshellarg($config['config_passwd'].','.$config['config_user']);
			run_cmd($header_update);
			$ret = run_cmd($setup_update,$output,15);
			if ($ret != 15) break;
			// fall through
		case 15:	// missing configuration (eg. mailserver)
			if (!$verbose) echo implode("\n",(array)$output)."\n";
			break;

		case 0:
			echo "\nEGroupware successful updated\n";
			break;
	}
	exit($ret);
}

/**
 * Patches a given password (for header admin) into the EGroupware header.inc.php and returns the old one
 *
 * @param string $filename
 * @param string &$user username on return(!)
 * @param string $password new password
 * @return string old password
 */
function patch_header($filename,&$user,$password)
{
	$header = file_get_contents($filename);

	if (!preg_match('/'.preg_quote("\$GLOBALS['egw_info']['server']['header_admin_user'] = '")."([^']+)';/m",$header,$umatches) ||
		!preg_match('/'.preg_quote("\$GLOBALS['egw_info']['server']['header_admin_password'] = '")."([^']*)';/m",$header,$pmatches))
	{
		bail_out(99,"$filename is no regular EGroupware header.inc.php!");
	}
	file_put_contents($filename,preg_replace('/'.preg_quote("\$GLOBALS['egw_info']['server']['header_admin_password'] = '")."([^']*)';/m",
		"\$GLOBALS['egw_info']['server']['header_admin_password'] = '".$password."';",$header));

	$user = $umatches[1];

	return $pmatches[1];
}

/**
 * Runs given shell command, exists with error-code after echoing the output of the failed command (if not already running verbose)
 *
 * @param string $cmd
 * @param array &$output=null $output of command
 * @param int|array $no_bailout=null exit code(s) to NOT bail out
 * @return int exit code of $cmd
 */
function run_cmd($cmd,array &$output=null,$no_bailout=null)
{
	global $verbose;

	if ($verbose)
	{
		echo $cmd."\n";
		system($cmd,$ret);
	}
	else
	{
		$output[] = $cmd;
		exec($cmd,$output,$ret);
	}
	if ($ret && !in_array($ret,(array)$no_bailout))
	{
		bail_out($ret,$verbose?null:$output);
	}
	return $ret;
}

/**
 * Stop programm execution with a given exit code and optional extra message
 *
 * @param int $ret=1
 * @param array|string $output line(s) to output before temination notice
 */
function bail_out($ret=1,$output=null)
{
	if ($output) echo implode("\n",(array)$output);
	echo "\n\nInstallation failed --> exiting!\n\n";
	exit($ret);
}

/**
 * Return a rand string, eg. to generate passwords
 *
 * @param int $len=16
 * @return string
 */
function randomstring($len=16)
{
	static $usedchars = array(
		'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f',
		'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
		'w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L',
		'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
		'@','!','$','%','&','/','(',')','=','?',';',':','#','_','-','<',
		'>','|','{','[',']','}',	// dont add \,'" as we have problems dealing with them
	);

	$str = '';
	for($i=0; $i < $len; $i++)
	{
		$str .= $usedchars[mt_rand(0,count($usedchars)-1)];
	}
	return $str;
}

/**
 * Give usage information and an optional error-message, before stoping program execution with exit-code 90 or 0
 *
 * @param string $error=null optional error-message
 */
function usage($error=null)
{
	global $prog,$config;

	echo "Usage: $prog [-h|--help] [-v|--verbose] [--distro=(suse|rh|debian)] [options, ...]\n\n";
	echo "options and their defaults:\n";
	foreach($config as $name => $default)
	{
		if (in_array($name,array('config_passwd','db_pass','admin_passwd','ldap_root_pw')))
		{
			$default = '<16 char random string>';
		}
		echo '--'.str_pad($name,20).$default."\n";
	}
	if ($error)
	{
		echo "$error\n\n";
		exit(90);
	}
	exit(0);
}