app.inc.php 12.6 KB
Newer Older
tbrehm's avatar
tbrehm committed
1
<?php
2
3
/**
Copyright (c) 2007-2022, Till Brehm, projektfarm Gmbh
tbrehm's avatar
tbrehm committed
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name of ISPConfig nor the names of its contributors
      may be used to endorse or promote products derived from this software without
      specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

30
31
32
33
// Set timezone
if(isset($conf['timezone']) && $conf['timezone'] != '') {	// note: !empty($conf['timezone']) should give the same result and is more idiomatic for current versions of PHP (gwyneth 20220315)
	date_default_timezone_set($conf['timezone']);
}
34

35
36
37
38
39
40
41
42
43
/**
 * Class for defining (mostly static) methods that are commonly used across the whole application.
 *
 * @category unknown
 * @package server
 * @author Till Brehm
 * @license bsd-3-clause
 * @link empty
 **/
tbrehm's avatar
tbrehm committed
44
class app {
45
46
47
48
49
	/** @var array	List of modules that have been loaded. */
	var $loaded_modules = [];
	/** @var array	List of plugins that have been loaded. */
	var $loaded_plugins = [];
	/** @var callable	Script calling this. */
50
	var $_calling_script = '';
51
	/** @var resource?	Database used for ISPConfig3. */
52
	public $db;
53

54
55
56
57
58
59
	/**
	 * Class constructor, which depends on the global configuration stored in $conf.
	 *
	 * @param void
	 * @return void
	 */
60
	function __construct() {
61
		/** @var object Global object storing this application's configuration. */
62
63
64
		global $conf;

		if($conf['start_db'] == true) {
65
			$this->load('db_' . $conf['db_type']);
66
67
			try {
				$this->db = new db;
68
			} catch(Exception $e) {
69
70
				$this->db = false;
			}
tbrehm's avatar
tbrehm committed
71

72
			/*
73
74
75
				Initialize the connection to the master DB,
				if we are in a multiserver setup
			*/
tbrehm's avatar
tbrehm committed
76

77
			if($conf['dbmaster_host'] != '' && ($conf['dbmaster_host'] != $conf['db_host'] || ($conf['dbmaster_host'] == $conf['db_host'] && $conf['dbmaster_database'] != $conf['db_database']))) {
78
79
80
81
82
				try {
					$this->dbmaster = new db($conf['dbmaster_host'], $conf['dbmaster_user'], $conf['dbmaster_password'], $conf['dbmaster_database'], $conf['dbmaster_port'], $conf['dbmaster_client_flags']);
				} catch (Exception $e) {
					$this->dbmaster = false;
				}
83
84
85
86
			} else {
				$this->dbmaster = $this->db;
			}
		}
87
	} // end constructor
88

89
90
91
92
93
94
95
	/**
	 * Getter method for some of the (valid) proprieties.
	 *
	 * @param string $name	A valid property name to get. Will be checked for validity first!
	 *
	 * @return mixed
	 */
96
	public function __get($name) {
97
98
		/** @var array List of all possible proprieties that are valid to get. */
		$valid_names = ['functions', 'getconf', 'letsencrypt', 'modules', 'plugins', 'services', 'system'];
99
100
101
102
103
104
105
106
107
108
109
110
111
		if(!in_array($name, $valid_names)) {
			trigger_error('Undefined property ' . $name . ' of class app', E_USER_WARNING);
		}
		if(property_exists($this, $name)) {
			return $this->{$name};
		}
		$this->uses($name);
		if(property_exists($this, $name)) {
			return $this->{$name};
		} else {
			trigger_error('Undefined property ' . $name . ' of class app', E_USER_WARNING);
		}
	}
112

113
114
115
116
117
118
119
	/**
	 * Sets the calling script.
	 *
	 * @param callable $caller	Calling script function.
	 *
	 * @return void
	 */
120
121
122
	function setCaller($caller) {
		$this->_calling_script = $caller;
	}
123

124
125
126
127
128
129
130
131
132
	/**
 	* Gets the calling script.
	*
	* Note that there is no error checking!
 	*
 	* @param void
 	*
 	* @return callable|null
 	*/
133
134
135
	function getCaller() {
		return $this->_calling_script;
	}
136

137
138
139
140
141
142
143
	/**
	 * Emergency exit funcion.
	 *
	 * @param string $errmsg	Error message to be displayedby the die() command on exit.
	 *
	 * @return void
	 */
144
145
	function forceErrorExit($errmsg = 'undefined') {
		global $conf;
146

147
148
149
150
151
152
		if($this->_calling_script == 'server') {
			@unlink($conf['temppath'] . $conf['fs_div'] . '.ispconfig_lock');
		}
		die('Exiting because of error: ' . $errmsg);
	}

153
154
155
156
157
158
159
160
161
162
163
	/**
	 * Dynamic plugin loader and instantiator.
	 *
	 * This will include PHP scripts on demand, each representing a class to be loaded,
	 * and if the process succeeds, it will retrieve an instance for the class.
	 *
	 * @param string $classes	A list of plugin classes to be loaded (e.g. their files will be included)
	 *							and subsequently instantiated; it's a comma-separated string.
	 *
	 * @return void
	 */
164
	function uses($classes) {
jwarnier's avatar
jwarnier committed
165
166
		global $conf;

167
		/** @var array|null List of classes to be used, as an array, after successful 'explosion' */
168
		$cl = explode(',', $classes);
jwarnier's avatar
jwarnier committed
169
170
171
		if(is_array($cl)) {
			foreach($cl as $classname) {
				if(!@is_object($this->$classname)) {
172
173
					if(is_file($conf['classpath'] . '/' . $classname . '.inc.php') && (DEVSYSTEM || !is_link($conf['classpath'] . '/' . $classname . '.inc.php'))) {
						include_once $conf['classpath'] . '/' . $classname . '.inc.php';
jwarnier's avatar
jwarnier committed
174
						$this->$classname = new $classname;
tbrehm's avatar
tbrehm committed
175
176
177
					}
				}
			}
jwarnier's avatar
jwarnier committed
178
		}
179
	}
tbrehm's avatar
tbrehm committed
180

181
182
183
184
185
186
187
188
189
190
191
	/**
 	* Dynamic plugin loader (no instantation).
 	*
	* Similar to uses() but does _not_ instantate a new class; files are merely included.
	* die() is called on a failure to include the file for a class.
	*
	* @param string $classes	A list of plugin classes to be loaded (e.g. their files will be included);
	*							it's a comma-separated string.
	*
 	* @return void
 	*/
192
	function load($classes) {
jwarnier's avatar
jwarnier committed
193
194
		global $conf;

195
		/** @var array|null List of classes to be loaded, as an array, after successful 'explosion' */
196
		$cl = explode(',', $classes);
jwarnier's avatar
jwarnier committed
197
198
		if(is_array($cl)) {
			foreach($cl as $classname) {
199
200
				if(is_file($conf['classpath'] . '/' . $classname . '.inc.php') && (DEVSYSTEM || !is_link($conf['classpath'] . '/' . $classname . '.inc.php'))) {
					include_once $conf['classpath'] . '/' . $classname . '.inc.php';
jwarnier's avatar
jwarnier committed
201
				} else {
202
					die('Unable to load: ' . $conf['classpath'] . '/' . $classname . '.inc.php');
tbrehm's avatar
tbrehm committed
203
204
				}
			}
jwarnier's avatar
jwarnier committed
205
		}
206
	}
tbrehm's avatar
tbrehm committed
207

208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
	/**
  	* Logs a message with a certain priority to the different log backends.
  	*
  	* This method will check if the priority is equal or larger than what the user has
	* defined as the minimum logging level, and will output to several logging facilities:
	*  - At the very least, the message will _usually_ go to stdout;
	*  - It may optionally also go to the file log (usually `/var/log/ispconfig/ispconfig.log`)
	*      which will be created if it doesn't exist;
	*  - When the $dblog parameter is set to true (the default), the message will also be logged
	*      to the database;
	*  - If the system is configured to send email messages to the administrator,
	*      this method will also handle those (assuming, again, that the priority matches).
	*
	* Debugging messages will also have the name of the calling module/script as well as a line number
	*   to assist error tracking (gwyneth 20220315). This incurs in a slight performance hit.
	*
  	* @param string $msg	The message to be logged.
  	* @param int $priority	Should be set to 0 = DEBUG, 1 = WARNING or 2 = ERROR; anything else
	*   will skip setting the priority textual variable.
	* @param bool $dblog	Should the message also be logged to the database? (Default is _true_)
  	*
  	* @return void
	*
	* @note The error() method below seems to write to an invalid priority (3), which will cause
	* no message priority text to be emitted, and will _force_ a database write and/or sending
	* an email to the administrator.
  	*/
235
	function log($msg, $priority = 0, $dblog = true) {
jwarnier's avatar
jwarnier committed
236
		global $conf;
237

238
239
240
241
242
243
244
245
246
247
248
		/**
		 * @var string $file_line_caller
		 *
		 * For debugging, deal with retrieving caller information from the stack. (gwyneth 20220315)
		 * See https://stackoverflow.com/q/1252529/1035977 (including the precious comments!) for an explanation
		 * of how this works.
		 **/
		$file_line_caller = "";
		/** @var string	Defined here because recent versions of PHP are stricter with scoping issues. (gwyneth 20220315) */
		$priority_txt = '';

249
		switch ($priority) {
250
		case 0:
251
252
253
				$priority_txt = 'DEBUG';
				$bt = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1);	// we don't need _all_ data, so we save some processing time here (gwyneth 20220315)
				$caller = array_shift($bt);
254
255
256
				if(!empty($caller['file']) && !empty($caller['line'])) {
					$file_line_caller = '[' . strtr(basename($caller['file'], '.php'), '_', ' ') . ':' . $caller['line'] . '] ';
				}
257
				break;
258
		case 1:
259
260
				$priority_txt = 'WARNING';
				break;
261
		case 2:
262
263
264
265
				$priority_txt = 'ERROR';
				break;
		// Note: $this->error() seems to use case 3 to deliberately skip setting a priority text.
		// It will also *force* a write to the logs and/or send emails. (gwyneth 20220315)
266
		}
jwarnier's avatar
jwarnier committed
267

268
		/** @var string Formatted message to be sent to the logging subsystems. */
269
		$log_msg = @date('d.m.Y-H:i') . ' - ' . $priority_txt . ' ' . $file_line_caller . '- '. $msg;
270
271

