rc = rcmail::get_instance(); $this->load_config('config.inc.dist.php'); $this->load_config(); if ($this->plugin_enabled($this->domain)) { if (!$this->get_username()) { rcube::write_log('errors', 'qmailforward error: cannot retrieve username/domain'); return 1; } $this->add_texts('localization/', true); $this->include_script('qmailforward.js'); // insert the menu button $this->add_hook('settings_actions', [$this, 'settings_tab']); $this->_init_storage(); $this->register_action('plugin.qmailforward', [$this, 'init_html']); $this->register_action('plugin.qmailforward-save', [$this, 'forward_save']); } } /* allow qmailforward_allowed_hosts only, unless that array is empty */ private function plugin_enabled($domain) { $denied_domains = $this->rc->config->get('qmailforward_allowed_hosts'); if (in_array($domain, $denied_domains)) return false; else return true; } /* build the html */ public function init_html() { // page title $this->rc->output->set_pagetitle($this->gettext('page-title')); // register content handler // 'forwardform' = container's name/id inside template $this->rc->output->add_handler('forwardform', array($this, 'forward_form')); // send content to template 'form_template' $this->rc->output->send('qmailforward.form_template'); } /********************************************************************************************** * forward form * * function derived from the RC managesieve-forward plugin, which already has a nice * html form * * $attrib is an array containing the xml attrib of the rc obj * array(3) { ["name"]=> string(11) "forwardform" ["id"]=> string(11) "forwardform" ["class"]=> string(8) "propform" } * **********************************************************************************************/ public function forward_form($attrib) { $stored = array(); // container array for db data // load data from the db $stored = $this->storage->load($this->user, $this->domain); // build FORM tag $form_id = $attrib['id']; // = forwardform $out = $this->rc->output->request_form([ 'id' => $form_id, 'name' => $form_id, 'method' => 'post', 'task' => 'settings', 'action' => 'plugin.qmailforward-save', // we have registered an action for that to handle the post 'noclose' => true ] + $attrib ); // form elements $status = new html_select(['name' => 'forward_status', 'id' => 'forward_status', 'class' => 'custom-select']); $action = new html_select(['name' => 'forward_action', 'id' => 'forward_action', 'class' => 'custom-select']); $status->add($this->gettext('on'), 'on'); $status->add($this->gettext('off'), 'off'); $action->add($this->gettext('copy'), 'copy'); $action->add($this->gettext('redirect'), 'redirect'); // force domain selection in redirect email input $domains = (array) $this->rc->config->get('qmailforward_domains'); if (!empty($domains)) { sort($domains); $domain_select = new html_select(['name' => 'action_domain', 'id' => 'action_domain', 'class' => 'custom-select']); $domain_select->add(array_combine($domains, $domains)); if (!empty($stored)) { $parts = explode('@', $stored[$this->rc->config->get('qmailforward_sql_valias_field')]); if (!empty($parts)) { $the_domain = in_array($parts[1], $domains) ? $parts[1] : ''; $the_username = !empty($the_domain) ? $parts[0] : ''; } } } // the value is the entire address, unless we are allowing only a set of domains $target_value = !empty($domain_select) ? $the_username : $stored[$this->rc->config->get('qmailforward_sql_valias_field')]??''; // redirect target $domain_selected = !empty($the_domain) ? $the_domain : null; $action_target = '' . '' . (!empty($domain_select) ? ' @ ' . $domain_select->show($domain_selected) : '') . ''; // Message tab $table = new html_table(['cols' => 2]); // forward $table->add('title', html::label('forward_action', $this->gettext('action'))); $copy = ($stored[$this->rc->config->get('qmailforward_sql_copy_field')]??null)==1 ? 'copy' : 'redirect'; $table->add('forward input-group input-group-combo', $action->show($copy).' '.$action_target); // status $table->add('title', html::label('forward_status', $this->gettext('status'))); $on_off = ( empty($stored) || (!empty($domains) && $the_domain=='') ) ? 'off' : 'on'; $table->add(null, $status->show($on_off)); $out .= $table->show($attrib); $out .= ''; $this->rc->output->add_gui_object('qmailforward_form', $form_id); return $out; } /********************************************** * collect the POST data * forward_action => copy/redirect * action_target => user@domain.tld (or just user if action_domain defined) * action_domain = domain.tld * forward_status => on/off **********************************************/ public function forward_save() { $success = true; $error = ''; // build email $email = !is_null($_POST['action_domain']??null) ? $_POST['action_target'].'@'.$_POST['action_domain'] : $_POST['action_target']; // sanity check if (!$this->validEmail($email)) { $error = $this->gettext('invalid_email').": ".$email.$_SERVER['REQUEST_METHOD'] ; $success = false; } /*************************************** * save to db * * result = true on success * false on error ***************************************/ if (!$this->storage->save($this->user, $this->domain)) $success = false; // return feedback if ($success) { $this->rc->output->command('display_message', $this->gettext('success'), 'confirmation'); } else { $error = !empty($error) ? ": ".$error : ''; $this->rc->output->command('display_message', $this->gettext('failed').$error, 'error'); } } /* set the button in the settings menu side bar */ public function settings_tab($p) { $this->include_stylesheet($this->local_skin_path() . '/style.css'); // add qmailforward tab $p['actions'][] = [ 'action' => 'plugin.qmailforward', // we defined an handler for this action 'class' => 'qmailforward', 'label' => 'qmailforward.forward', 'title' => 'qmailforward.manage-forward', 'role' => 'button', 'aria-disabled' => 'false', 'tabindex' => '0']; return $p; } /* load the classes for db storage */ private function _init_storage() { if (!$this->storage) { // Add include path for internal classes $include_path = $this->home . '/lib' . \PATH_SEPARATOR; $include_path .= ini_get('include_path'); set_include_path($include_path); $class = $this->rc->config->get('qmailforward_storage', 'mysql'); $class = "rcube_qmailforward_storage_" . $class; // try to instantiate class if (class_exists($class)) { $this->storage = new $class($this->rc->config); } else { // no storage found, raise error rcube::raise_error(['code' => 604, 'type' => 'qmailforward', 'line' => __LINE__, 'file' => __FILE__, 'message' => "Failed to find storage driver. Check qmailforward_storage config option", ], true, true); } } } /* save the username and the domain */ private function get_username() { $username = explode('@', $_SESSION['username']); $this->user = $username[0]; $this->domain = $username[1]; if (!is_null($this->user) && !is_null($this->domain)) return true; else return false; } /******************************************************** * http://www.linuxjournal.com/article/9585?page=0,3 * Validate an email address. * Provide email address (raw input) * Returns true if the email address has the email * address format and the domain exists. ********************************************************/ private function validEmail($email) { // DNS check disabled by default $checkDNS = $this->rc->config->get('qmailforward_dnscheck'); $isValid = true; $atIndex = strrpos($email, "@"); if (is_bool($atIndex) && !$atIndex) { $isValid = false; } else { $domain = substr($email, $atIndex+1); $local = substr($email, 0, $atIndex); $localLen = strlen($local); $domainLen = strlen($domain); if ($localLen < 1 || $localLen > 64) { // local part length exceeded $isValid = false; } else if ($domainLen < 1 || $domainLen > 255) { // domain part length exceeded $isValid = false; } else if ($local[0] == '.' || $local[$localLen-1] == '.') { // local part starts or ends with '.' $isValid = false; } else if (preg_match('/\\.\\./', $local)) { // local part has two consecutive dots $isValid = false; } else if (!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) { // character not valid in domain part $isValid = false; } else if (preg_match('/\\.\\./', $domain)) { // domain part has two consecutive dots $isValid = false; } else if (!preg_match('/^(\\\\.|[A-Za-z0-9!#%&`_=\\/$\'*+?^{}|~.-])+$/', str_replace("\\\\","",$local))) { // character not valid in local part unless // local part is quoted if (!preg_match('/^"(\\\\"|[^"])+"$/', str_replace("\\\\","",$local))) { $isValid = false; } } if ($checkDNS && $isValid && !(checkdnsrr($domain,"MX") || checkdnsrr($domain,"A"))) { // domain not found in DNS $isValid = false; } } return $isValid; } }