grthtrhthjhtyjytjytkergtrhtrjytjerhrfh4:24 29/09/2026 'pre_execute',
'install_mkdirs' => 'mkdir',
'install_copy' => 'copy',
'install_images' => 'image_dir',
'install_menus' => 'menu',
'install_userpage' => 'user_page',
'install_dashlets' => 'dashlets',
'install_administration' => 'administration',
'install_connectors' => 'connectors',
'install_vardefs' => 'vardefs',
'install_layoutdefs' => 'layoutdefs',
'install_layoutfields' => 'layoutfields',
'install_relationships' => 'relationships',
'install_languages' => 'language',
'install_logichooks' => 'logic_hooks',
'post_execute' => 'post_execute',
);
/**
* config settings
* @var array
*/
private $config = array();
private $config_hash;
private $blackListExempt = array();
private $classBlackListExempt = array();
private $validExt = array(
'png',
'gif',
'jpg',
'css',
'js',
'php',
'txt',
'html',
'htm',
'tpl',
'pdf',
'md5',
'xml',
'hbs'
);
private $classBlackList = array(
// Class names specified here must be in lowercase as the implementation
// of the tokenizer converts all tokens to lowercase.
'reflection',
'reflectionclass',
'reflectionzendextension',
'reflectionextension',
'reflectionfunction',
'reflectionfunctionabstract',
'reflectionmethod',
'reflectionobject',
'reflectionparameter',
'reflectionproperty',
'reflector',
'reflectionexception',
'lua',
'ziparchive',
'splfileinfo',
'splfileobject',
'pclzip',
);
private $blackList = array(
'popen',
'proc_open',
'escapeshellarg',
'escapeshellcmd',
'proc_close',
'proc_get_status',
'proc_nice',
'passthru',
'clearstatcache',
'disk_free_space',
'disk_total_space',
'diskfreespace',
'dir',
'fclose',
'feof',
'fflush',
'fgetc',
'fgetcsv',
'fgets',
'fgetss',
'file_exists',
'file_get_contents',
'filesize',
'filetype',
'flock',
'fnmatch',
'fpassthru',
'fputcsv',
'fputs',
'fread',
'fscanf',
'fseek',
'fstat',
'ftell',
'ftruncate',
'fwrite',
'glob',
'is_dir',
'is_file',
'is_link',
'is_readable',
'is_uploaded_file',
'opendir',
'parse_ini_string',
'pathinfo',
'pclose',
'readfile',
'readlink',
'realpath_cache_get',
'realpath_cache_size',
'realpath',
'rewind',
'readdir',
'set_file_buffer',
'tmpfile',
'umask',
'ini_set',
'set_time_limit',
'eval',
'exec',
'system',
'shell_exec',
'passthru',
'chgrp',
'chmod',
'chwown',
'file_put_contents',
'file',
'fileatime',
'filectime',
'filegroup',
'fileinode',
'filemtime',
'fileowner',
'fileperms',
'fopen',
'is_executable',
'is_writable',
'is_writeable',
'lchgrp',
'lchown',
'linkinfo',
'lstat',
'mkdir',
'mkdir_recursive',
'parse_ini_file',
'rmdir',
'rmdir_recursive',
'stat',
'tempnam',
'touch',
'unlink',
'getimagesize',
'call_user_func',
'call_user_func_array',
'create_function',
//mutliple files per function call
'copy',
'copy_recursive',
'link',
'rename',
'symlink',
'move_uploaded_file',
'chdir',
'chroot',
'create_cache_directory',
'mk_temp_dir',
'write_array_to_file',
'write_encoded_file',
'create_custom_directory',
'sugar_rename',
'sugar_chown',
'sugar_fopen',
'sugar_mkdir',
'sugar_file_put_contents',
'sugar_file_put_contents_atomic',
'sugar_chgrp',
'sugar_chmod',
'sugar_touch',
// Functions that have callbacks can circumvent our security measures.
// List retrieved through PHP's XML documentation, and running the
// following script in the reference directory:
// grep -R callable . | grep -v \.svn | grep methodparam | cut -d: -f1 | sort -u | cut -d"." -f2 | sed 's/\-/\_/g' | cut -d"/" -f4
// AMQPQueue
'consume',
// PHP internal - arrays
'array_diff_uassoc',
'array_diff_ukey',
'array_filter',
'array_intersect_uassoc',
'array_intersect_ukey',
'array_map',
'array_reduce',
'array_udiff_assoc',
'array_udiff_uassoc',
'array_udiff',
'array_uintersect_assoc',
'array_uintersect_uassoc',
'array_uintersect',
'array_walk_recursive',
'array_walk',
'uasort',
'uksort',
'usort',
// EIO functions that accept callbacks.
'eio_busy',
'eio_chmod',
'eio_chown',
'eio_close',
'eio_custom',
'eio_dup2',
'eio_fallocate',
'eio_fchmod',
'eio_fchown',
'eio_fdatasync',
'eio_fstat',
'eio_fstatvfs',
'eio_fsync',
'eio_ftruncate',
'eio_futime',
'eio_grp',
'eio_link',
'eio_lstat',
'eio_mkdir',
'eio_mknod',
'eio_nop',
'eio_open',
'eio_read',
'eio_readahead',
'eio_readdir',
'eio_readlink',
'eio_realpath',
'eio_rename',
'eio_rmdir',
'eio_sendfile',
'eio_stat',
'eio_statvfs',
'eio_symlink',
'eio_sync_file_range',
'eio_sync',
'eio_syncfs',
'eio_truncate',
'eio_unlink',
'eio_utime',
'eio_write',
// PHP internal - error functions
'set_error_handler',
'set_exception_handler',
// Forms Data Format functions
'fdf_enum_values',
// PHP internal - function handling
'call_user_func_array',
'call_user_func',
'forward_static_call_array',
'forward_static_call',
'register_shutdown_function',
'register_tick_function',
// Gearman
'setclientcallback',
'setcompletecallback',
'setdatacallback',
'setexceptioncallback',
'setfailcallback',
'setstatuscallback',
'setwarningcallback',
'setworkloadcallback',
'addfunction',
// Firebird/InterBase
'ibase_set_event_handler',
// LDAP
'ldap_set_rebind_proc',
// LibXML
'libxml_set_external_entity_loader',
// Mailparse functions
'mailparse_msg_extract_part_file',
'mailparse_msg_extract_part',
'mailparse_msg_extract_whole_part_file',
// Memcache(d) functions
'addserver',
'setserverparams',
'get',
'getbykey',
'getdelayed',
'getdelayedbykey',
// MySQLi
'set_local_infile_handler',
// PHP internal - network functions
'header_register_callback',
// Newt
'newt_entry_set_filter',
'newt_set_suspend_callback',
// OAuth
'consumerhandler',
'timestampnoncehandler',
'tokenhandler',
// PHP internal - output control
'ob_start',
// PHP internal - PCNTL
'pcntl_signal',
// PHP internal - PCRE
'preg_replace_callback',
// SQLite
'sqlitecreateaggregate',
'sqlitecreatefunction',
'sqlite_create_aggregate',
'sqlite_create_function',
// RarArchive
'open',
// Readline
'readline_callback_handler_install',
'readline_completion_function',
// PHP internal - session handling
'session_set_save_handler',
// PHP internal - SPL
'construct',
'iterator_apply',
'spl_autoload_register',
// Sybase
'sybase_set_message_handler',
// PHP internal - variable handling
'is_callable',
// XML Parser
'xml_set_character_data_handler',
'xml_set_default_handler',
'xml_set_element_handler',
'xml_set_end_namespace_decl_handler',
'xml_set_external_entity_ref_handler',
'xml_set_notation_decl_handler',
'xml_set_processing_instruction_handler',
'xml_set_start_namespace_decl_handler',
'xml_set_unparsed_entity_decl_handler',
'simplexml_load_file',
'simplexml_load_string',
// unzip
'unzip',
'unzip_file',
);
private $methodsBlackList = array(
'setlevel',
'put' => array('sugarautoloader'),
'unlink' => array('sugarautoloader')
);
public function printToWiki()
{
echo "'''Default Extensions''' ' .str_replace('{PACKAGE}', $package, translate('ML_PACKAGE_NOT_CONFIRM')). '
";
foreach ($this->validExt as $b) {
echo '#' . $b . '
';
}
echo "'''Default Black Listed Functions'''
";
foreach ($this->blackList as $b) {
echo '#' . $b . '
';
}
}
/**
* ModuleScanner constructor.
*/
public function __construct()
{
$params = array(
'blackListExempt' => 'MODULE_INSTALLER_PACKAGE_SCAN_BLACK_LIST_EXEMPT',
'blackList' => 'MODULE_INSTALLER_PACKAGE_SCAN_BLACK_LIST',
'classBlackListExempt' => 'MODULE_INSTALLER_PACKAGE_SCAN_CLASS_BLACK_LIST_EXEMPT',
'classBlackList' => 'MODULE_INSTALLER_PACKAGE_SCAN_CLASS_BLACK_LIST',
'validExt' => 'MODULE_INSTALLER_PACKAGE_SCAN_VALID_EXT',
'methodsBlackList' => 'MODULE_INSTALLER_PACKAGE_SCAN_METHOD_LIST',
);
$disableConfigOverride = defined('MODULE_INSTALLER_DISABLE_CONFIG_OVERRIDE')
&& MODULE_INSTALLER_DISABLE_CONFIG_OVERRIDE;
$disableDefineOverride = defined('MODULE_INSTALLER_DISABLE_DEFINE_OVERRIDE')
&& MODULE_INSTALLER_DISABLE_DEFINE_OVERRIDE;
if (!$disableConfigOverride && !empty($GLOBALS['sugar_config']['moduleInstaller'])) {
$this->config = $GLOBALS['sugar_config']['moduleInstaller'];
}
foreach ($params as $param => $constName) {
if (!$disableConfigOverride && isset($this->config[$param]) && is_array($this->config[$param])) {
$this->{$param} = array_merge($this->{$param}, $this->config[$param]);
}
if (!$disableDefineOverride && defined($constName)) {
$value = constant($constName);
$value = explode(',', $value);
$value = array_map('trim', $value);
$value = array_filter($value, 'strlen');
$this->{$param} = array_merge($this->{$param}, $value);
}
}
}
private $issues = array();
private $pathToModule = '';
/**
*returns a list of issues
*/
public function getIssues()
{
return $this->issues;
}
/**
*returns true or false if any issues were found
*/
public function hasIssues()
{
return !empty($this->issues);
}
/**
*Ensures that a file has a valid extension
*/
public function isValidExtension($file)
{
$file = strtolower($file);
$pi = pathinfo($file);
//make sure they don't override the files.md5
if (empty($pi['extension']) || $pi['basename'] == 'files.md5') {
return false;
}
return in_array($pi['extension'], $this->validExt);
}
public function isConfigFile($file)
{
$real = realpath($file);
if ($real == realpath("config.php")) {
return true;
}
if (file_exists("config_override.php") && $real == realpath("config_override.php")) {
return true;
}
return false;
}
/**
*Scans a directory and calls on scan file for each file
**/
public function scanDir($path)
{
static $startPath = '';
if (empty($startPath)) {
$startPath = $path;
}
if (!is_dir($path)) {
return false;
}
$d = dir($path);
while ($e = $d->read()) {
$next = $path . '/' . $e;
if (is_dir($next)) {
if (substr($e, 0, 1) == '.') {
continue;
}
$this->scanDir($next);
} else {
$issues = $this->scanFile($next);
}
}
return true;
}
/**
* Check if the file contents looks like PHP
* @param string $contents File contents
* @return boolean
*/
public function isPHPFile($contents)
{
if (stripos($contents, 'isValidExtension($file)) {
$issues[] = translate('ML_INVALID_EXT');
$this->issues['file'][$file] = $issues;
return $issues;
}
if ($this->isConfigFile($file)) {
$issues[] = translate('ML_OVERRIDE_CORE_FILES');
$this->issues['file'][$file] = $issues;
return $issues;
}
$contents = file_get_contents($file);
if (!$this->isPHPFile($contents)) {
return $issues;
}
$tokens = @token_get_all($contents);
$checkFunction = false;
$possibleIssue = '';
$lastToken = false;
foreach ($tokens as $index=>$token) {
if (is_string($token[0])) {
switch ($token[0]) {
case '`':
$issues['backtick'] = translate('ML_INVALID_FUNCTION') . " '`'";
// no break
case '(':
if ($checkFunction) {
$issues[] = $possibleIssue;
}
break;
}
$checkFunction = false;
$possibleIssue = '';
} else {
$token['_msi'] = token_name($token[0]);
switch ($token[0]) {
case T_WHITESPACE: break;
case T_EVAL:
if (in_array('eval', $this->blackList) && !in_array('eval', $this->blackListExempt)) {
$issues[]= translate('ML_INVALID_FUNCTION') . ' eval()';
}
break;
case T_STRING:
$token[1] = strtolower($token[1]);
if ($lastToken !== false && $lastToken[0] == T_NEW) {
if (!in_array($token[1], $this->classBlackList)) {
break;
}
if (in_array($token[1], $this->classBlackListExempt)) {
break;
}
} elseif ($token[0] == T_DOUBLE_COLON) {
if (!in_array($lastToken[1], $this->classBlackList)) {
break;
}
if (in_array($lastToken[1], $this->classBlackListExempt)) {
break;
}
} else {
//if nothing else fit, lets check the last token to see if this is a possible method call
if ($lastToken !== false &&
($lastToken[0] == T_OBJECT_OPERATOR || $lastToken[0] == T_DOUBLE_COLON)) {
// check static blacklist for methods
if (!empty($this->methodsBlackList[$token[1]])) {
if ($this->methodsBlackList[$token[1]] == '*') {
$issues[]= translate('ML_INVALID_METHOD') . ' ' .$token[1]. '()';
break;
} else {
if ($lastToken[0] == T_DOUBLE_COLON && $index > 2 && $tokens[$index-2][0] == T_STRING) {
$classname = strtolower($tokens[$index-2][1]);
if (in_array($classname, $this->methodsBlackList[$token[1]])) {
$issues[]= translate('ML_INVALID_METHOD') . ' ' .$classname . '::' . $token[1]. '()';
break;
}
}
}
}
//this is a method call, check the black list
if (in_array($token[1], $this->methodsBlackList)) {
$issues[]= translate('ML_INVALID_METHOD') . ' ' .$token[1]. '()';
}
break;
}
if (!in_array($token[1], $this->blackList)) {
break;
}
if (in_array($token[1], $this->blackListExempt)) {
break;
}
}
// no break
case T_VARIABLE:
$checkFunction = true;
$possibleIssue = translate('ML_INVALID_FUNCTION') . ' ' . $token[1] . '()';
break;
default:
$checkFunction = false;
$possibleIssue = '';
}
if ($token[0] != T_WHITESPACE) {
$lastToken = $token;
}
}
}
if (!empty($issues)) {
$this->issues['file'][$file] = $issues;
}
return $issues;
}
/**
* checks files.md5 file to see if the file is from sugar
* ONLY WORKS ON FILES
*
* @param string $path
* @return bool
*/
public function sugarFileExists($path)
{
static $md5 = array();
if (empty($md5) && file_exists('files.md5')) {
include('files.md5');
$md5 = isset($md5_string) ? $md5_string : null;
}
if ($path[0] !== '.' || $path[1] !== '/') {
$path = './' . $path;
}
if (isset($md5[$path])) {
return true;
}
return false;
}
/**
* Normalize a path to not contain dots & multiple slashes
*
* @param string $path
* @return string false
*/
public function normalizePath($path)
{
if (DIRECTORY_SEPARATOR !== '/') {
// convert to / for OSes that use other separators
$path = str_replace(DIRECTORY_SEPARATOR, '/', $path);
}
$res = array();
foreach (explode("/", $path) as $component) {
if (empty($component)) {
continue;
}
if ($component === '.') {
continue;
}
if ($component === '..') {
// this is not allowed, bail
return false;
}
$res[] = $component;
}
return join("/", $res);
}
/**
*This function will scan the Manifest for disabled actions specified in $GLOBALS['sugar_config']['moduleInstaller']['disableActions']
*if $GLOBALS['sugar_config']['moduleInstaller']['disableRestrictedCopy'] is set to false or not set it will call on scanCopy to ensure that it is not overriding files
*/
public function scanManifest($manifestPath)
{
$issues = array();
if (!file_exists($manifestPath)) {
$this->issues['manifest'][$manifestPath] = translate('ML_NO_MANIFEST');
return $issues;
}
$fileIssues = $this->scanFile($manifestPath);
//if the manifest contains malicious code do not open it
if (!empty($fileIssues)) {
return $fileIssues;
}
$this->lockConfig();
list($manifest, $installdefs) = MSLoadManifest($manifestPath);
$fileIssues = $this->checkConfig($manifestPath);
if (!empty($fileIssues)) {
return $fileIssues;
}
//scan for disabled actions
if (isset($this->config['disableActions'])) {
foreach ($this->config['disableActions'] as $action) {
if (isset($installdefs[$this->manifestMap[$action]])) {
$issues[] = translate('ML_INVALID_ACTION_IN_MANIFEST') . $this->manifestMap[$action];
}
}
}
// now lets scan for files that will override our files
if (empty($this->config['disableRestrictedCopy']) && isset($installdefs['copy'])) {
foreach ($installdefs['copy'] as $copy) {
$from = $this->normalizePath($copy['from']);
if ($from === false) {
$this->issues['copy'][$copy['from']] = translate('ML_PATH_MAY_NOT_CONTAIN') .
' ".." -' . $copy['from'];
continue;
}
$from = str_replace(''.str_replace('{PACKAGE}', $package, translate('ML_PACKAGE_SCANNING')). '
' . translate('ML_INSTALLATION_FAILED') . '