You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
652 lines
19 KiB
652 lines
19 KiB
<?php
|
|
/*******************************************************************
|
|
class.osticket.php
|
|
|
|
osTicket (sys) -> Config.
|
|
|
|
Core osTicket object: loads congfig and provides loggging facility.
|
|
|
|
Use osTicket::start(configId)
|
|
|
|
Peter Rotich <peter@osticket.com>
|
|
Copyright (c) 2006-2013 osTicket
|
|
http://www.osticket.com
|
|
|
|
Released under the GNU General Public License WITHOUT ANY WARRANTY.
|
|
See LICENSE.TXT for details.
|
|
|
|
vim: expandtab sw=4 ts=4 sts=4:
|
|
**********************************************************************/
|
|
|
|
require_once(INCLUDE_DIR.'class.csrf.php'); //CSRF token class.
|
|
require_once(INCLUDE_DIR.'class.migrater.php');
|
|
require_once(INCLUDE_DIR.'class.plugin.php');
|
|
require_once INCLUDE_DIR . 'class.message.php';
|
|
|
|
define('LOG_WARN',LOG_WARNING);
|
|
|
|
class osTicket {
|
|
|
|
var $loglevel=array(1=>'Error','Warning','Debug');
|
|
|
|
//Page errors.
|
|
var $errors;
|
|
|
|
//System
|
|
var $system;
|
|
|
|
|
|
|
|
|
|
var $warning;
|
|
var $message;
|
|
|
|
var $title; //Custom title. html > head > title.
|
|
var $headers;
|
|
var $pjax_extra;
|
|
|
|
var $config;
|
|
var $session;
|
|
var $csrf;
|
|
var $company;
|
|
var $plugins;
|
|
|
|
function __construct() {
|
|
|
|
require_once(INCLUDE_DIR.'class.config.php'); //Config helper
|
|
require_once(INCLUDE_DIR.'class.company.php');
|
|
|
|
if (!defined('DISABLE_SESSION') || !DISABLE_SESSION)
|
|
$this->session = osTicketSession::start(SESSION_TTL); // start DB based session
|
|
|
|
$this->config = new OsticketConfig();
|
|
|
|
$this->csrf = new CSRF('__CSRFToken__');
|
|
|
|
$this->company = new Company();
|
|
|
|
$this->plugins = new PluginManager();
|
|
}
|
|
|
|
function isSystemOnline() {
|
|
return ($this->getConfig() && $this->getConfig()->isHelpDeskOnline() && !$this->isUpgradePending());
|
|
}
|
|
|
|
function isUpgradePending() {
|
|
foreach (DatabaseMigrater::getUpgradeStreams(UPGRADE_DIR.'streams/') as $stream=>$hash)
|
|
if (strcasecmp($hash,
|
|
$this->getConfig()->getSchemaSignature($stream)))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
function getSession() {
|
|
return $this->session;
|
|
}
|
|
|
|
function getConfig() {
|
|
return $this->config;
|
|
}
|
|
|
|
function getDBSignature($namespace='core') {
|
|
return $this->getConfig()->getSchemaSignature($namespace);
|
|
}
|
|
|
|
function getVersion() {
|
|
return THIS_VERSION;
|
|
}
|
|
|
|
function getCSRF(){
|
|
return $this->csrf;
|
|
}
|
|
|
|
function getCSRFToken() {
|
|
return $this->getCSRF()->getToken();
|
|
}
|
|
|
|
function getCSRFFormInput() {
|
|
return $this->getCSRF()->getFormInput();
|
|
}
|
|
|
|
function validateCSRFToken($token) {
|
|
return ($token && $this->getCSRF()->validateToken($token));
|
|
}
|
|
|
|
function checkCSRFToken($name=false, $rotate=false) {
|
|
$name = $name ?: $this->getCSRF()->getTokenName();
|
|
$token = $_POST[$name] ?: $_SERVER['HTTP_X_CSRFTOKEN'];
|
|
if ($token && $this->validateCSRFToken($token)) {
|
|
if ($rotate) $this->getCSRF()->rotate();
|
|
return true;
|
|
}
|
|
|
|
$msg=sprintf(__('Invalid CSRF token [%1$s] on %2$s'),
|
|
Format::htmlchars($token), THISPAGE);
|
|
$this->logWarning(__('Invalid CSRF Token').' '.$name, $msg, false);
|
|
|
|
return false;
|
|
}
|
|
|
|
function getLinkToken() {
|
|
return md5($this->getCSRFToken().SECRET_SALT.session_id());
|
|
}
|
|
|
|
function validateLinkToken($token) {
|
|
return ($token && !strcasecmp($token, $this->getLinkToken()));
|
|
}
|
|
|
|
/* Replace Template Variables */
|
|
function replaceTemplateVariables($input, $vars=array()) {
|
|
|
|
$replacer = new VariableReplacer();
|
|
$replacer->assign(array_merge($vars,
|
|
array('url' => $this->getConfig()->getBaseUrl(),
|
|
'company' => $this->company)
|
|
));
|
|
|
|
return $replacer->replaceVars($input);
|
|
}
|
|
|
|
static function getVarScope() {
|
|
return array(
|
|
'url' => __("osTicket's base url (FQDN)"),
|
|
'company' => array('class' => 'Company', 'desc' => __('Company Information')),
|
|
);
|
|
}
|
|
|
|
function addExtraHeader($header, $pjax_script=false) {
|
|
$this->headers[md5($header)] = $header;
|
|
$this->pjax_extra[md5($header)] = $pjax_script;
|
|
}
|
|
|
|
function getExtraHeaders() {
|
|
return $this->headers;
|
|
}
|
|
function getExtraPjax() {
|
|
return $this->pjax_extra;
|
|
}
|
|
|
|
function setPageTitle($title) {
|
|
$this->title = $title;
|
|
}
|
|
|
|
function getPageTitle() {
|
|
return $this->title;
|
|
}
|
|
|
|
function getErrors() {
|
|
return $this->errors;
|
|
}
|
|
|
|
function setErrors($errors) {
|
|
$this->errors = $errors;
|
|
}
|
|
|
|
function getError() {
|
|
return $this->system['err'];
|
|
}
|
|
|
|
function setError($error) {
|
|
$this->system['error'] = $error;
|
|
}
|
|
|
|
function clearError() {
|
|
$this->setError('');
|
|
}
|
|
|
|
function getWarning() {
|
|
return $this->system['warning'];
|
|
}
|
|
|
|
function setWarning($warning) {
|
|
$this->system['warning'] = $warning;
|
|
}
|
|
|
|
function clearWarning() {
|
|
$this->setWarning('');
|
|
}
|
|
|
|
|
|
function getNotice() {
|
|
return $this->system['notice'];
|
|
}
|
|
|
|
function setNotice($notice) {
|
|
$this->system['notice'] = $notice;
|
|
}
|
|
|
|
function clearNotice() {
|
|
$this->setNotice('');
|
|
}
|
|
|
|
|
|
function alertAdmin($subject, $message, $log=false) {
|
|
|
|
//Set admin's email address
|
|
if (!($to = $this->getConfig()->getAdminEmail()))
|
|
$to = ADMIN_EMAIL;
|
|
|
|
//append URL to the message
|
|
$message.="\n\n".$this->getConfig()->getBaseUrl();
|
|
|
|
//Try getting the alert email.
|
|
$email=null;
|
|
if(!($email=$this->getConfig()->getAlertEmail()))
|
|
$email=$this->getConfig()->getDefaultEmail(); //will take the default email.
|
|
|
|
if($email) {
|
|
$email->sendAlert($to, $subject, $message, null, array('text'=>true, 'reply-tag'=>false));
|
|
} else {//no luck - try the system mail.
|
|
Mailer::sendmail($to, $subject, $message, '"'.__('osTicket Alerts').sprintf('" <%s>',$to));
|
|
}
|
|
|
|
//log the alert? Watch out for loops here.
|
|
if($log)
|
|
$this->log(LOG_CRIT, $subject, $message, false); //Log the entry...and make sure no alerts are resent.
|
|
|
|
}
|
|
|
|
function logDebug($title, $message, $force=false) {
|
|
return $this->log(LOG_DEBUG, $title, $message, false, $force);
|
|
}
|
|
|
|
function logInfo($title, $message, $alert=false) {
|
|
return $this->log(LOG_INFO, $title, $message, $alert);
|
|
}
|
|
|
|
function logWarning($title, $message, $alert=true) {
|
|
return $this->log(LOG_WARN, $title, $message, $alert);
|
|
}
|
|
|
|
function logError($title, $error, $alert=true) {
|
|
return $this->log(LOG_ERR, $title, $error, $alert);
|
|
}
|
|
|
|
function logDBError($title, $error, $alert=true) {
|
|
|
|
if($alert && !$this->getConfig()->alertONSQLError())
|
|
$alert =false;
|
|
|
|
$e = new Exception();
|
|
$bt = str_replace(ROOT_DIR, _S(/* `root` is a root folder */ '(root)').'/',
|
|
$e->getTraceAsString());
|
|
$error .= nl2br("\n\n---- "._S('Backtrace')." ----\n".$bt);
|
|
|
|
// Prevent recursive loops through this code path
|
|
if (substr_count($bt, __FUNCTION__) > 1)
|
|
return;
|
|
|
|
return $this->log(LOG_ERR, $title, $error, $alert);
|
|
}
|
|
|
|
function log($priority, $title, $message, $alert=false, $force=false) {
|
|
|
|
//We are providing only 3 levels of logs. Windows style.
|
|
switch($priority) {
|
|
case LOG_EMERG:
|
|
case LOG_ALERT:
|
|
case LOG_CRIT:
|
|
case LOG_ERR:
|
|
$level=1; //Error
|
|
break;
|
|
case LOG_WARN:
|
|
case LOG_WARNING:
|
|
$level=2; //Warning
|
|
break;
|
|
case LOG_NOTICE:
|
|
case LOG_INFO:
|
|
case LOG_DEBUG:
|
|
default:
|
|
$level=3; //Debug
|
|
}
|
|
|
|
$loglevel=array(1=>'Error','Warning','Debug');
|
|
|
|
$info = array(
|
|
'title' => &$title,
|
|
'level' => $loglevel[$level],
|
|
'level_id' => $level,
|
|
'body' => &$message,
|
|
);
|
|
Signal::send('syslog', null, $info);
|
|
|
|
//Logging everything during upgrade.
|
|
if($this->getConfig()->getLogLevel()<$level && !$force)
|
|
return false;
|
|
|
|
//Alert admin if enabled...
|
|
$alert = $alert && !$this->isUpgradePending();
|
|
if ($alert && $this->getConfig()->getLogLevel() >= $level)
|
|
$this->alertAdmin($title, $message);
|
|
|
|
//Save log based on system log level settings.
|
|
$sql='INSERT INTO '.SYSLOG_TABLE.' SET created=NOW(), updated=NOW() '
|
|
.',title='.db_input(Format::sanitize($title, true))
|
|
.',log_type='.db_input($loglevel[$level])
|
|
.',log='.db_input(Format::sanitize($message, false))
|
|
.',ip_address='.db_input($_SERVER['REMOTE_ADDR']);
|
|
|
|
db_query($sql, false);
|
|
|
|
return true;
|
|
}
|
|
|
|
function purgeLogs() {
|
|
|
|
if(!($gp=$this->getConfig()->getLogGracePeriod()) || !is_numeric($gp))
|
|
return false;
|
|
|
|
//System logs
|
|
$sql='DELETE FROM '.SYSLOG_TABLE.' WHERE DATE_ADD(created, INTERVAL '.$gp.' MONTH)<=NOW()';
|
|
db_query($sql);
|
|
|
|
//TODO: Activity logs
|
|
|
|
return true;
|
|
}
|
|
/*
|
|
* Util functions
|
|
*
|
|
*/
|
|
|
|
function get_var($index, $vars, $default='', $type=null) {
|
|
|
|
if(is_array($vars)
|
|
&& array_key_exists($index, $vars)
|
|
&& (!$type || gettype($vars[$index])==$type))
|
|
return $vars[$index];
|
|
|
|
return $default;
|
|
}
|
|
|
|
function get_db_input($index, $vars, $quote=true) {
|
|
return db_input($this->get_var($index, $vars), $quote);
|
|
}
|
|
|
|
function get_path_info() {
|
|
if(isset($_SERVER['PATH_INFO']))
|
|
return $_SERVER['PATH_INFO'];
|
|
|
|
if(isset($_SERVER['ORIG_PATH_INFO']))
|
|
return $_SERVER['ORIG_PATH_INFO'];
|
|
|
|
//TODO: conruct possible path info.
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Fetch the current version(s) of osTicket softwares via DNS. The
|
|
* constants of MAJOR_VERSION, THIS_VERSION, and GIT_VERSION will be
|
|
* consulted to arrive at the most relevant version code for the latest
|
|
* release.
|
|
*
|
|
* Parameters:
|
|
* $product - (string|default:'core') the product to fetch versions for
|
|
* $major - (string|optional) optional major version to compare. This is
|
|
* useful if more than one version is available. Only versions
|
|
* specifying this major version ('m') are considered as version
|
|
* candidates.
|
|
*
|
|
* Dns:
|
|
* The DNS zone will have TXT records for the product will be published
|
|
* in this format:
|
|
*
|
|
* "v=1; m=1.9; V=1.9.11; c=deadbeef"
|
|
*
|
|
* Where the string is a semicolon-separated string of key/value pairs
|
|
* with the following meanings:
|
|
*
|
|
* --+--------------------------
|
|
* v | DNS record format version
|
|
*
|
|
* For v=1, this is the meaning of the other keys
|
|
* --+-------------------------------------------
|
|
* m | (optional) major product version
|
|
* V | Full product version (usually a git tag)
|
|
* c | Git commit id of the release tag
|
|
* s | Schema signature of the version, which might help detect
|
|
* | required migration
|
|
*
|
|
* Returns:
|
|
* (string|bool|null)
|
|
* - 'v1.9.11' or 'deadbeef' if release tag or git commit id seems to
|
|
* be most appropriate based on the value of GIT_VERSION
|
|
* - null if the $major version is no longer supported
|
|
* - false if no information is available in DNS
|
|
*/
|
|
function getLatestVersion($product='core', $major=null) {
|
|
$records = dns_get_record($product.'.updates.osticket.com', DNS_TXT);
|
|
if (!$records)
|
|
return false;
|
|
|
|
$versions = array();
|
|
foreach ($records as $r) {
|
|
$txt = $r['txt'];
|
|
$info = array();
|
|
foreach (explode(';', $r['txt']) as $kv) {
|
|
list($k, $v) = explode('=', $kv);
|
|
if (!($k = trim($k)))
|
|
continue;
|
|
$info[$k] = trim($v);
|
|
}
|
|
$versions[] = $info;
|
|
}
|
|
foreach ($versions as $info) {
|
|
switch ($info['v']) {
|
|
case '1':
|
|
if ($major && $info['m'] && $info['m'] != $major)
|
|
continue 2;
|
|
if ($product == 'core' && GIT_VERSION == '$git')
|
|
return $info['c'];
|
|
return $info['V'];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* getTrustedProxies
|
|
*
|
|
* Get defined trusted proxies
|
|
*/
|
|
|
|
static function getTrustedProxies() {
|
|
static $proxies = null;
|
|
// Parse trusted proxies from config file
|
|
if (!isset($proxies) && defined('TRUSTED_PROXIES'))
|
|
$proxies = array_filter(
|
|
array_map('trim', explode(',', TRUSTED_PROXIES)));
|
|
|
|
return $proxies ?: array();
|
|
}
|
|
|
|
/*
|
|
* getLocalNetworkAddresses
|
|
*
|
|
* Get defined local network addresses
|
|
*/
|
|
static function getLocalNetworkAddresses() {
|
|
static $ips = null;
|
|
// Parse local addreses from config file
|
|
if (!isset($ips) && defined('LOCAL_NETWORKS'))
|
|
$ips = array_filter(
|
|
array_map('trim', explode(',', LOCAL_NETWORKS)));
|
|
|
|
return $ips ?: array();
|
|
}
|
|
|
|
static function get_root_path($dir) {
|
|
|
|
/* If run from the commandline, DOCUMENT_ROOT will not be set. It is
|
|
* also likely that the ROOT_PATH will not be necessary, so don't
|
|
* bother attempting to figure it out.
|
|
*
|
|
* Secondly, if the directory of main.inc.php is the same as the
|
|
* document root, the the ROOT path truly is '/'
|
|
*/
|
|
if(!isset($_SERVER['DOCUMENT_ROOT'])
|
|
|| !strcasecmp($_SERVER['DOCUMENT_ROOT'], $dir))
|
|
return '/';
|
|
|
|
/* The main idea is to try and use full-path filename of PHP_SELF and
|
|
* SCRIPT_NAME. The SCRIPT_NAME should be the path of that script
|
|
* inside the DOCUMENT_ROOT. This is most likely useful if osTicket
|
|
* is run using something like Apache UserDir setting where the
|
|
* DOCUMENT_ROOT of Apache and the installation path of osTicket
|
|
* have nothing in comon.
|
|
*
|
|
* +---------------------------+-------------------+----------------+
|
|
* | PHP Script | SCRIPT_NAME | ROOT_PATH |
|
|
* +---------------------------+-------------------+----------------+
|
|
* | /home/u1/www/osticket/... | /~u1/osticket/... | /~u1/osticket/ |
|
|
* +---------------------------+-------------------+----------------+
|
|
*
|
|
* The algorithm will remove the directory of main.inc.php from
|
|
* as seen. What's left should be the script executed inside
|
|
* the osTicket installation. That is removed from SCRIPT_NAME.
|
|
* What's left is the ROOT_PATH.
|
|
*/
|
|
$bt = debug_backtrace(false);
|
|
$frame = array_pop($bt);
|
|
$file = str_replace('\\','/', $frame['file']);
|
|
$path = substr($file, strlen(ROOT_DIR));
|
|
if($path && ($pos=strpos($_SERVER['SCRIPT_NAME'], $path))!==false)
|
|
return ($pos) ? substr($_SERVER['SCRIPT_NAME'], 0, $pos) : '/';
|
|
|
|
if (self::is_cli())
|
|
return '/';
|
|
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
* get_client_ip
|
|
*
|
|
* Get client IP address from "Http_X-Forwarded-For" header by following a
|
|
* chain of trusted proxies.
|
|
*
|
|
* "Http_X-Forwarded-For" header value is a comma+space separated list of IP
|
|
* addresses, the left-most being the original client, and each successive
|
|
* proxy that passed the request all the way to the originating IP address.
|
|
*
|
|
*/
|
|
static function get_client_ip($header='HTTP_X_FORWARDED_FOR') {
|
|
|
|
// Request IP
|
|
$ip = $_SERVER['REMOTE_ADDR'];
|
|
// Trusted proxies.
|
|
$proxies = self::getTrustedProxies();
|
|
// Return current IP address if header is not set and
|
|
// request is not from a trusted proxy.
|
|
if (!isset($_SERVER[$header])
|
|
|| !$proxies
|
|
|| !self::is_trusted_proxy($ip, $proxies))
|
|
return $ip;
|
|
|
|
// Get chain of proxied ip addresses
|
|
$ips = array_map('trim', explode(',', $_SERVER[$header]));
|
|
// Add request IP to the chain
|
|
$ips[] = $ip;
|
|
// Walk the chain in reverse - remove invalid IPs
|
|
$ips = array_reverse($ips);
|
|
foreach ($ips as $k => $ip) {
|
|
// Make sure the IP is valid and not a trusted proxy
|
|
if ($k && !Validator::is_ip($ip))
|
|
unset($ips[$k]);
|
|
elseif ($k && !self::is_trusted_proxy($ip, $proxies))
|
|
return $ip;
|
|
}
|
|
|
|
// We trust the 400 lb hacker... return left most valid IP
|
|
return array_pop($ips);
|
|
}
|
|
|
|
/*
|
|
* Checks if the IP is that of a trusted proxy
|
|
*
|
|
*/
|
|
static function is_trusted_proxy($ip, $proxies=array()) {
|
|
$proxies = $proxies ?: self::getTrustedProxies();
|
|
// We don't have any proxies set.
|
|
if (!$proxies)
|
|
return false;
|
|
// Wildcard set - trust all proxies
|
|
else if ($proxies == '*')
|
|
return true;
|
|
|
|
return ($proxies && Validator::check_ip($ip, $proxies));
|
|
}
|
|
|
|
/**
|
|
* is_local_ip
|
|
*
|
|
* Check if a given IP is part of defined local address blocks
|
|
*
|
|
*/
|
|
static function is_local_ip($ip, $ips=array()) {
|
|
$ips = $ips
|
|
?: self::getLocalNetworkAddresses()
|
|
?: array();
|
|
|
|
foreach ($ips as $addr) {
|
|
if (Validator::check_ip($ip, $addr))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns TRUE if the request was made via HTTPS and false otherwise
|
|
*/
|
|
function is_https() {
|
|
|
|
// Local server flags
|
|
if (isset($_SERVER['HTTPS'])
|
|
&& strtolower($_SERVER['HTTPS']) == 'on')
|
|
return true;
|
|
|
|
// Check if SSL was terminated by a loadbalancer
|
|
return (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])
|
|
&& !strcasecmp($_SERVER['HTTP_X_FORWARDED_PROTO'], 'https'));
|
|
}
|
|
|
|
/**
|
|
* Returns TRUE if the current browser is IE and FALSE otherwise
|
|
*/
|
|
function is_ie() {
|
|
if (preg_match('/MSIE|Internet Explorer|Trident\/[\d]{1}\.[\d]{1,2}/',
|
|
$_SERVER['HTTP_USER_AGENT']))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/* returns true if script is being executed via commandline */
|
|
static function is_cli() {
|
|
return (!strcasecmp(substr(php_sapi_name(), 0, 3), 'cli')
|
|
|| (!isset($_SERVER['REQUEST_METHOD']) &&
|
|
!isset($_SERVER['HTTP_HOST']))
|
|
//Fallback when php-cgi binary is used via cli
|
|
);
|
|
}
|
|
|
|
/**** static functions ****/
|
|
function start() {
|
|
// Prep basic translation support
|
|
Internationalization::bootstrap();
|
|
|
|
if(!($ost = new osTicket()))
|
|
return null;
|
|
|
|
// Bootstrap installed plugins
|
|
$ost->plugins->bootstrap();
|
|
|
|
// Mirror content updates to the search backend
|
|
$ost->searcher = new SearchInterface();
|
|
|
|
return $ost;
|
|
}
|
|
}
|
|
|
|
?>
|