diff --git a/admin/inc/class.admin_cmd.inc.php b/admin/inc/class.admin_cmd.inc.php index e350447e69..2a9c8fadc2 100644 --- a/admin/inc/class.admin_cmd.inc.php +++ b/admin/inc/class.admin_cmd.inc.php @@ -19,18 +19,21 @@ abstract class admin_cmd const scheduled = 1; const successful = 2; const failed = 3; + const pending = 4; + /** * The status of the command, one of either scheduled, successful, failed or deleted * * @var int */ - private $status; + protected $status; static $stati = array( admin_cmd::scheduled => 'scheduled', admin_cmd::successful => 'successful', admin_cmd::failed => 'failed', admin_cmd::deleted => 'deleted', + admin_cmd::pending => 'pending', ); protected $created; @@ -40,8 +43,8 @@ abstract class admin_cmd private $modified; private $modifier; private $modifier_email; - private $error; - private $errno; + protected $error; + protected $errno; public $requested; public $requested_email; public $comment; @@ -167,7 +170,7 @@ abstract class admin_cmd { $ret = $this->remote_exec(); } - $this->status = admin_cmd::successful; + if (is_null($this->status)) $this->status = admin_cmd::successful; } catch (Exception $e) { $this->error = $e->getMessage(); @@ -229,7 +232,10 @@ abstract class admin_cmd ); $url = $remote['remote_url'].'/admin/remote.php?domain='.urlencode($remote['remote_domain']).'&secret='.urlencode($secret); //echo "sending command to $url\n"; _debug_array($opts); - $message = file_get_contents($url, false, stream_context_create($opts)); + if (!($message = @file_get_contents($url, false, stream_context_create($opts)))) + { + throw new egw_exception(lang('Could not remote execute the command').': '.$http_response_header[0]); + } //echo "got: $message\n"; if (($value = unserialize($message)) !== false && $message !== serialize(false)) @@ -283,7 +289,7 @@ abstract class admin_cmd { return false; } - if (is_null($this->id)) + if (!$this->id) { $this->id = admin_cmd::$sql->data['id']; // if the cmd has no uid yet, we create one from our id and the install-id of this eGW instance @@ -429,6 +435,12 @@ abstract class admin_cmd { return $this->$property; // making all (non static) class vars readonly available } + switch($property) + { + case 'accounts': + self::_instanciate_accounts(); + return self::$accounts; + } return $this->data[$property]; } @@ -658,7 +670,7 @@ abstract class admin_cmd * @todo accounts class instanciation for setup * @throws egw_exception_assertion_failed(lang('%1 class not instanciated','accounts'),999); */ - protected function _instanciate_accounts() + static function _instanciate_accounts() { if (!is_object(admin_cmd::$accounts)) { @@ -857,7 +869,7 @@ abstract class admin_cmd { $data['remote_hash'] = self::remote_hash($data['install_id'],$data['config_passwd']); } - elseif ($data['install_id'] || $data['config_passwd'] || !$data['remote_hash']) + elseif (!$data['remote_hash'] && !($data['install_id'] && $data['config_passwd'])) { throw new egw_exception_wrong_userinput(lang('Either Install ID AND config password needed OR the remote hash!')); } @@ -914,4 +926,41 @@ abstract class admin_cmd { return preg_match('/^[0-9a-f]{32}$/',$str); } + + /** + * Check if the current command has the right crediential to be excuted remotely + * + * Command can reimplement that method, to allow eg. anonymous execution. + * + * This default implementation use a secret to authenticate with the installation, + * which is a md5 hash build from the uid of the command (to not allow to send new + * commands with an earsdroped secret) and the md5 hash of the md5 hash of the + * config password and the install_id (egw_admin_remote.remote_hash) + * + * @param string $secret hash used to authenticate the command ( + * @param string $config_passwd of the current domain + * @throws egw_exception_no_permission + */ + function check_remote_access($secret,$config_passwd) + { + // as a security measure remote administration need to be enabled under Admin > Site configuration + list(,$remote_admin_install_id) = explode('-',$this->uid); + $allowed_remote_admin_ids = $GLOBALS['egw_info']['server']['allow_remote_admin'] ? explode(',',$GLOBALS['egw_info']['server']['allow_remote_admin']) : array(); + + // to authenticate with the installation we use a secret, which is a md5 hash build from the uid + // of the command (to not allow to send new commands with an earsdroped secret) and the md5 hash + // of the md5 hash of the config password and the install_id (egw_admin_remote.remote_hash) + if (is_null($config_passwd) || is_numeric($this->uid) || !in_array($remote_admin_install_id,$allowed_remote_admin_ids) || + $secret != ($md5=md5($this->uid.$this->remote_hash($GLOBALS['egw_info']['server']['install_id'],$config_passwd)))) + { + //die("secret='$secret' != '$md5', is_null($config_passwd)=".is_null($config_passwd).", uid=$this->uid, remote_install_id=$remote_admin_install_id, allowed: ".implode(', ',$allowed_remote_admin_ids)); + $msg = lang('Permission denied!'); + if (!in_array($remote_admin_install_id,$allowed_remote_admin_ids)) + { + $msg .= "\n".lang('Remote administration need to be enabled in the remote instance under Admin > Site configuration!'); + } + throw new egw_exception_no_permission($msg,0); + } + } } +admin_cmd::_instanciate_accounts(); diff --git a/admin/remote.php b/admin/remote.php index e2e567b911..6668d86415 100644 --- a/admin/remote.php +++ b/admin/remote.php @@ -22,6 +22,20 @@ $GLOBALS['egw_info'] = array( include('../header.inc.php'); +// install an own exception handler to forward exceptions back to the remote side +function remote_exception_handler(Exception $e) +{ + $msg = $e->getMessage(); + if (is_object($GLOBALS['egw']->translation)) + { + $msg = $GLOBALS['egw']->translation->convert($msg,$GLOBALS['egw']->translation->charset(),'utf-8'); + } + header('HTTP/1.1 200 '.$msg); + echo $e->getCode().' '.$msg; + $GLOBALS['egw']->common->egw_exit(); +} +set_exception_handler('remote_exception_handler'); + $GLOBALS['egw']->applications->read_installed_apps(); // set $GLOBALS['egw_info']['apps'] (not set for login) $instance = isset($_GET['domain']) ? $_GET['domain'] : $_REQUEST['domain']; // use GET before the rest @@ -29,27 +43,8 @@ if (!isset($GLOBALS['egw_domain'][$instance])) { $instance = $GLOBALS['egw_info']['server']['default_domain']; } -$domain_data = $GLOBALS['egw_domain'][$instance]; -//echo $instance; _debug_array($domain_data); - -// as a security measure remote administration need to be enabled under Admin > Site configuration -list(,$remote_admin_install_id) = explode('-',$_REQUEST['uid']); -$allowed_remote_admin_ids = $GLOBALS['egw_info']['server']['allow_remote_admin'] ? explode(',',$GLOBALS['egw_info']['server']['allow_remote_admin']) : array(); -// to authenticate with the installation we use a secret, which is a md5 hash build from the uid -// of the command (to not allow to send new commands with an earsdroped secret) and the md5 hash -// of the md5 hash of the config password and the install_id (egw_admin_remote.remote_hash) -if (!$domain_data || is_numeric($_REQUEST['uid']) || !in_array($remote_admin_install_id,$allowed_remote_admin_ids) || - $_REQUEST['secret'] != ($md5=md5($_REQUEST['uid'].admin_cmd::remote_hash($GLOBALS['egw_info']['server']['install_id'],$domain_data['config_passwd'])))) -{ - header("HTTP/1.1 200 Unauthorized"); - //die("0 secret != '$md5'"); - echo lang('0 Permission denied!'); - if (!in_array($remote_admin_install_id,$allowed_remote_admin_ids)) - { - echo "\n".lang('Remote administration need to be enabled in the remote instance under Admin > Site configuration!'); - } - $GLOBALS['egw']->common->egw_exit(); -} +$config_passwd = $GLOBALS['egw_domain'][$instance]['config_passwd']; +unset($GLOBALS['egw_domain']); require_once(EGW_INCLUDE_ROOT.'/admin/inc/class.admin_cmd.inc.php'); @@ -58,7 +53,15 @@ require_once(EGW_INCLUDE_ROOT.'/admin/inc/class.admin_cmd.inc.php'); $cmd = admin_cmd::read($_REQUEST['uid']); if (is_object($cmd)) { - exit_with_status($cmd); + $cmd->check_remote_access($_REQUEST['secret'],$config_passwd); + + $success_msg = 'Successful'; + // if the comand object has a rerun method, call it + if (method_exists($cmd,'rerun')) + { + $success_msg = $cmd->rerun(); + } + exit_with_status($cmd,$success_msg); } // check if requests contains a reasonable looking admin command to be queued @@ -67,7 +70,7 @@ if (!$_REQUEST['uid'] || // no uid !$_REQUEST['creator_email']) // no creator email { header("HTTP/1.1 200 Bad format!"); - echo lang('0 Bad format!'); + echo '0 Bad format!'; $GLOBALS['egw']->common->egw_exit(); } @@ -81,22 +84,18 @@ if (isset($data['modifier'])) $data['modifier'] = 0; if (isset($data['requested'])) $data['requested'] = 0; // instanciate comand and run it -try { - $cmd = admin_cmd::instanciate($data); - //_debug_array($cmd); exit; - $success_msg = $cmd->run(); - - $GLOBALS['egw']->translation->convert($success_msg,$GLOBALS['egw']->translation->charset(),'utf-8'); +$cmd = admin_cmd::instanciate($data); - if (!is_string($success_msg)) - { - $success_msg = serialize($success_msg); - } -} -catch (Exception $e) { - header('HTTP/1.1 200 '.$e->getMessage()); - echo $e->getCode().' '.$e->getMessage(); - $GLOBALS['egw']->common->egw_exit(); +$cmd->check_remote_access($_REQUEST['secret'],$config_passwd); + +//_debug_array($cmd); exit; +$success_msg = $cmd->run(); + +$GLOBALS['egw']->translation->convert($success_msg,$GLOBALS['egw']->translation->charset(),'utf-8'); + +if (!is_string($success_msg)) +{ + $success_msg = serialize($success_msg); } exit_with_status($cmd,$success_msg); @@ -112,6 +111,7 @@ function exit_with_status($cmd,$success_msg='Successful') default: // everything else is returned as 200 HTTP status $success_msg = $cmd->stati[$cmd->status]; // fall through + case admin_cmd::pending: case admin_cmd::successful: header('HTTP/1.1 200 '.$cmd->stati[$cmd->status]); header('Content-type: text/plain; charset=utf-8');