		// Check if the user-set priority defines that this message should be output at all.
jwarnier's avatar
jwarnier committed
272
		if($priority >= $conf['log_priority']) {
273
274
275
276
277
			// Prepare to add a line on the logfile, or to create the logfile in
			// append mode if it doesn't exist yet. Failure means that die() is called.

			//if(is_writable($conf["log_file"])) {
			if(!$fp = fopen($conf['log_file'], 'a')) {
278
279
280
				die('Unable to open logfile.');
			}

Thom's avatar
Thom committed
281
			if(!fwrite($fp, $log_msg . "\r\n")) {
282
283
				die('Unable to write to logfile.');
			}
tbrehm's avatar
tbrehm committed
284

285
			echo $log_msg . "\n";
286
287
			fclose($fp);

288
			// Log to database.
289
			if($dblog === true && isset($this->dbmaster)) {
290
291
				$server_id = $conf['server_id'];
				$loglevel = $priority;
292
				$message = $msg;
293
				$datalog_id = (isset($this->modules->current_datalog_id) && $this->modules->current_datalog_id > 0)? $this->modules->current_datalog_id : 0;
294
				if($datalog_id > 0) {
295
					$tmp_rec = $this->dbmaster->queryOneRecord("SELECT count(syslog_id) as number FROM sys_log WHERE datalog_id = ? AND loglevel = ?", $datalog_id, LOGLEVEL_ERROR);
296
297
					//* Do not insert duplicate errors into the web log.
					if($tmp_rec['number'] == 0) {
298
299
						$sql = "INSERT INTO sys_log (server_id,datalog_id,loglevel,tstamp,message) VALUES (?, ?, ?, UNIX_TIMESTAMP(), ?)";
						$this->dbmaster->query($sql, $server_id, $datalog_id, $loglevel, $message);
300
301
					}
				} else {
302
303
					$sql = "INSERT INTO sys_log (server_id,datalog_id,loglevel,tstamp,message) VALUES (?, 0, ?, UNIX_TIMESTAMP(), ?)";
					$this->dbmaster->query($sql, $server_id, $loglevel, $message);
304
				}
305
306
307
308
309
310
311
312
			}

			//} else {
			//    die("Unable to write to logfile.");
			//}

		} // if

313
		// Send an email to the administrator if the current priority demands it.
314
		if(isset($conf['admin_notify_priority']) && $priority >= $conf['admin_notify_priority'] && $conf['admin_mail'] != '') {
315
316
317
318
319
			if($conf['hostname'] != 'localhost' && $conf['hostname'] != '') {
				$hostname = $conf['hostname'];
			} else {
				$hostname = exec('hostname -f');
			}
320
			// Send notification to admin.
321
			$mailBody         = $hostname . " - " . $log_msg;
322
			$mailSubject      = substr("[" . $hostname . "]" . " " . $log_msg, 0, 70) . '...';
323
324
325
326
327
328
329
330
			$mailHeaders      = "MIME-Version: 1.0" . "\n";
			$mailHeaders     .= "Content-type: text/plain; charset=utf-8" . "\n";
			$mailHeaders     .= "Content-Transfer-Encoding: 8bit" . "\n";
			$mailHeaders     .= "From: ". $conf['admin_mail'] . "\n";
			$mailHeaders     .= "Reply-To: ". $conf['admin_mail'] . "\n";

			mail($conf['admin_mail'], $mailSubject, $mailBody, $mailHeaders);
		}
331
	} // func log
tbrehm's avatar
tbrehm committed
332

333
334
335
336
337
338
339
340
341
342
343
	/**
  	* Logs a message with an undefined priority (3) and dies.
  	*
  	* This method writes to an invalid/undefined priority level (3), which will cause
  	* no message priority text to be emitted, but will _force_ a database write and/or sending
  	* an email to the administrator.
  	*
  	* @param string $msg	The message to be logged.
  	*
  	* @return void
  	*/
344
	function error($msg) {
345
		$this->log($msg, 3);	// isn't this supposed to be error code 2? (gwyneth 20220315)
jwarnier's avatar
jwarnier committed
346
		die($msg);
347
	}
tbrehm's avatar
tbrehm committed
348
349
}

350
351
352
353
354
/**
 * @var \app $app
 *
 * Initialize application object.
 */
tbrehm's avatar
tbrehm committed
355
356
$app = new app;

357
?>