]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - ModuleInstall/ModuleScanner.php
Release 6.5.8
[Github/sugarcrm.git] / ModuleInstall / ModuleScanner.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38 class ModuleScanner{
39         private $manifestMap = array(
40                         'pre_execute'=>'pre_execute',
41                         'install_mkdirs'=>'mkdir',
42                         'install_copy'=>'copy',
43                         'install_images'=>'image_dir',
44                         'install_menus'=>'menu',
45                         'install_userpage'=>'user_page',
46                         'install_dashlets'=>'dashlets',
47                         'install_administration'=>'administration',
48                         'install_connectors'=>'connectors',
49                         'install_vardefs'=>'vardefs',
50                         'install_layoutdefs'=>'layoutdefs',
51                         'install_layoutfields'=>'layoutfields',
52                         'install_relationships'=>'relationships',
53                         'install_languages'=>'language',
54             'install_logichooks'=>'logic_hooks',
55                         'post_execute'=>'post_execute',
56
57         );
58
59         /**
60          * config settings
61          * @var array
62          */
63         private $config = array();
64         private $config_hash;
65
66         private $blackListExempt = array();
67         private $classBlackListExempt = array();
68
69         private $validExt = array('png', 'gif', 'jpg', 'css', 'js', 'php', 'txt', 'html', 'htm', 'tpl', 'pdf', 'md5', 'xml');
70         private $classBlackList = array(
71         // Class names specified here must be in lowercase as the implementation
72         // of the tokenizer converts all tokens to lowercase.
73         'reflection',
74         'reflectionclass',
75         'reflectionzendextension',
76         'reflectionextension',
77         'reflectionfunction',
78         'reflectionfunctionabstract',
79         'reflectionmethod',
80         'reflectionobject',
81         'reflectionparameter',
82         'reflectionproperty',
83         'reflector',
84         'reflectionexception',
85         'lua',
86     );
87         private $blackList = array(
88     'popen',
89     'proc_open',
90     'escapeshellarg',
91     'escapeshellcmd',
92     'proc_close',
93     'proc_get_status',
94     'proc_nice',
95         'passthru',
96     'clearstatcache',
97     'disk_free_space',
98     'disk_total_space',
99     'diskfreespace',
100     'fclose',
101     'feof',
102     'fflush',
103     'fgetc',
104     'fgetcsv',
105     'fgets',
106     'fgetss',
107     'file_exists',
108     'file_get_contents',
109     'filesize',
110     'filetype',
111     'flock',
112     'fnmatch',
113     'fpassthru',
114     'fputcsv',
115     'fputs',
116     'fread',
117     'fscanf',
118     'fseek',
119     'fstat',
120     'ftell',
121     'ftruncate',
122     'fwrite',
123     'glob',
124     'is_dir',
125     'is_file',
126     'is_link',
127     'is_readable',
128     'is_uploaded_file',
129     'parse_ini_string',
130     'pathinfo',
131     'pclose',
132     'readfile',
133     'readlink',
134     'realpath_cache_get',
135     'realpath_cache_size',
136     'realpath',
137     'rewind',
138     'set_file_buffer',
139     'tmpfile',
140     'umask',
141     'ini_set',
142         'eval',
143         'exec',
144         'system',
145         'shell_exec',
146         'passthru',
147         'chgrp',
148         'chmod',
149         'chwown',
150         'file_put_contents',
151         'file',
152         'fileatime',
153         'filectime',
154         'filegroup',
155         'fileinode',
156         'filemtime',
157         'fileowner',
158         'fileperms',
159         'fopen',
160         'is_executable',
161         'is_writable',
162         'is_writeable',
163         'lchgrp',
164         'lchown',
165         'linkinfo',
166         'lstat',
167         'mkdir',
168     'mkdir_recursive',
169         'parse_ini_file',
170         'rmdir',
171     'rmdir_recursive',
172         'stat',
173         'tempnam',
174         'touch',
175         'unlink',
176         'getimagesize',
177         'call_user_func',
178         'call_user_func_array',
179         'create_function',
180
181
182         //mutliple files per function call
183         'copy',
184     'copy_recursive',
185         'link',
186         'rename',
187         'symlink',
188         'move_uploaded_file',
189         'chdir',
190         'chroot',
191         'create_cache_directory',
192         'mk_temp_dir',
193         'write_array_to_file',
194         'write_encoded_file',
195         'create_custom_directory',
196         'sugar_rename',
197         'sugar_chown',
198         'sugar_fopen',
199         'sugar_mkdir',
200         'sugar_file_put_contents',
201         'sugar_chgrp',
202         'sugar_chmod',
203         'sugar_touch',
204
205         // Functions that have callbacks can circumvent our security measures.
206         // List retrieved through PHP's XML documentation, and running the
207         // following script in the reference directory:
208
209         // grep -R callable . | grep -v \.svn | grep methodparam | cut -d: -f1 | sort -u | cut -d"." -f2 | sed 's/\-/\_/g' | cut -d"/" -f4
210
211         // AMQPQueue
212         'consume',
213
214         // PHP internal - arrays
215         'array_diff_uassoc',
216         'array_diff_ukey',
217         'array_filter',
218         'array_intersect_uassoc',
219         'array_intersect_ukey',
220         'array_map',
221         'array_reduce',
222         'array_udiff_assoc',
223         'array_udiff_uassoc',
224         'array_udiff',
225         'array_uintersect_assoc',
226         'array_uintersect_uassoc',
227         'array_uintersect',
228         'array_walk_recursive',
229         'array_walk',
230         'uasort',
231         'uksort',
232         'usort',
233
234         // EIO functions that accept callbacks.
235         'eio_busy',
236         'eio_chmod',
237         'eio_chown',
238         'eio_close',
239         'eio_custom',
240         'eio_dup2',
241         'eio_fallocate',
242         'eio_fchmod',
243         'eio_fchown',
244         'eio_fdatasync',
245         'eio_fstat',
246         'eio_fstatvfs',
247         'eio_fsync',
248         'eio_ftruncate',
249         'eio_futime',
250         'eio_grp',
251         'eio_link',
252         'eio_lstat',
253         'eio_mkdir',
254         'eio_mknod',
255         'eio_nop',
256         'eio_open',
257         'eio_read',
258         'eio_readahead',
259         'eio_readdir',
260         'eio_readlink',
261         'eio_realpath',
262         'eio_rename',
263         'eio_rmdir',
264         'eio_sendfile',
265         'eio_stat',
266         'eio_statvfs',
267         'eio_symlink',
268         'eio_sync_file_range',
269         'eio_sync',
270         'eio_syncfs',
271         'eio_truncate',
272         'eio_unlink',
273         'eio_utime',
274         'eio_write',
275
276         // PHP internal - error functions
277         'set_error_handler',
278         'set_exception_handler',
279
280         // Forms Data Format functions
281         'fdf_enum_values',
282
283         // PHP internal - function handling
284         'call_user_func_array',
285         'call_user_func',
286         'forward_static_call_array',
287         'forward_static_call',
288         'register_shutdown_function',
289         'register_tick_function',
290
291         // Gearman
292         'setclientcallback',
293         'setcompletecallback',
294         'setdatacallback',
295         'setexceptioncallback',
296         'setfailcallback',
297         'setstatuscallback',
298         'setwarningcallback',
299         'setworkloadcallback',
300         'addfunction',
301
302         // Firebird/InterBase
303         'ibase_set_event_handler',
304
305         // LDAP
306         'ldap_set_rebind_proc',
307
308         // LibXML
309         'libxml_set_external_entity_loader',
310
311         // Mailparse functions
312         'mailparse_msg_extract_part_file',
313         'mailparse_msg_extract_part',
314         'mailparse_msg_extract_whole_part_file',
315
316         // Memcache(d) functions
317         'addserver',
318         'setserverparams',
319         'get',
320         'getbykey',
321         'getdelayed',
322         'getdelayedbykey',
323
324         // MySQLi
325         'set_local_infile_handler',
326
327         // PHP internal - network functions
328         'header_register_callback',
329
330         // Newt
331         'newt_entry_set_filter',
332         'newt_set_suspend_callback',
333
334         // OAuth
335         'consumerhandler',
336         'timestampnoncehandler',
337         'tokenhandler',
338
339         // PHP internal - output control
340         'ob_start',
341
342         // PHP internal - PCNTL
343         'pcntl_signal',
344
345         // PHP internal - PCRE
346         'preg_replace_callback',
347
348         // SQLite
349         'sqlitecreateaggregate',
350         'sqlitecreatefunction',
351         'sqlite_create_aggregate',
352         'sqlite_create_function',
353
354         // RarArchive
355         'open',
356
357         // Readline
358         'readline_callback_handler_install',
359         'readline_completion_function',
360
361         // PHP internal - session handling
362         'session_set_save_handler',
363
364         // PHP internal - SPL
365         'construct',
366         'iterator_apply',
367         'spl_autoload_register',
368
369         // Sybase
370         'sybase_set_message_handler',
371
372         // PHP internal - variable handling
373         'is_callable',
374
375         // XML Parser
376         'xml_set_character_data_handler',
377         'xml_set_default_handler',
378         'xml_set_element_handler',
379         'xml_set_end_namespace_decl_handler',
380         'xml_set_external_entity_ref_handler',
381         'xml_set_notation_decl_handler',
382         'xml_set_processing_instruction_handler',
383         'xml_set_start_namespace_decl_handler',
384         'xml_set_unparsed_entity_decl_handler',
385 );
386
387         public function printToWiki(){
388                 echo "'''Default Extensions'''<br>";
389                 foreach($this->validExt as $b){
390                         echo '#' . $b . '<br>';
391
392                 }
393                 echo "'''Default Black Listed Functions'''<br>";
394                 foreach($this->blackList as $b){
395                         echo '#' . $b . '<br>';
396
397                 }
398
399         }
400
401         public function __construct(){
402             if(!empty($GLOBALS['sugar_config']['moduleInstaller'])) {
403                 $this->config = $GLOBALS['sugar_config']['moduleInstaller'];
404             }
405
406                 if(!empty($this->config['blackListExempt'])){
407                         $this->blackListExempt = array_merge($this->blackListExempt, $this->config['blackListExempt']);
408                 }
409                 if(!empty($this->config['blackList'])){
410                         $this->blackList = array_merge($this->blackList, $this->config['blackList']);
411                 }
412         if(!empty($this->config['classBlackListExempt'])){
413             $this->classBlackListExempt = array_merge($this->classBlackListExempt, $this->config['classBlackListExempt']);
414         }
415         if(!empty($this->config['classBlackList'])){
416             $this->classBlackList = array_merge($this->classBlackList, $this->config['classBlackList']);
417         }
418           if(!empty($this->config['validExt'])){
419                         $this->validExt = array_merge($this->validExt, $this->config['validExt']);
420                 }
421
422         }
423
424         private $issues = array();
425         private $pathToModule = '';
426
427         /**
428          *returns a list of issues
429          */
430         public function getIssues(){
431                 return $this->issues;
432         }
433
434         /**
435          *returns true or false if any issues were found
436          */
437         public function hasIssues(){
438                 return !empty($this->issues);
439         }
440
441         /**
442          *Ensures that a file has a valid extension
443          */
444         private function isValidExtension($file){
445                 $file = strtolower($file);
446
447                 $extPos = strrpos($file, '.');
448                 //make sure they don't override the files.md5
449                 if($extPos === false || $file == 'files.md5')return false;
450                 $ext = substr($file, $extPos + 1);
451                 return in_array($ext, $this->validExt);
452
453         }
454
455         /**
456          *Scans a directory and calls on scan file for each file
457          **/
458         public function scanDir($path){
459                 static $startPath = '';
460                 if(empty($startPath))$startPath = $path;
461                 if(!is_dir($path))return false;
462                 $d = dir($path);
463                 while($e = $d->read()){
464                 $next = $path . '/' . $e;
465                 if(is_dir($next)){
466                         if(substr($e, 0, 1) == '.')continue;
467                         $this->scanDir($next);
468                 }else{
469                         $issues = $this->scanFile($next);
470
471
472                 }
473                 }
474         return true;
475         }
476
477         /**
478          * Check if the file contents looks like PHP
479          * @param string $contents File contents
480          * @return boolean
481          */
482         public function isPHPFile($contents)
483         {
484             if(stripos($contents, '<?php') !== false) return true;
485             for($tag=0;($tag = stripos($contents, '<?', $tag)) !== false;$tag++) {
486             if(strncasecmp(substr($contents, $tag, 13), '<?xml version', 13) == 0) {
487                 // <?xml version is OK, skip it
488                 $tag++;
489                 continue;
490             }
491             // found <?, it's PHP
492             return true;
493             }
494             return false;
495         }
496
497         /**
498          * Given a file it will open it's contents and check if it is a PHP file (not safe to just rely on extensions) if it finds <?php tags it will use the tokenizer to scan the file
499          * $var()  and ` are always prevented then whatever is in the blacklist.
500          * It will also ensure that all files are of valid extension types
501          *
502          */
503         public function scanFile($file){
504                 $issues = array();
505                 if(!$this->isValidExtension($file)){
506                         $issues[] = translate('ML_INVALID_EXT');
507                         $this->issues['file'][$file] = $issues;
508                         return $issues;
509                 }
510                 $contents = file_get_contents($file);
511                 if(!$this->isPHPFile($contents)) return $issues;
512                 $tokens = @token_get_all($contents);
513                 $checkFunction = false;
514                 $possibleIssue = '';
515                 $lastToken = false;
516                 foreach($tokens as $index=>$token){
517                         if(is_string($token[0])){
518                                 switch($token[0]){
519                                         case '`':
520                                                 $issues['backtick'] = translate('ML_INVALID_FUNCTION') . " '`'";
521                                         case '(':
522                                                 if($checkFunction)$issues[] = $possibleIssue;
523                                                 break;
524                                 }
525                                 $checkFunction = false;
526                                 $possibleIssue = '';
527                         }else{
528                                 $token['_msi'] = token_name($token[0]);
529                                 switch($token[0]){
530                                         case T_WHITESPACE: continue;
531                                         case T_EVAL:
532                                                 if(in_array('eval', $this->blackList) && !in_array('eval', $this->blackListExempt))
533                                                 $issues[]= translate('ML_INVALID_FUNCTION') . ' eval()';
534                                                 break;
535                                         case T_STRING:
536                                                 $token[1] = strtolower($token[1]);
537                                                 if($lastToken !== false && $lastToken[0] == T_NEW) {
538                             if(!in_array($token[1], $this->classBlackList))break;
539                             if(in_array($token[1], $this->classBlackListExempt))break;
540                         } elseif ($token[0] == T_DOUBLE_COLON) {
541                             if(!in_array($lastToken[1], $this->classBlackList))break;
542                             if(in_array($lastToken[1], $this->classBlackListExempt))break;
543                         } else {
544                             if(!in_array($token[1], $this->blackList))break;
545                             if(in_array($token[1], $this->blackListExempt))break;
546
547                             if ($lastToken !== false &&
548                             ($lastToken[0] == T_OBJECT_OPERATOR ||  $lastToken[0] == T_DOUBLE_COLON))
549                             {
550                                 break;
551                             }
552                         }
553                                         case T_VARIABLE:
554                                                 $checkFunction = true;
555                                                 $possibleIssue = translate('ML_INVALID_FUNCTION') . ' ' .  $token[1] . '()';
556                                                 break;
557
558                                         default:
559                                                 $checkFunction = false;
560                                                 $possibleIssue = '';
561
562                                 }
563                                 if ($token[0] != T_WHITESPACE)
564                                 {
565                                         $lastToken = $token;
566                                 }
567                         }
568
569                 }
570                 if(!empty($issues)){
571                         $this->issues['file'][$file] = $issues;
572                 }
573
574                 return $issues;
575         }
576
577
578         /*
579          * checks files.md5 file to see if the file is from sugar
580          * ONLY WORKS ON FILES
581          */
582         public function sugarFileExists($path){
583                 static $md5 = array();
584                 if(empty($md5) && file_exists('files.md5'))
585                 {
586                         include('files.md5');
587                         $md5 = $md5_string;
588                 }
589                 if(isset($md5['./' . $path]))return true;
590
591
592         }
593
594
595         /**
596          *This function will scan the Manifest for disabled actions specified in $GLOBALS['sugar_config']['moduleInstaller']['disableActions']
597          *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
598          */
599         public function scanManifest($manifestPath){
600                 $issues = array();
601                 if(!file_exists($manifestPath)){
602                         $this->issues['manifest'][$manifestPath] = translate('ML_NO_MANIFEST');
603                         return $issues;
604                 }
605                 $fileIssues = $this->scanFile($manifestPath);
606                 //if the manifest contains malicious code do not open it
607                 if(!empty($fileIssues)){
608                         return $fileIssues;
609                 }
610                 $this->lockConfig();
611                 list($manifest, $installdefs) = MSLoadManifest($manifestPath);
612                 $fileIssues = $this->checkConfig($manifestPath);
613                 if(!empty($fileIssues)){
614                         return $fileIssues;
615                 }
616
617                 //scan for disabled actions
618                 if(isset($this->config['disableActions'])){
619                         foreach($this->config['disableActions'] as $action){
620                                 if(isset($installdefs[$this->manifestMap[$action]])){
621                                         $issues[] = translate('ML_INVALID_ACTION_IN_MANIFEST') . $this->manifestMap[$action];
622                                 }
623                         }
624                 }
625
626                 //now lets scan for files that will override our files
627                 if(empty($this->config['disableRestrictedCopy']) && isset($installdefs['copy'])){
628                         foreach($installdefs['copy'] as $copy){
629                                 $from = str_replace('<basepath>', $this->pathToModule, $copy['from']);
630                                 $to = $copy['to'];
631                                 if(substr_count($from, '..')){
632                                         $this->issues['copy'][$from] = translate('ML_PATH_MAY_NOT_CONTAIN').' ".." -' . $from;
633                                 }
634                                 if(substr_count($to, '..')){
635                                         $this->issues['copy'][$to] = translate('ML_PATH_MAY_NOT_CONTAIN'). ' ".." -' . $to;
636                                 }
637                                 while(substr_count($from, '//')){
638                                         $from = str_replace('//', '/', $from);
639                                 }
640                                 while(substr_count($to, '//')){
641                                         $to = str_replace('//', '/', $to);
642                                 }
643                                 $this->scanCopy($from, $to);
644                         }
645                 }
646                 if(!empty($issues)){
647                         $this->issues['manifest'][$manifestPath] = $issues;
648                 }
649
650
651
652         }
653
654
655
656         /**
657          * Takes in where the file will is specified to be copied from and to
658          * and ensures that there is no official sugar file there. If the file exists it will check
659          * against the MD5 file list to see if Sugar Created the file
660          *
661          */
662         function scanCopy($from, $to){
663                                 //if the file doesn't exist for the $to then it is not overriding anything
664                                 if(!file_exists($to))return;
665                                 //if $to is a dir and $from is a file then make $to a full file path as well
666                                 if(is_dir($to) && is_file($from)){
667                                         if(substr($to,-1) === '/'){
668                                                 $to = substr($to, 0 , strlen($to) - 1);
669                                         }
670                                         $to .= '/'. basename($from);
671                                 }
672                                 //if the $to is a file and it is found in sugarFileExists then don't allow overriding it
673                                 if(is_file($to) && $this->sugarFileExists($to)){
674                                         $this->issues['copy'][$from] = translate('ML_OVERRIDE_CORE_FILES') . '(' . $to . ')';
675                                 }
676
677                                 if(is_dir($from)){
678                                         $d = dir($from);
679                                         while($e = $d->read()){
680                                                 if($e == '.' || $e == '..')continue;
681                                                 $this->scanCopy($from .'/'. $e, $to .'/' . $e);
682                                         }
683                                 }
684
685
686
687
688
689                         }
690
691
692         /**
693          *Main external function that takes in a path to a package and then scans
694          *that package's manifest for disabled actions and then it scans the PHP files
695          *for restricted function calls
696          *
697          */
698         public function scanPackage($path){
699                 $this->pathToModule = $path;
700                 $this->scanManifest($path . '/manifest.php');
701                 if(empty($this->config['disableFileScan'])){
702                         $this->scanDir($path);
703                 }
704         }
705
706         /**
707          *This function will take all issues of the current instance and print them to the screen
708          **/
709         public function displayIssues($package='Package'){
710                 echo '<h2>'.str_replace('{PACKAGE}' , $package ,translate('ML_PACKAGE_SCANNING')). '</h2><BR><h2 class="error">' . translate('ML_INSTALLATION_FAILED') . '</h2><br><p>' .str_replace('{PACKAGE}' , $package ,translate('ML_PACKAGE_NOT_CONFIRM')). '</p><ul><li>'. translate('ML_OBTAIN_NEW_PACKAGE') . '<li>' . translate('ML_RELAX_LOCAL').
711 '</ul></p><br>' . translate('ML_SUGAR_LOADING_POLICY') .  ' <a href=" http://kb.sugarcrm.com/custom/module-loader-restrictions-for-sugar-open-cloud/">' . translate('ML_SUGAR_KB') . '</a>.'.
712 '<br>' . translate('ML_AVAIL_RESTRICTION'). ' <a href=" http://developers.sugarcrm.com/wordpress/2009/08/14/module-loader-restrictions/">' . translate('ML_SUGAR_DZ') .  '</a>.<br><br>';
713
714
715                 foreach($this->issues as $type=>$issues){
716                         echo '<div class="error"><h2>'. ucfirst($type) .' ' .  translate('ML_ISSUES') . '</h2> </div>';
717                         echo '<div id="details' . $type . '" >';
718                         foreach($issues as $file=>$issue){
719                                 $file = str_replace($this->pathToModule . '/', '', $file);
720                                 echo '<div style="position:relative;left:10px"><b>' . $file . '</b></div><div style="position:relative;left:20px">';
721                                 if(is_array($issue)){
722                                         foreach($issue as $i){
723                                                 echo "$i<br>";
724                                         }
725                                 }else{
726                                         echo "$issue<br>";
727                                 }
728                                 echo "</div>";
729                         }
730                         echo '</div>';
731
732                 }
733                 echo "<br><input class='button' onclick='document.location.href=\"index.php?module=Administration&action=UpgradeWizard&view=module\"' type='button' value=\"" . translate('LBL_UW_BTN_BACK_TO_MOD_LOADER') . "\" />";
734
735         }
736
737         /**
738          * Lock config settings
739          */
740         public function lockConfig()
741         {
742             if(empty($this->config_hash)) {
743                 $this->config_hash = md5(serialize($GLOBALS['sugar_config']));
744             }
745         }
746
747         /**
748          * Check if config was modified. Return
749          * @param string $file
750          * @return array Errors if something wrong, false if no problems
751          */
752         public function checkConfig($file)
753         {
754             $config_hash_after = md5(serialize($GLOBALS['sugar_config']));
755             if($config_hash_after != $this->config_hash) {
756                 $this->issues['file'][$file] = array(translate('ML_CONFIG_OVERRIDE'));
757                 return $this->issues;
758             }
759             return false;
760         }
761
762 }
763
764 /**
765  * Load manifest file
766  * Outside of the class to isolate the context
767  * @param string $manifest_file
768  * @return array
769  */
770 function MSLoadManifest($manifest_file)
771 {
772         include( $manifest_file );
773         return array($manifest, $installdefs);
774 }
775
776 ?>