golden hour
/home/doctorbruno/public_html/taeionline.com/support/include
⬆️ Go Up
Upload
File/Folder
Size
Actions
.MANIFEST
194.97 KB
Del
OK
.htaccess
14 B
Del
OK
JSON.php
33.13 KB
Del
OK
PasswordHash.php
6.92 KB
Del
OK
Spyc.php
31.74 KB
Del
OK
UniversalClassLoader.php
8.61 KB
Del
OK
ajax.admin.php
7.29 KB
Del
OK
ajax.config.php
4.61 KB
Del
OK
ajax.content.php
9.76 KB
Del
OK
ajax.draft.php
13.03 KB
Del
OK
ajax.email.php
2.33 KB
Del
OK
ajax.export.php
1006 B
Del
OK
ajax.filter.php
874 B
Del
OK
ajax.forms.php
13.84 KB
Del
OK
ajax.i18n.php
5.1 KB
Del
OK
ajax.kbase.php
2.93 KB
Del
OK
ajax.note.php
2.05 KB
Del
OK
ajax.orgs.php
11.73 KB
Del
OK
ajax.plugins.php
2.15 KB
Del
OK
ajax.schedule.php
4.29 KB
Del
OK
ajax.search.php
12.72 KB
Del
OK
ajax.sequence.php
3.2 KB
Del
OK
ajax.staff.php
11.62 KB
Del
OK
ajax.tasks.php
33.87 KB
Del
OK
ajax.thread.php
8.81 KB
Del
OK
ajax.tickets.php
79.7 KB
Del
OK
ajax.tips.php
1.66 KB
Del
OK
ajax.upgrader.php
2.24 KB
Del
OK
ajax.users.php
18.33 KB
Del
OK
api.cron.php
1.06 KB
Del
OK
api.tickets.php
10.36 KB
Del
OK
class.2fa.php
7.76 KB
Del
OK
class.ajax.php
1.45 KB
Del
OK
class.api.php
15.31 KB
Del
OK
class.app.php
1.49 KB
Del
OK
class.attachment.php
6.95 KB
Del
OK
class.auth.php
50.73 KB
Del
OK
class.avatar.php
6.44 KB
Del
OK
class.banlist.php
2.58 KB
Del
OK
class.base32.php
4.07 KB
Del
OK
class.businesshours.php
7.08 KB
Del
OK
class.canned.php
9.05 KB
Del
OK
class.captcha.php
1.73 KB
Del
OK
class.category.php
11.14 KB
Del
OK
class.charset.php
3.41 KB
Del
OK
class.cli.php
9.48 KB
Del
OK
class.client.php
15.39 KB
Del
OK
class.collaborator.php
5.65 KB
Del
OK
class.company.php
2.65 KB
Del
OK
class.config.php
60.36 KB
Del
OK
class.controller.php
1.15 KB
Del
OK
class.cron.php
3.6 KB
Del
OK
class.crypto.php
18.92 KB
Del
OK
class.csrf.php
2.38 KB
Del
OK
class.dept.php
33.21 KB
Del
OK
class.dispatcher.php
7.12 KB
Del
OK
class.draft.php
6.23 KB
Del
OK
class.dynamic_forms.php
64.98 KB
Del
OK
class.email.php
49 KB
Del
OK
class.error.php
1.69 KB
Del
OK
class.export.php
32.92 KB
Del
OK
class.faq.php
15.05 KB
Del
OK
class.file.php
34.59 KB
Del
OK
class.filter.php
31.9 KB
Del
OK
class.filter_action.php
23.67 KB
Del
OK
class.format.php
44.3 KB
Del
OK
class.forms.php
195.58 KB
Del
OK
class.http.php
7.16 KB
Del
OK
class.i18n.php
24.28 KB
Del
OK
class.import.php
6.9 KB
Del
OK
class.json.php
2.71 KB
Del
OK
class.knowledgebase.php
5.8 KB
Del
OK
class.list.php
42 KB
Del
OK
class.lock.php
4.05 KB
Del
OK
class.log.php
1.55 KB
Del
OK
class.mail.php
35.76 KB
Del
OK
class.mailer.php
25.75 KB
Del
OK
class.mailfetch.php
9.72 KB
Del
OK
class.mailparse.php
30.99 KB
Del
OK
class.message.php
6.42 KB
Del
OK
class.migrater.php
5.2 KB
Del
OK
class.misc.php
7.5 KB
Del
OK
class.model.php
2.3 KB
Del
OK
class.nav.php
14.15 KB
Del
OK
class.note.php
2.39 KB
Del
OK
class.oauth2.php
4.04 KB
Del
OK
class.organization.php
22.45 KB
Del
OK
class.orm.php
120.9 KB
Del
OK
class.osticket.php
20.64 KB
Del
OK
class.ostsession.php
21.86 KB
Del
OK
class.page.php
10.59 KB
Del
OK
class.pagenate.php
5.53 KB
Del
OK
class.passwd.php
1.21 KB
Del
OK
class.pdf.php
3.83 KB
Del
OK
class.plugin.php
36.13 KB
Del
OK
class.priority.php
1.81 KB
Del
OK
class.queue.php
103.51 KB
Del
OK
class.report.php
11.76 KB
Del
OK
class.role.php
11.27 KB
Del
OK
class.schedule.php
46.28 KB
Del
OK
class.search.php
61.66 KB
Del
OK
class.sequence.php
7.27 KB
Del
OK
class.session.php
19.25 KB
Del
OK
class.setup.php
3.55 KB
Del
OK
class.signal.php
4.16 KB
Del
OK
class.sla.php
9.11 KB
Del
OK
class.staff.php
60.21 KB
Del
OK
class.task.php
56.21 KB
Del
OK
class.team.php
12.31 KB
Del
OK
class.template.php
23.45 KB
Del
OK
class.thread.php
108.96 KB
Del
OK
class.thread_actions.php
17.08 KB
Del
OK
class.ticket.php
167.39 KB
Del
OK
class.timezone.php
21.94 KB
Del
OK
class.topic.php
20.06 KB
Del
OK
class.translation.php
34.79 KB
Del
OK
class.upgrader.php
13.76 KB
Del
OK
class.user.php
43.4 KB
Del
OK
class.usersession.php
7.55 KB
Del
OK
class.util.php
10.07 KB
Del
OK
class.validator.php
12.94 KB
Del
OK
class.variable.php
11.93 KB
Del
OK
class.xml.php
3.23 KB
Del
OK
class.yaml.php
1.15 KB
Del
OK
cli
-
Del
OK
client
-
Del
OK
config
-
Del
OK
fpdf
-
Del
OK
htmLawed.php
53.53 KB
Del
OK
html2text.php
33.71 KB
Del
OK
i18n
-
Del
OK
index.php
37 B
Del
OK
laminas-mail
-
Del
OK
mpdf
-
Del
OK
mysqli.php
9.55 KB
Del
OK
ost-config.php
5.63 KB
Del
OK
ost-sampleconfig.php
6.24 KB
Del
OK
pear
-
Del
OK
plugins
-
Del
OK
staff
-
Del
OK
tnef_decoder.php
19.82 KB
Del
OK
upgrader
-
Del
OK
Edit: class.email.php
<?php /********************************************************************* class.email.php 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.'laminas-mail/vendor/autoload.php'); include_once INCLUDE_DIR.'class.role.php'; include_once(INCLUDE_DIR.'class.dept.php'); include_once(INCLUDE_DIR.'class.mail.php'); include_once(INCLUDE_DIR.'class.mailer.php'); include_once(INCLUDE_DIR.'class.oauth2.php'); include_once(INCLUDE_DIR.'class.mailfetch.php'); include_once(INCLUDE_DIR.'class.mailparse.php'); include_once(INCLUDE_DIR.'api.tickets.php'); class Email extends VerySimpleModel { static $meta = array( 'table' => EMAIL_TABLE, 'pk' => array('email_id'), 'joins' => array( 'priority' => array( 'constraint' => array('priority_id' => 'Priority.priority_id'), 'null' => true, ), 'dept' => array( 'constraint' => array('dept_id' => 'Dept.id'), 'null' => true, ), 'topic' => array( 'constraint' => array('topic_id' => 'Topic.topic_id'), 'null' => true, ), 'mailbox' => array( 'reverse' => 'MailBoxAccount.account', 'list' => false, 'null' => true, ), 'smtp' => array( 'reverse' => 'SmtpAccount.account', 'list' => false, 'null' => true, ), ) ); const PERM_BANLIST = 'emails.banlist'; static protected $perms = array( self::PERM_BANLIST => array( 'title' => /* @trans */ 'Banlist', 'desc' => /* @trans */ 'Ability to add/remove emails from banlist via ticket interface', 'primary' => true, )); private $stash; private $address; function getId() { return $this->email_id; } function __toString() { return $this->getAddress(); } // TODO: move stash/restore to a StashableTrait function restore($key, $drop=true) { if (($data = $this->stash($key, null)) && $drop) $this->stash[$key] = null; return $data; } function stash($key, $data=null) { if (!isset($this->stash)) $this->stash = &$_SESSION[':email'][$this->getId()]; // If data is null then stash is being pop-ed if (!isset($data) && $key && isset($this->stash[$key])) return $this->stash[$key]; // stash data if ($key && $data) $this->stash[$key] = $data; } function stashFormData(array $data) { $this->stash('formdata', array_filter($data)); } function restoreFormData($drop=true) { return $this->restore('formdata', $drop) ?: []; } function restoreErrors($drop=true) { return $this->restore('errors', $drop) ?: []; } function restoreNotice($drop=true) { return $this->restore('notice', $drop); } function getEmail() { return $this->email; } function getAddress() { if (!isset($this->address)) $this->address = $this->name ? sprintf('%s <%s>', $this->name, $this->email) : $this->email; return $this->address; } function getName() { return $this->name; } function getPriorityId() { return $this->priority_id; } function getDeptId() { return $this->dept_id; } function getDept() { return $this->dept; } function getTopicId() { return $this->topic_id; } function getTopic() { return $this->topic; } function autoRespond() { return !$this->noautoresp; } function getHashtable() { return $this->ht; } static function getSupportedAuthTypes() { static $auths = null; if (!isset($auths)) { $auths = []; // OAuth auth foreach (Oauth2AuthorizationBackend::allRegistered() as $id => $bk) $auths[$id] = $bk->getName(); // Basic authentication $auths['basic'] = sprintf('%s (%s)', __('Basic Authentication'), __('Legacy')); } return $auths; } static function getSupportedSMTPAuthTypes() { return array_merge([ 'mailbox' => sprintf('%s %s', __('Same as'), __('Remote Mailbox')), 'none' => sprintf('%s - %s', __('None'), __('No Authentication Required'))], self::getSupportedAuthTypes()); } function getInfo() { // Base information mimus objects $info = array_filter($this->getHashtable(), function($e) { return !is_object($e); }); // Remote Mailbox Info if (($mailbox=$this->getMailBoxAccount())) $info = array_merge($info, $mailbox->getInfo()); // SMTP Account Info if (($smtp=$this->getSmtpAccount())) $info = array_merge($info, $smtp->getInfo()); // Restore stahed formdata (if any) if ($_SERVER['REQUEST_METHOD'] == 'GET' && ($data=$this->restoreFormData())) $info = array_merge($info, $data); return $info; } function getMailBoxAccount($autoinit=true) { if (!$this->mailbox && isset($this->email_id) && $autoinit) $this->mailbox = MailBoxAccount::create([ 'email_id' => $this->email_id]); return $this->mailbox; } function getSmtpAccount($autoinit=true) { if (!$this->smtp && isset($this->email_id) && $autoinit) $this->smtp = SmtpAccount::create([ 'email_id' => $this->email_id]); return $this->smtp; } function getAuthAccount($which) { $account = null; switch ($which) { case 'mailbox': $account = $this->getMailBoxAccount(); break; case 'smtp': $account = $this->getSmtpAccount(); break; } return $account; } function send($to, $subject, $message, $attachments=null, $options=null, $cc=array()) { $mailer = new osTicket\Mail\Mailer($this); if($attachments) $mailer->addAttachments($attachments); return $mailer->send($to, $subject, $message, $options, $cc); } function sendAutoReply($to, $subject, $message, $attachments=null, $options=array()) { $options+= array('autoreply' => true); return $this->send($to, $subject, $message, $attachments, $options); } function sendAlert($to, $subject, $message, $attachments=null, $options=array()) { $options+= array('notice' => true); return $this->send($to, $subject, $message, $attachments, $options); } function delete() { global $cfg; //Make sure we are not trying to delete default emails. if(!$cfg || $this->getId()==$cfg->getDefaultEmailId() || $this->getId()==$cfg->getAlertEmailId()) //double...double check. return 0; if (!parent::delete()) return false; $type = array('type' => 'deleted'); Signal::send('object.deleted', $this, $type); // Delete email accounts if ($this->mailbox) $this->mailbox->delete(); if ($this->smtp) $this->smtp->delete(); Dept::objects() ->filter(array('email_id' => $this->getId())) ->update(array( 'email_id' => $cfg->getDefaultEmailId() )); Dept::objects() ->filter(array('autoresp_email_id' => $this->getId())) ->update(array( 'autoresp_email_id' => 0, )); return true; } function save($refetch=false) { if ($this->dirty) $this->updated = SqlFunction::NOW(); return parent::save($refetch || $this->dirty); } function update($vars, &$errors=false) { global $cfg; // very basic checks $vars['name'] = Format::striptags(trim($vars['name'])); $vars['email'] = trim($vars['email']); $id = isset($this->email_id) ? $this->getId() : 0; if ($id && $id != $vars['id']) $errors['err']=__('Get technical help!') .' '.__('Internal error occurred'); if (!$vars['email'] || !Validator::is_email($vars['email'])) { $errors['email']=__('Valid email required'); } elseif (($eid=Email::getIdByEmail($vars['email'])) && $eid != $id) { $errors['email']=__('Email already exists'); } elseif ($cfg && !strcasecmp($cfg->getAdminEmail(), $vars['email'])) { $errors['email']=__('Email already used as admin email!'); } elseif (Staff::getIdByEmail($vars['email'])) { //make sure the email doesn't belong to any of the staff $errors['email']=__('Email in use by an agent'); } if (!$vars['name']) $errors['name']=__('Email name required'); /* TODO: ??? $dept = Dept::lookup($vars['dept_id']); if($dept && !$dept->isActive()) $errors['dept_id'] = ''; $topic = Topic::lookup($vars['topic_id']); if($topic && !$topic->isActive()) $errors['topic_id'] = ''; */ // Remote Mailbox Settings if (($mailbox = $this->getMailBoxAccount())) $mailbox->update($vars, $errors); // SMTP Settings if (($smtp = $this->getSmtpAccount())) $smtp->update($vars, $errors); //abort on errors if ($errors) return false; if ($errors) return false; // Update basic settings $this->email = Format::sanitize($vars['email']); $this->name = Format::striptags($vars['name']); $this->dept_id = (int) $vars['dept_id']; $this->priority_id = (int) (isset($vars['priority_id']) ? $vars['priority_id'] : 0); $this->topic_id = (int) $vars['topic_id']; $this->noautoresp = (int) $vars['noautoresp']; $this->notes = Format::sanitize($vars['notes']); if ($this->save()) return true; if ($id) { //update $errors['err'] = sprintf(__('Unable to update %s.'), __('this email')) .' '.__('Internal error occurred'); } else { $errors['err'] = sprintf(__('Unable to add %s.'), __('this email')) .' '.__('Internal error occurred'); } return false; } static function getIdByEmail($email) { $qs = static::objects()->filter(Q::any(array( 'email' => $email, ))) ->values_flat('email_id'); $row = $qs->first(); return $row ? $row[0] : false; } static function create($vars=false) { $inst = new static($vars); $inst->created = SqlFunction::NOW(); return $inst; } static function getAddresses($options=array(), $flat=true) { $objects = static::objects(); if ($options['smtp']) $objects = $objects->filter(array('smtp__active' => 1)); if ($options['depts']) $objects = $objects->filter(array('dept_id__in'=>$options['depts'])); if (!$flat) return $objects; $addresses = array(); foreach ($objects->values_flat('email_id', 'email') as $row) { list($id, $email) = $row; $addresses[$id] = $email; } return $addresses; } static function getPermissions() { return self::$perms; } // Supported Remote Mailbox protocols static function mailboxProtocols() { return [ 'IMAP' => 'IMAP', 'POP' => 'POP']; } } RolePermission::register(/* @trans */ 'Miscellaneous', Email::getPermissions()); class EmailAccount extends VerySimpleModel { static $meta = array( 'table' => EMAIL_ACCOUNT_TABLE, 'pk' => array('id'), 'joins' => array( 'email' => array( 'constraint' => array('email_id' => 'Email.email_id'), ), ), ); private $bkId; private $form; private $cred; private $config; private $instance; // If account supports tls or ssl private $encryption = false; // Account settings private $settings; public function getAccountSetting($stashed=false) { if (!isset($this->settings)) { // Set properties to stashed form data (if any and requested) if ($stashed && ($info=$this->getInfo())) { foreach (['host', 'port', 'protocol'] as $p) { $k = "{$this->type}_$p"; if (isset($info[$k])) $this->{$p} = $info[$k]; } } $this->settings = new osTicket\Mail\AccountSetting($this); } return $this->settings; } public function getHostInfo() { return $this->getAccountSetting()->getHostInfo(); } public function getHost() { return $this->host; } public function getPort() { return $this->port; } public function getProtocol() { return $this->protocol; } public function getNumErrors() { return $this->num_errors; } public function isOAuthAuth() { return str_starts_with($this->getAuthBk(), 'oauth'); } public function isBasicAuth() { return str_starts_with($this->getAuthBk(), 'basic'); } public function isActive() { return ($this->active && $this->hasCredentials()); } // **** Don't use it **** // This routine is depricated and will be removed in the future - OAuth2 // Plugin uses it to check if the email accout has auth2 backend. public function isEnabled() { return $this->isOAuthAuth(); } public function isAuthBackendEnabled() { return $this->isOAuthAuth() ? (($i=$this->getOAuth2Instance()) && $i->isEnabled()) : true; } public function isStrict() { return $this->getConfig()->getStrictMatching(); } public function checkStrictMatching($token=null) { $token ??= $this->getAccessToken(); return ($token && $token->isMatch( $this->getEmail()->getEmail(), $this->isStrict())); } public function shouldAuthorize() { // check status and make sure it's oauth if (!$this->isAuthBackendEnabled() || !$this->isOAuthAuth()) return false; return (!($cred=$this->getFreshCredentials()) // Get token with signature match - mismatch means config // changed somehow || !($token=$cred->getAccessToken($this->getConfigSignature())) // Check if expired || $token->isExpired() // If Strict Matching is enabled ensure the email matches // the Resource Owner || !$this->checkStrictMatching($token)); } public function getId() { return $this->id; } public function getType() { return $this->type; } public function getAuthBk() { return $this->auth_bk; } public function getAuthId() { return $this->auth_id; } public function getBkId() { if (!isset($this->bkId)) { $id = sprintf('%s:%d', $this->getAuthBk(), $this->getId()); if ($this->isOAuthAuth()) $id .= sprintf(':%d:%b', $this->getAuthId(), $this->isStrict()); #TODO: Remove strict and delegate to email account $this->bkId = $id; } return $this->bkId; } public function getEmailId() { return $this->email_id; } public function getEmail() { return $this->email; } public function getName() { return $this->getEmail()->getName(); } public function getAccessToken() { $cred = $this->getFreshCredentials(); return $cred ? $cred->getAccessToken($this->getConfigSignature()) : null; } private function getOAuth2Backend($auth=null) { $auth = $auth ?: $this->getAuthBk(); return Oauth2AuthorizationBackend::getBackend($auth); } public function getOAuth2ConfigDefaults() { $email = $this->getEmail(); return [ 'auth_type' => 'autho', 'auth_name' => $email->getName(), 'name' => sprintf('%s (%s)', $email->getEmail(), $this->getType()), 'isactive' => 1, 'strict_matching' => $this->isStrict(), 'notes' => sprintf( __('OAuth2 Authorization for %s'), $email->getEmail()), ]; } public function getOAuth2ConfigInfo() { $vars = $this->getOAuth2ConfigDefaults(); if (($i=$this->getOAuth2Instance())) $vars = array_merge($vars, $i->getInfo()); return $vars; } private function getOAuth2ConfigForm($vars, $auth=null) { // Lookup OAuth2 backend & Get basic config form if (($bk=$this->getOAuth2Backend($auth))) return $bk->getConfigForm( array_merge( $this->getOAuth2ConfigDefaults(), $vars ?: $bk->getDefaults() #nolint ), !strcmp($auth, $this->getAuthBk()) ? $this->getAuthId() : null ); } private function getBasicAuthConfigForm($vars, $auth=null) { $creds = $this->getCredentialsVars($auth) ?: []; if (!$vars && $creds) { $vars = [ 'username' => $creds['username'], 'passwd' => $creds['password'], ]; } elseif (!$_POST && !isset($vars['username']) && $this->email) $vars['username'] = $this->email->getEmail(); if (!isset($vars['passwd']) && $_POST && $creds) $vars['passwd'] = $creds['password']; return new BasicAuthConfigForm($vars); } public function getOAuth2Instance($bk=null) { $bk = $bk ?: $this->getOAuth2Backend(); if (!isset($this->instance) && $this->getAuthId() && $bk) $this->instance = $bk->getPluginInstance($this->getAuthId()); return $this->instance; } public function getConfigSignature() { if (($i=$this->getOAuth2Instance())) return $i->getSignature(); } public function getInfo() { $ht = array(); foreach (static::$vars as $var) { if (isset($this->ht[$var])) $ht[$this->type.'_'.$var] = $this->ht[$var]; } // Add stashed info (if any) if (($data=$this->email->restoreFormData(false))) $ht = array_merge($ht, $data); return $ht; } private function getNamespace() { return sprintf('email.%d.account.%d', $this->getEmailId(), $this->getId()); } protected function getConfig() { if (!isset($this->config)) $this->config = new EmailAccountConfig($this->getNamespace()); return $this->config; } public function getAuthConfigForm($auth, $vars=false) { if (!isset($this->form) || strcmp($auth, $this->getAuthBk())) { list($type, $provider) = explode(':', $auth); switch ($type) { case 'oauth2': $this->form = $this->getOAuth2ConfigForm($vars, $auth); break; case 'basic': $this->form = $this->getBasicAuthConfigForm($vars, $auth); $setting = $this->getAccountSetting(true); if (!$setting || !$setting->isValid()) $this->form->setNotice( __('Host, Port & Protocol Required')); break; } } return $this->form; } public function saveAuth($auth, $form, &$errors) { // Validate the form if (!$form->isValid()) return false; $vars = $form->getClean(); list($type, $provider) = explode(':', $auth); switch ($type) { case 'basic': // Set username and password if (!$this->updateCredentials($auth, $vars, $errors) && !isset($errors['err'])) $errors['err'] = sprintf('%s %s', __('Error Saving'), __('Authentication')); break; case 'oauth2': // For OAuth we are simply saving configuration - // credetials are saved post successful authorization // redirect. // Lookup OAuth backend if (($bk=$this->getOAuth2Backend($auth))) { // Merge form data, post vars and any defaults $vars = array_merge($this->getOAuth2ConfigDefaults(), array_intersect_key($_POST, $this->getOAuth2ConfigDefaults()), $vars); // Update or add OAuth2 instance if ($this->getAuthId() && ($i=$bk->getPluginInstance($this->getAuthId()))) { $vars = array_merge($bk->getDefaults(), $vars); #nolint if ($i->update($vars, $errors)) { // Disable account if backend is changed if (strcasecmp($this->auth_bk, $auth)) $this->active = 0; // Auth backend can be changed on update $this->auth_bk = $auth; $this->save(); // Update Strict Matching $this->getConfig()->setStrictMatching($_POST['strict_matching'] ? 1 : 0); } elseif (!isset($errors['err'])) { $errors['err'] = sprintf('%s %s', __('Error Saving'), __('Authentication')); } } else { // Ask the backend to add OAuth2 instance for this account if (($i=$bk->addPluginInstance($vars, $errors))) { #nolint // Cache instance $this->instance = $i; $this->auth_bk = $auth; $this->auth_id = $i->getId(); $this->save(); } else { $errors['err'] = __('Error Adding OAuth2 Instance'); } } } break; default: $errors['err'] = __('Unknown Authentication Type'); } return !($errors); } public function logError($error) { return $this->logActivity($error); } public function hasCredentials() { return ($this->getFreshCredentials()); } private function getCredentialsVars($auth=null) { $vars = []; if (($cred = $this->getCredentials($auth))) $vars = $cred->toArray(); return $vars; } public function getFreshCredentials($auth=null) { return $this->getCredentials($auth, true); } public function getCredentials($auth=null, $refresh=false) { // Authentication doesn't match - it's getting reconfigured. if ($auth && strncasecmp($this->getAuthBk(), $auth, strlen($auth)) && !in_array($auth, ['none', 'mailbox'])) return []; if (!isset($this->cred) || $refresh) { $this->cred = $cred = null; $auth = $auth ?: $this->getAuthBk(); list($type, $provider) = explode(':', $auth); try { switch ($type) { case 'mailbox': $cred = $this->getMailBoxCredentials($refresh); break; case 'none': // No authentication required (open replay) $cred = new osTicket\Mail\NoAuthCredentials([ 'username' => $this->email->getEmail()]); break; case 'basic': $cred = $this->getBasicAuthCredentials(); break; case 'oauth2': $cred = $this->getOAuth2AuthCredentials($provider, $refresh); break; default: throw new Exception(sprintf('%s: %s', $type, __('Unknown Credential Type'))); } // Cache the credentials $this->cred = $cred; } catch (Exception $ex) { // Log the error $this->logError(sprintf('%s: %s', __('Credentials'), $ex->getMessage() )); } } return $this->cred; } private function getMailBoxCredentials($refresh=false) { if (($mb=$this->email->getMailBoxAccount()) && $mb->getAuthBk()) return $mb->getCredentials($mb->getAuthBk(), $refresh); } private function getBasicAuthCredentials() { if (($c=$this->getConfig()) && ($creds=$c->toArray()) && isset($creds['username']) && isset($creds['passwd'])) { return new osTicket\Mail\BasicAuthCredentials([ 'username' => $creds['username'], // Decrypt password 'password' => Crypto::decrypt($creds['passwd'], SECRET_SALT, md5($creds['username'].$this->getNamespace())) ]); } } private function updateBasicAuthCredentials($vars, &$errors) { // Get current credentials - we need to re-encrypt // password as username might be changing $creds = $this->getCredentialsVars('basic'); // password change? if (!$vars['username']) { $errors['username'] = __('Username Required'); } elseif (!$vars['passwd'] && !$creds['password']) { $errors['passwd'] = __('Password Required'); } elseif (($setting=$this->getAccountSetting(true)) && !$setting->isValid()) { $errors['err'] = implode(', ', $setting->getErrors()); } elseif ($setting && !$errors) { // Validate the credentials try { $cred = new osTicket\Mail\BasicAuthCredentials([ 'username' => $vars['username'], 'password' => $vars['passwd'] ?: $creds['password'], ]); if (!$this->validateCredentials($cred)) $errors['err'] = __('Invalid Credentials'); } catch (Exception $ex) { $errors['err'] = $ex->getMessage(); } } if (!$errors) { // Save credentials and get out of here. $info = [ // username 'username' => $vars['username'], // Encrypt password 'passwd' => Crypto::encrypt($vars['passwd'] ?: $creds['password'], SECRET_SALT, md5($vars['username'].$this->getNamespace())) ]; if (!$this->getConfig()->updateInfo($info)) $errors['err'] = sprintf('%s: %s', __('BasicAuth'), __('Error saving credentials')); } return !count($errors); } private function getOAuth2AuthCredentials($provider, $refresh=false) { if (!($c=$this->getConfig())) return false; $creds=$c->toArray(); // Decrypt Access Token if ($creds['access_token']) { $creds['access_token'] = Crypto::decrypt( $creds['access_token'], SECRET_SALT, md5($creds['resource_owner_email'].$this->getNamespace()) ); } // Decrypt Referesh Token if ($creds['refresh_token']) { $creds['refresh_token'] = Crypto::decrypt( $creds['refresh_token'], SECRET_SALT, md5($creds['resource_owner_email'].$this->getNamespace()) ); } try { // Init credentials and see of we need to // refresh the token $errors = []; $auth = sprintf('oauth2:%s', $provider); $class = 'osTicket\Mail\OAuth2AuthCredentials'; if (($cred=$class::init($creds)) && ($token=$cred->getToken()) && ($refresh && $token->isExpired()) && ($bk=$this->getOAuth2Backend()) && ($info=$bk->refreshAccessToken( #nolint $token->getRefreshToken(), $this->getBkId(), $errors)) && isset($info['access_token']) && $this->updateCredentials($auth, // Merge new access token with // already decrypted creds array_merge($creds, $info), $errors )) { return $this->getCredentials($auth, $refresh); } elseif ($errors) { // Throw an exception with the error throw new Exception($errors['refresh_token'] ?? __('Referesh Token Expired')); } } catch (Exception $ex) { // rethrow the exception including above. throw $ex; } return $cred; } private function updateOAuth2AuthCredentials($provider, $vars, &$errors) { $err = sprintf('%s_auth_bk', $this->getType()); if (!$vars['access_token']) { $errors[$err] = __('Access Token Required'); } elseif (!$vars['resource_owner_email'] || !Validator::is_email($vars['resource_owner_email'])) { $errors[$err] = __('Resource Owner Required'); } elseif ($this->isStrict() // When in Strict mode Account Email must match resource owner's // email. Strict mode can be disabled for a global admin to // authorized onbehalf of other user accounts or shared mailboxes. && strcasecmp($this->getEmail()->getEmail(), $vars['resource_owner_email'])) { $errors[$err] = sprintf(__('Strict Mode: Expecting Authorization for %s not %s'), $this->getEmail()->getEmail(), $vars['resource_owner_email']); } elseif (!$errors) { // Encrypt Access Token $vars['access_token'] = Crypto::encrypt( $vars['access_token'], SECRET_SALT, md5($vars['resource_owner_email'].$this->getNamespace())); // Encrypt Referesh Token if ($vars['refresh_token']) { $vars['refresh_token'] = Crypto::encrypt( $vars['refresh_token'], SECRET_SALT, md5($vars['resource_owner_email'].$this->getNamespace()) ); } $vars['config_signature'] = $this->getConfigSignature(); // TODO: Validate if (!$this->getConfig()->updateInfo($vars)) $errors['err'] = sprintf('oauth2:%s - %s', Format::htmlchars($provider), __('Error saving credentials')); } return !count($errors); } public function updateCredentials($auth, $vars, &$errors) { if (!$vars || $errors) return false; list($type, $provider) = explode(':', $auth); switch ($type) { case 'basic': if (!($this->updateBasicAuthCredentials($vars, $errors))) return false; break; case 'oauth2': if (!($this->updateOAuth2AuthCredentials($provider, $vars, $errors))) return false; break; default: $errors['err'] = sprintf('%s - %s', __('Unknown Authentication'), Format::htmlchars($auth)); } if ($errors) return false; // Save the auth backend $this->auth_bk = $auth; // Clear cached credentials $this->creds = null; return $this->save(); } /* * Destory the account config */ function destroyConfig() { return $this->getConfig()->destroy(); } function update($vars, &$errors) { return false; } public function logActivity($error=null, $now=null) { if (isset($error)) { $this->num_errors += 1; $this->last_error_msg = $error; $this->last_error = $now ?: SqlFunction::NOW(); } else { $this->num_errors = 0; $this->last_error = null; $this->last_error_msg = null; $this->last_activity = $now ?: SqlFunction::NOW(); } return $this->save(); } function save($refetch=false) { if ($this->dirty) { $this->updated = SqlFunction::NOW(); } return parent::save($refetch || $this->dirty); } function delete() { // Destroy the Email config $this->destroyConfig(); // Delete the Plugin instance if ($this->isOAuthAuth() && ($i=$this->getOAuth2Instance())) $i->delete(); // Delete the EmailAccount parent::delete(); } static function create($ht=false) { $i = new static($ht); $i->active = isset($ht['active']) ? $ht['active'] : 0; $i->created = SqlFunction::NOW(); return $i; } } class MailBoxAccount extends EmailAccount { static $meta = array( 'table' => EMAIL_ACCOUNT_TABLE, 'pk' => array('id'), 'joins' => array( 'email' => array( 'constraint' => array('email_id' => 'Email.email_id'), ), 'account' => array( 'constraint' => array( 'type' => "'mailbox'", 'email_id' => 'Email.email_id'), ), ), ); static protected $vars = [ 'active', 'host', 'port', 'protocol', 'auth_bk', 'folder', 'fetchfreq', 'fetchmax', 'postfetch', 'archivefolder' ]; private $cred; private $mailbox; static public function objects() { return parent::objects() ->filter(['type' => 'mailbox']); } //TODO: Morhp MailBoxAccount to ImapMailBoxAccount public function isImap() { return (strcasecmp($this->getProtocol(), 'IMAP') === 0); } //TODO: Morhp MailBoxAccount to PopMailBoxAccount public function isPop() { return (strcasecmp($this->getProtocol(), 'POP') === 0); } public function getProtocol() { return $this->protocol; } public function getFolder() { return $this->folder; } public function getFetchFolder() { return $this->getFolder(); } public function getArchiveFolder() { return ($this->isImap() && $this->archivefolder) ? $this->archivefolder : null; } public function canDeleteEmails() { return !strcasecmp($this->postfetch, 'delete'); } public function getMaxFetch() { return $this->fetchmax; } protected function validateCredentials(osTicket\Mail\AuthCredentials $creds) { return $this->getMailBox($creds); } public function getMailBox(osTicket\Mail\AuthCredentials $cred=null) { if (!isset($this->mailbox) || $cred) { $this->cred = $cred ?: $this->getFreshCredentials(); $setting = $this->getAccountSetting(); $setting->setCredentials($this->cred); switch (strtolower($this->getProtocol())) { case 'imap': $mailbox = new osTicket\Mail\Imap($setting); break; case 'pop3': case 'pop': $mailbox = new osTicket\Mail\Pop3($setting); break; default: throw new Exception('Unknown Mail protocol: '.$this->getProtocol()); } $this->mailbox = $mailbox; } return $this->mailbox; } public function fetchEmails() { try { $this->logLastFetch(); $fetcher = new osTicket\Mail\Fetcher($this); return $fetcher->processEmails(); } catch (Throwable $t) { // May throw an Exception or Error // Log the message $this->logFetchError($t->getMessage()); // rethrow the throwable so caller can handle it throw $t; } return 0; } protected function setInfo($vars, &$errors) { $creds = null; if ($vars['mailbox_active']) { if (!$vars['mailbox_host']) $errors['mailbox_host'] = __('Host name required'); if (!$vars['mailbox_port']) $errors['mailbox_port'] = __('Port required'); if (!$vars['mailbox_protocol']) $errors['mailbox_protocol'] = __('Select protocol'); elseif (!in_array($vars['mailbox_protocol'], Email::mailboxProtocols())) $errors['mailbox_protocol'] = __('Invalid protocol'); if (!$vars['mailbox_auth_bk']) $errors['mailbox_auth_bk'] = __('Select Authentication'); if (!$vars['mailbox_fetchfreq'] || !is_numeric($vars['mailbox_fetchfreq'])) $errors['mailbox_fetchfreq'] = __('Fetch interval required'); if (!$vars['mailbox_fetchmax'] || !is_numeric($vars['mailbox_fetchmax'])) $errors['mailbox_fetchmax'] = __('Maximum emails required'); if ($vars['mailbox_protocol'] == 'POP' && !empty($vars['mailbox_folder'])) $errors['mailbox_folder'] = __('POP mail servers do not support folders'); if (!$vars['mailbox_postfetch']) $errors['mailbox_postfetch'] = __('Indicate what to do with fetched emails'); } if (!strcasecmp($vars['mailbox_postfetch'], 'archive')) { if ($vars['mailbox_protocol'] == 'POP') $errors['mailbox_postfetch'] = __('POP mail servers do not support folders'); elseif (!$vars['mailbox_archivefolder']) $errors['mailbox_postfetch'] = __('Valid folder required'); elseif (!strcasecmp($vars['mailbox_folder'], $vars['mailbox_archivefolder'])) $errors['mailbox_postfetch'] = __('Archive folder cannot be same as fetched folder (INBOX)'); } // Make sure authentication is configured if selection is made if ($vars['mailbox_auth_bk'] && !($creds=$this->getFreshCredentials($vars['mailbox_auth_bk']))) $errors['mailbox_auth_bk'] = __('Configure Authentication'); if (!$errors) { $this->active = $vars['mailbox_active'] ? 1 : 0; $this->host = $vars['mailbox_host']; $this->port = $vars['mailbox_port'] ?: 0; $this->protocol = $vars['mailbox_protocol']; $this->auth_bk = $vars['mailbox_auth_bk'] ?: null; $this->folder = $vars['mailbox_folder'] ?: null; $this->fetchfreq = $vars['mailbox_fetchfreq'] ?: 5; $this->fetchmax = $vars['mailbox_fetchmax'] ?: 30; $this->postfetch = $vars['mailbox_postfetch']; $this->last_activity = null; $this->last_error_msg = null; $this->num_errors = 0; //Post fetch email handling... switch ($vars['mailbox_postfetch']) { case 'archive': $this->archivefolder = $vars['mailbox_archivefolder']; break; case 'delete': default: $this->archivefolder = null; } // If mailbox is active and we have credentials then attemp to // authenticate if ($this->active && $creds) { try { // Get mailbox (Storage Backend) if (($mb=$this->getMailBox($creds))) { if ($this->folder && !$mb->hasFolder($this->folder)) $errors['mailbox_folder'] = __('Unknown Folder'); if ($this->archivefolder && $this->isImap() && !$mb->hasFolder($this->archivefolder) && !$mb->createFolder($this->archivefolder)) $errors['mailbox_archivefolder'] = __('Unable to create Folder'); } else { $errors['mailbox_auth'] = __('Authentication Error'); } } catch (Exception $ex) { $errors['mailbox_auth'] = $ex->getMessage(); } } } return !$errors; } public function logLastFetch($now=null) { return $this->logActivity(null, $now); } private function logFetchError($error) { return $this->logActivity($error ?: __('Mail Fetch Error')); } public function update($vars, &$errors) { if (!$this->setInfo($vars, $errors)) return false; return $this->save(); } static function create($ht=false) { $i = parent::create($ht); $i->type = 'mailbox'; return $i; } } class SmtpAccount extends EmailAccount { static $meta = array( 'table' => EMAIL_ACCOUNT_TABLE, 'pk' => array('id'), 'joins' => array( 'email' => array( 'constraint' => array('email_id' => 'Email.email_id'), ), 'account' => array( 'constraint' => array( 'type' => "'smtp'", 'email_id' => 'Email.email_id'), ), ), ); static protected $vars = [ 'active', 'host', 'port', 'protocol', 'auth_bk', 'allow_spoofing' ]; private $smtp; private $cred; static public function objects() { return parent::objects() ->filter(['type' => 'smtp']); } public function isMailboxAuth() { return (strcasecmp($this->getAuthBk(), 'mailbox') === 0); } /* * Check if using mailbox auth and MailboxAccount exists if so * return the MailboxAccount config, otherwise return it's own * config */ protected function getConfig() { if ($this->isMailboxAuth() && ($email=$this->getEmail()) && ($account=$email->getMailBoxAccount())) return $account->getConfig(); return parent::getConfig(); } public function allowSpoofing() { return ($this->allow_spoofing); } protected function validateCredentials(osTicket\Mail\AuthCredentials $creds) { return $this->getSmtp($creds); } public function getSmtpConnection() { $this->smtp = $this->getSmtp(); if (!$this->smtp->connect()) return false; return $this->smtp; } public function getSmtp(osTicket\Mail\AuthCredentials $cred=null) { if (!isset($this->smtp) || $cred) { $this->cred = $cred ?: $this->getFreshCredentials(); if ($this->cred) { $setting = $this->getAccountSetting(); $setting->setCredentials($this->cred); $smtpOptions = new osTicket\Mail\SmtpOptions($setting); $smtp = new osTicket\Mail\Smtp($smtpOptions); // Attempt to connect now if credentials are sent in if ($cred) $smtp->connect(); $this->smtp = $smtp; } } return $this->smtp; } protected function setInfo($vars, &$errors) { $creds = null; $_errors = []; if ($vars['smtp_active']) { if (!$vars['smtp_host']) $_errors['smtp_host'] = __('Host name required'); if (!$vars['smtp_port']) $_errors['smtp_port'] = __('Port required'); if (!$vars['smtp_auth_bk']) $_errors['smtp_auth_bk'] = __('Select Authentication'); elseif (!($creds=$this->getFreshCredentials($vars['smtp_auth_bk']))) $_errors['smtp_auth_bk'] = ($vars['smtp_auth_bk'] == 'mailbox') ? __('Configure Mailbox Authentication') : __('Configure Authentication'); } elseif ($vars['smtp_auth_bk'] // We default to mailbox - so we're not going to check // unless account is active, see above! && strcasecmp($vars['smtp_auth_bk'], 'mailbox') && !($creds=$this->getFreshCredentials($vars['smtp_auth_bk']))) $_errors['smtp_auth_bk'] = __('Configure Authentication'); // Check if set to active and using mailbox auth, if so check strict // matching. if ($vars['smtp_active'] == 1 && ($vars['smtp_auth_bk'] === 'mailbox') && (strpos($vars['mailbox_auth_bk'], 'oauth2') === 0) && !$this->checkStrictMatching()) $_errors['smtp_auth_bk'] = sprintf('%s and %s', __('Resource Owner'), __('Email Mismatch')); if (!$_errors) { $this->active = $vars['smtp_active'] ? 1 : 0; $this->host = $vars['smtp_host']; $this->port = $vars['smtp_port'] ?: 0; $this->auth_bk = $vars['smtp_auth_bk'] ?: null; $this->protocol = 'SMTP'; $this->allow_spoofing = $vars['smtp_allow_spoofing'] ? 1 : 0; $this->last_activity = null; $this->last_error_msg = null; $this->num_errors = 0; // If account is active then attempt to authenticate if ($this->active && $creds) { try { if (!($smtp=$this->getSmtp($creds))) $_errors['smtp_auth'] = __('Authentication Error'); } catch (Exception $ex) { $_errors['smtp_auth'] = $ex->getMessage(); } } } $errors = array_merge($errors, $_errors); return !$errors; } function update($vars, &$errors) { if (!$this->setInfo($vars, $errors)) return false; return $this->save(); } static function create($ht=false) { $i = parent::create($ht); $i->type = 'smtp'; return $i; } } /* * Email Config Store * * Extends base central config store * */ class EmailAccountConfig extends Config { /* * Get strict matching (default: true) */ public function getStrictMatching() { return $this->get('strict_matching', true); } /* * Set strict matching */ public function setStrictMatching($mode) { return $this->set('strict_matching', !!$mode); } public function updateInfo($vars) { return parent::updateAll($vars); } } /* * Basic Authentication Configuration Form * */ class BasicAuthConfigForm extends AbstractForm { private $account; private $host; private $password; function __construct($source=null, $options=array()) { parent::__construct($source, $options); } private function getPassword() { return $this->_source['passwd']; } function buildFields() { $password = $this->getPassword(); return array( 'username' => new TextboxField(array( 'required' => true, 'label' => __('Username'), 'configuration' => array( 'length' => 0, 'autofocus' => true, ), )), 'passwd' => new PasswordField(array( 'label' => __('Password'), 'required' => !$password, 'validator' => 'noop', 'hint' => $password ? __('Enter a new password to change current one') : '', 'configuration' => array( 'length' => 0, 'classes' => 'span12', 'placeholder' => $password ? str_repeat('•', strlen($password)*2) : __('Password'), ), )), ); } } ?>
Save