Server : Apache System : Linux server.lienzindia.com 4.18.0-348.7.1.el8_5.x86_64 #1 SMP Wed Dec 22 13:25:12 UTC 2021 x86_64 User : plutus ( 1007) PHP Version : 7.4.33 Disable Function : NONE Directory : /var/webuzo-data/roundcube/public_html/plugins/webuzo/soft2fa/ |
Upload File : |
<?php class soft2fa{ private $rc; private $webuzo_user = ''; private $email = ''; private $session_key = 'soft2fa_plugin'; // Reference to the main webuzo plugin instance private $plugin; public function __construct($plugin){ $this->plugin = $plugin; } public function initialize(){ global $globals; // Load roundcube instance $this->rc = rcmail::get_instance(); $this->webuzo_user = $this->rc->config->get('webuzo_user'); if(empty($this->webuzo_user)){ return; } // Include necessary libs include_once($globals['path'].'/lib/classes/hotp.php'); include_once($globals['path'].'/lib/classes/Base32.php'); include_once($globals['path'].'/lib/IPv6/IPv6.php'); $this->conf_path = '/var/webuzo/users/'. $this->webuzo_user.'/rc_2fa'; $this->email = !empty($this->rc->user->data['username']) ? $this->rc->user->data['username'] : ''; // Include locals $this->plugin->add_texts('soft2fa/localization/'); // Add required hooks $this->plugin->add_hook('startup', [$this, 'startup']); $this->plugin->add_hook('login_after', [$this, 'login_after']); // Load settings if(!empty($this->is_login()) && $this->rc->task == 'settings') { // Init plugin $this->plugin->register_action('plugin.soft2fa', [$this, 'soft2fa_settings']); // Register menu $this->plugin->add_hook('settings_actions', [$this, 'settings_actions']); // Handle AJAX $this->plugin->register_action('plugin.soft2fa_post_data', [$this, 'soft2fa_post_data']); $this->plugin->register_action('plugin.soft2fa_download_backup_codes', [$this, 'soft2fa_download_backup_codes']); } } private function is_login(){ if(empty($_SESSION['user_id']) || empty($_SESSION['password'])){ return false; } return true; } public function startup() { // Skip if already logged in and 2FA is verified or IP is in whitelist. if (!empty($this->is_login()) && (!empty($_SESSION['soft2fa_plugin']['soft2fa_verify']) || $this->check_whitelist())) { // Show verified msg if already logged in and trying to access if($this->rc->action ==='plugin.verify2fa'){ $this->soft2fa_verification_ui(true); } return; } // Fetch 2FA and backup code data $app_auth = $this->get_2fa_auth_data($this->email); $backup_codes = $this->get_2fa_backup_codes($this->email); // Skip if both 2FA methods are disable if (empty($app_auth['2fa_status']) && empty($backup_codes)) { return; } // If not login task, redirect to 2FA verification if ($this->rc->task != 'login') { $this->rc->output->redirect(['_task' => 'login', '_action' => 'plugin.verify2fa']); } // 2FA verification handler if ($this->rc->task === 'login' && $this->rc->action === 'plugin.verify2fa') { $token = rcube_utils::get_input_value('_token', rcube_utils::INPUT_POST); if (empty($token)) { // Show the 2FA UI $this->soft2fa_verification_ui(); return; } if (!rcmail::get_instance()->check_request()) { $this->rc->output->show_message($this->plugin->gettext('csrf_error'), 'error'); $this->soft2fa_verification_ui(); return; } $code_input = trim(rcube_utils::get_input_value('_2fa_code', rcube_utils::INPUT_POST)); $encoded_input = base64_encode($code_input); $index = array_search($encoded_input, array_column($backup_codes, 'code')); // Check OTP or valid backup code $is_otp_valid = (!empty($app_auth['2fa_otp']) && $app_auth['2fa_otp'] === $code_input); $is_valid_backup_code = ($index !== false); if (!empty($is_otp_valid) || !empty($is_valid_backup_code)) { // If backup code used, mark it as used if (!empty($is_valid_backup_code)) { if (!empty($backup_codes[$index]['status'])) { $this->rc->output->show_message($this->plugin->gettext('code_used'), 'warning'); $this->soft2fa_verification_ui(); return; } // Mark the code as used $data = $this->plugin->loaddata($this->conf_path); $data[$this->email]['2fa']['backup_codes'][$index]['status'] = true; $resp = $this->plugin->writedata($this->conf_path, $data); if(empty($resp)){ $this->rc->output->show_message($this->plugin->gettext('write_error'), 'warning'); $this->soft2fa_verification_ui(); return; } } // Set session $this->rc->session->append($this->session_key,'soft2fa_verify', true); $this->rc->output->redirect(['_task' => 'mail']); return; } // Invalid code $this->rc->output->show_message($this->plugin->gettext('invalid_code'), 'warning'); $this->soft2fa_verification_ui(); } } // Does auto loggedin? function login_after(){ // Do not proceed further if not autologin if (!isset($_POST['_autologin'])) { return; } $email_path = '/var/webuzo/users/'.$this->webuzo_user.'/emails'; $emails = $this->plugin->loaddata($email_path); $email = !empty($this->rc->user->data['username']) ? $this->rc->user->data['username'] : ''; $stored_hash = !empty($email) && !empty($emails[$email]['password']) ? $emails[$email]['password'] : ''; $rc_passwd_hash = sha1($this->rc->decrypt($_SESSION['password'])); // Show the 2FA page if the stored password is empty or if the session password matches the password in the Webuzo email file if (empty($stored_hash) || $stored_hash == $rc_passwd_hash) { return; } $this->rc->session->append($this->session_key,'soft2fa_verify', true); } private function soft2fa_verification_ui($is_loggedin = false){ $this->rc->output->set_env('soft2fa_verified', $is_loggedin); $base_url = $this->rc->url([''], true); if(!empty($is_loggedin)){ $base_url = $this->rc->url([ '_task' => 'mail', '_mbox' => 'INBOX' ]); } $this->rc->output->set_env('rc_webmail_url', $base_url); $this->rc->output->add_handlers(['soft2fa_ui_form' => [$this, 'validate_2fa_form']]); $this->rc->output->set_pagetitle($this->plugin->gettext('soft2fa_verify')); $this->rc->output->send('webuzo.soft2fa_verify'); } public function settings_actions($args){ $args['actions'][] = [ 'action' => 'plugin.soft2fa', 'class' => 'license', 'label' => 'two_factor_auth', 'title' => 'two_factor_auth', 'domain' => 'webuzo', ]; return $args; } public function soft2fa_settings(){ $this->rc->output->set_pagetitle($this->plugin->gettext('two_factor_auth')); // Load JS & CSS $this->plugin->include_stylesheet('soft2fa/css/soft2fa.css'); $this->plugin->include_script('soft2fa/js/jquery.qrcode.min.js'); $this->plugin->include_script('soft2fa/js/soft2fa.js'); $skin = $this->rc->config->get('skin'); if ($skin === 'larry' || $skin === 'classic') { $this->plugin->include_stylesheet('soft2fa/css/soft2fa_legacy.css'); } // Handle JS labels $this->rc->output->add_label( "webuzo.auth_confirm_dialog", 'webuzo.auth_reset_dialog', 'webuzo.backup_code_enable', 'webuzo.backup_code_disable', 'webuzo.backup_code_regen', 'webuzo.clipboard_success', 'webuzo.clipboard_error', 'webuzo.ip_delete_confirm_dialog' ); // Handle 2FA setup form $this->plugin->register_handler('plugin.body', [$this, 'soft2fa_settings_form']); $this->rc->output->send('plugin'); } public function validate_2fa_form($attrib){ // Create 2FA input field $field = new html_inputfield([ 'name' => '_2fa_code', 'id' => '2fakey', 'required' => true, 'size' => 6, 'class' => 'form-control', 'placeholder' => $this->plugin->gettext('2fa_placeholder') ]); $form_content = html::div('col-md-12', $field->show() ). html::div('col-md-12 mt-4', html::tag( 'button', ['type' => 'submit', 'name' => '2fa_submit' ,'class' => 'button mainaction submit btn btn-primary btn-md text-uppercase w-100'], 'Verify Code' ) ); return $form_content; } public function soft2fa_settings_form(){ $data = $this->plugin->loaddata($this->conf_path); $app2fa = $this->get_2fa_auth_data($this->email); $auth_status = !empty($data[$this->email]['2fa']['auth']['status']) ? $data[$this->email]['2fa']['auth']['status'] : false; $backup_codes = !empty($data[$this->email]['2fa']['backup_codes']) ? $data[$this->email]['2fa']['backup_codes'] : []; $preferences = [ 'authenticator' => [ 'icon' => 'qrcode-icon', 'status' => $auth_status, 'en_text' => $this->plugin->gettext('en_auth'), 'dis_text' => $this->plugin->gettext('dis_auth'), 'name' => $this->plugin->gettext('authenticator'), 'onclick' => !empty($auth_status) ? 'app_authenticator(2)' : 'app_authenticator()' ], 'backup_codes' => [ 'icon' => 'shild-icon', 'status' => !empty($backup_codes) ? true : false, 'en_text' => $this->plugin->gettext('gen_codes'), 'dis_text' => $this->plugin->gettext('view_codes'), 'name' => $this->plugin->gettext('backup_codes'), 'onclick' => !empty($backup_codes) ? 'app_backup_code()' : 'app_backup_code(1)', ] ]; $pref_options = ''; foreach ($preferences as $key => $pref) { $btn_text = !empty($pref['status']) ? $pref['dis_text'] : $pref['en_text']; $action_btn = new html_button([ 'class' => 'button w-100 soft-btn', 'onclick' => $pref['onclick'], ]); $pref_options .= html::div('pref-container', html::div('pref-row', html::div('pref-col pref-100', html::div('pref-row option_preference ', html::div('pref-col pref-75', html::span($pref['icon'].' pref-icon',''). html::span('pref-title', $pref['name']) ). html::div('pref-col pref-25', $action_btn->show($btn_text)) ) ) ) ); } $auth_model = ''; if(empty($auth_status)){ $secret_field = new html_inputfield(['name' => 'soft_2fa_code_key', 'id' => 'soft2fa_app_key', 'disabled' => 'disabled']); $secret32_field = new html_inputfield(['name' => 'soft_2fa_code_key_32', 'id' => 'soft2fa_app_key_32', 'disabled' => 'disabled']); $verify_fields = ''; for ($i=0;$i < 6; $i++) { $tmp_field = new html_inputfield(['name' => 'soft_2fa_code_key', 'class'=> 'form-control otp-input', 'maxlength' => '1']); $verify_fields .= html::div('col-sm-2', $tmp_field->show()); } $body = html::div('row px-4 pb-0', html::div('col-md-12 pt-2 text-center', html::label('', html::div(['id' => 'app_qr', 'data-qrcode' => htmlspecialchars($app2fa['2fa_qr'])]) ) ). html::div('col-md-12 mt-2', $this->plugin->gettext('qr_code_step1').html::br(). html::div('mt-2 text-center', html::label(['class' => 'secret_code', 'id' => 'secret_code'], $app2fa['2fa_key32']).html::div(['onclick' => 'copy_code(this)', 'class' => 'copy-icon', 'id' => 'qr_auth_code', 'title' => 'Copy code', 'data-code' => base64_encode($app2fa['2fa_key32'])],'')) ). html::div('col-md-12 mt-2', $this->plugin->gettext('qr_code_step2') ). html::div('col-md-12 mt-4', html::div('row otp-wrapper', $verify_fields ) ). html::div('col-md-12 mt-4 text-right', $this->plugin->gettext('refresh_qr').html::div(['class' => 'reload-icon', 'title' => 'Refresh QR code', 'onclick' => 'app_authenticator(3)'], '') ) ); $footer = html::tag('button', [ 'type' => 'button', 'class' => 'btn btn-danger', 'data-dismiss' => 'modal' ], $this->plugin->gettext('cancel')) . html::tag('button', [ 'type' => 'button', 'class' => 'btn btn-primary soft-btn', 'onclick' => 'app_authenticator(1)' ], $this->plugin->gettext('activate') ); $auth_model = $this->soft2fa_model('authenticator_model',$this->plugin->gettext('auth_model_title'), $body, $footer); } $backup_model = ''; if(!empty($backup_codes) && is_array($backup_codes)){ $codes = ''; $all_exipred = true; foreach ($backup_codes as $key => $code) { $codes .= html::div(['class' => 'col-md-6 backup-code text-center my-2', 'data-used' => $code['status'], 'title' => (!empty($code['status']) ? $this->plugin->gettext('code_used_title') : '')], base64_decode($code['code'])); if(empty($code['status'])){ $all_exipred = false; } } $body = html::div('row', html::div('col-md-12', $this->plugin->gettext('backup_codes_title') ). html::div('col-md-12', html::div('row backup-codes-wrap', $codes ) ). html::div('col-md-12', html::tag('ul',null, html::tag('li', null, $this->plugin->gettext('backup_code_warn1')). html::tag('li', null, $this->plugin->gettext('backup_code_warn2')) ) ). html::div('col-md-12 mt-2 text-right', $this->plugin->gettext('regen_backup_codes').html::div(['class' => 'reload-icon', 'onclick' => 'app_backup_code(3)'], '') ) ); $download_url = !empty($all_exipred) ? 'javascript:void(0);' : $this->rc->url([ '_task' => 'settings', '_action' => 'plugin.soft2fa_download_backup_codes', ]); $footer = html::tag('button', [ 'type' => 'button', 'class' => 'btn btn-danger', 'data-dismiss' => 'modal' ], $this->plugin->gettext('cancel') ). html::tag('a', [ 'href' => $download_url, 'class' => 'btn btn-primary soft-btn'.(!empty($all_exipred) ? 'disabled' : ''), ], $this->plugin->gettext('download_codes') ). html::tag('button', [ 'type' => 'button', 'class' => 'btn btn-primary soft-btn', 'onclick' => 'app_backup_code(2)' ], $this->plugin->gettext('deactivate') ); $backup_model = $this->soft2fa_model('backup_codes_model', $this->plugin->gettext('backup_codes_model_title'), $body, $footer); } $table = new html_table(array( 'class' => 'whitelist-table w-100', 'cols' => 4, 'border' => 0, )); // Add the table header $table->add_header('start-ip', rcube::Q('Start IP')); $table->add_header('end-ip', rcube::Q('End IP')); $table->add_header('date', rcube::Q('Date')); $table->add_header('options', rcube::Q('Options')); if(!empty($data[$this->email]['2fa']['whitelist'])){ foreach ($data[$this->email]['2fa']['whitelist'] as $key => $ip) { // $table->add_row(); $table->add('start-ip', rcube::Q($ip['start'])); $table->add('end-ip', rcube::Q($ip['end'])); $table->add('date', rcube::Q(date('d/m/Y', $ip['time']))); $table->add('options', html::a([ 'href' => 'javascript:void(0);', 'onclick' => "handle_ip(".$key.")", 'class' => 'btn btn-danger btn-sm delete-link', ], 'Delete')); } } $w_start_ip_field = new html_inputfield([ 'name' => '_w_start_ip', 'id' => 'w_start_ip', 'required' => true, 'size' => 6, 'class' => 'form-control', 'placeholder' => $this->plugin->gettext('ip_range') ]); $w_end_ip_field = new html_inputfield([ 'name' => '_w_end_ip', 'id' => 'w_end_ip', 'size' => 6, 'class' => 'form-control', 'placeholder' => $this->plugin->gettext('ip_range') ]); $w_form_content = html::div('pref-row mb-0', html::div('pref-col pref-100 px-0', html::label('', $this->plugin->gettext('ip_start_range')) ). html::div('pref-col pref-100 px-0', $w_start_ip_field->show() ). html::div('pref-col pref-100 px-0 mt-4', html::label('', $this->plugin->gettext('ip_end_range')) ). html::div('pref-col pref-100 px-0', $w_end_ip_field->show() ). html::div('pref-col pref-100 mt-4 flex-right px-0', html::tag( 'button', ['type' => 'submit', 'name' => '2fa_submit' ,'class' => 'button soft-btn btn btn-secondary'], $this->plugin->gettext('add_ip_range') ) ) ); $w_form = $this->rc->output->form_tag([ 'id' => 'whitelist-form', 'name' => 'whitelist-form', 'class' => 'w-100 mt-2', 'method' => 'post', 'action' => '', ], $w_form_content ); $whitelist_content = html::div('pref-container mb-30', html::div('pref-row', html::div('pref-col pref-50 option_preference p-4 mt-3', html::div('w-100', html::div('pref-col', html::span('pref-title', $this->plugin->gettext('w_ips')) ). html::div('pref-col', $w_form ) ) ). html::div('pref-col pref-50 option_preference p-4 mt-3', html::div('w-100', html::div('pref-col', html::div('pref-title h-auto', $this->plugin->gettext('w_ip_list')) ). html::div('pref-col mt-4', html::div('w_ip_list w-100', $table->show() ) ) ) ) ) ); $plugin_content = html::div('soft2fa-wrapper', $auth_model. $backup_model. html::div('pref-main-title', $this->plugin->gettext('two_factor_auth') ). html::div('preference-content', $pref_options. $whitelist_content ). html::div('advance-settings', ) ); return $plugin_content; $this->rc->output->send('plugin'); } public function soft2fa_download_backup_codes(){ // ensure user is logged in if (empty($this->is_login())) { header('HTTP/1.1 403 Forbidden'); exit('Unauthorized access'); } $codes = $this->get_2fa_backup_codes($this->email); if (empty($codes)) { header('HTTP/1.1 404 Not Found'); exit('No backup codes found'); } $content = ''; foreach ($codes as $code) { if (empty($code['status'])) { $content .= base64_decode($code['code']) . "\n"; } } if(empty($content)){ header('HTTP/1.1 404 Not Found'); exit('All backup codes are expired!'); } $content = "Generated codes --------------- Username: ".$this->email."\n\n". $content; $filename = 'backup_codes_'.str_ireplace(['.','@'],'_', $this->email); header('Content-Type: text/plain'); header('Content-Disposition: attachment; filename="'.$filename); header('Content-Length: ' . strlen($content)); echo $content; exit; } private function soft2fa_model($id= '', $title = '', $content = '', $footer = '', $size = 'md'){ $modal = html::div( [ 'class' => 'modal fade', 'id' => $id, 'tabindex' => '-1', 'aria-hidden' => 'true' ], html::div('modal-dialog modal-'.$size, html::div('modal-content', html::div('modal-header', html::tag('h5', 'modal-title', $title). html::tag('button', [ 'type' => 'button', 'class' => 'btn-close', 'data-dismiss' => 'modal', 'aria-label' => 'Close' ]) ). html::div('modal-body', $content). html::div('modal-footer', $footer) ) ) ); return $modal; } public function soft2fa_post_data(){ if(empty($this->is_login()) || $this->rc->task != 'settings' || $this->rc->action != 'plugin.soft2fa_post_data') { return; } // get 2fa info $type = rcube_utils::get_input_value('type', rcube_utils::INPUT_POST); $data = $this->plugin->loaddata($this->conf_path); $action = rcube_utils::get_input_value('action', rcube_utils::INPUT_POST); if(empty($action)){ $this->return_JSON(['error' => $this->plugin->gettext('invalid_action')]); return; } // Handle authenticator AJAX requests if($type == 'auth'){ if(in_array($action, ['enable', 'disable'])){ if(!empty($data[$this->email]['2fa']['auth']['status']) && $action == 'enable'){ $this->return_JSON(['error' => $this->plugin->gettext('auth_enabled')]); return; } if($action == 'enable'){ $post_otp = rcube_utils::get_input_value('otp', rcube_utils::INPUT_POST); $app2fa = $this->get_2fa_auth_data($this->email); $otp = $app2fa['2fa_otp']; if((int)$post_otp != $otp){ $this->return_JSON(['error' => $this->plugin->gettext('invalid_code')]); return; } $data[$this->email]['2fa']['auth']['status'] = true; $this->rc->session->append($this->session_key,'soft2fa_verify', true); }else{ unset( $data[$this->email]['2fa']['auth'] ); $this->rc->session->remove('soft2fa_verify'); } $resp = $this->plugin->writedata($this->conf_path, $data); if(!$resp){ $this->rc->session->remove('soft2fa_verify'); $this->return_JSON(['error' => $this->plugin->gettext('write_error')]); return; } $this->return_JSON(['auth_response' => 1, 'message' => ($action == 'enable' ? $this->plugin->gettext('auth_activate') : $this->plugin->gettext('auth_deactivate'))]); return; } if($action == 'regen_qr'){ $app2fa = $this->get_2fa_auth_data($this->email, true); $resp['reset_qr'] = 0; $resp['message'] = $this->plugin->gettext('error'); if(!empty($app2fa)){ $resp = [ 'regen_qr_response' => 1, 'qr_code' => htmlspecialchars($app2fa['2fa_qr']), 'auth_code' => base64_encode($app2fa['2fa_key32']), 'message' => $this->plugin->gettext('qr_reset_success'), 'prevent_refresh' => 1 ]; } $this->return_JSON($resp); return; } $this->return_JSON(['error' => $this->plugin->gettext('invalid_action')]); return; } // Handle backup codes AJAX requests if($type == 'backup_codes'){ if(!in_array($action, ['enable', 'regen_backup_codes', 'disable'])){ $this->return_JSON(['error' => $this->plugin->gettext('invalid_action')]); return; } if(!empty($data[$this->email]['2fa']['backup_codes']) && $action == 'enable'){ $this->return_JSON(['error' => $this->plugin->gettext('backup_codes_exists')]); return; } if(empty($data[$this->email]['2fa']['backup_codes']) && $action == 'disable'){ $this->return_JSON(['error' => $this->plugin->gettext('backup_code_deactivated')]); return; } if($action == 'enable' || $action == 'regen_backup_codes'){ $resp = $this->get_2fa_backup_codes($this->email, true); if(empty($resp)){ $this->return_JSON(['error' => $this->plugin->gettext('write_error')]); return; } $this->rc->session->append($this->session_key,'soft2fa_verify', true); $this->return_JSON(['message' => ($action == 'enable' ? $this->plugin->gettext('backup_code_activated') : $this->plugin->gettext('backup_code_regenerated'))]); return; } unset($data[$this->email]['2fa']['backup_codes']); $resp = $this->plugin->writedata($this->conf_path, $data); if(empty($resp)){ $this->return_JSON(['error' => $this->plugin->gettext('write_error')]); return; } $this->rc->session->remove('soft2fa_verify'); $this->return_JSON(['message' => $this->plugin->gettext('backup_code_deactivated')]); return; } if($type == 'whitelist'){ if(!in_array($action, ['add', 'delete'])){ $this->return_JSON(['error' => $this->plugin->gettext('invalid_action')]); return; } if($action == 'delete'){ $id = $token = rcube_utils::get_input_value('id', rcube_utils::INPUT_POST); if(!is_numeric($id) || strlen($id) ===0 || !isset($data[$this->email]['2fa']['whitelist'][$id])){ $this->return_JSON(['error' => $this->plugin->gettext('invlid_id')]); return; } unset($data[$this->email]['2fa']['whitelist'][$id]); $resp = $this->plugin->writedata($this->conf_path, $data); if(empty($resp)){ $this->return_JSON(['error' => $this->plugin->gettext('write_error')]); return; } $this->rc->session->append($this->session_key,'soft2fa_verify', true); $this->return_JSON(['message' => $this->plugin->gettext('whitelist_updated')]); return; } // Add IP $startip = $token = rcube_utils::get_input_value('start_ip', rcube_utils::INPUT_POST); $endip = $token = rcube_utils::get_input_value('end_ip', rcube_utils::INPUT_POST); if(empty($startip)){ $this->return_JSON(['message' => $this->plugin->gettext('invalid_input')]); return; } $w_list = !empty($data[$this->email]['2fa']['whitelist']) ? $data[$this->email]['2fa']['whitelist'] : []; $this->iprange_validate($startip, $endip, $w_list, $error); if(!empty($error)){ $exists[] = $startip.' - '.$endip; }else{ $data[$this->email]['2fa']['whitelist'][] = array( 'start' => $startip, 'end' => empty($endip) ? $startip : $endip, 'time' => time() ); } if(!empty($exists)){ $error['ip'] = implode(', <br>', $exists); $error = array_unique($error); $this->return_JSON(['message' => $error[0], 'prevent_refresh' => 1]); return false; } // Update conf $resp = $this->plugin->writedata($this->conf_path, $data); if(empty($resp)){ $this->return_JSON(['error' => $this->plugin->gettext('write_error')]); return; } $this->return_JSON(['message' => $this->plugin->gettext('whitelist_updated')]); } $this->return_JSON(['error' => $this->plugin->gettext('method_error')]); } private function return_JSON($data = []){ if(empty($data)){ return; } $this->rc->output->command('plugin.soft2fa_response', $data); $this->rc->output->send(); } private function get_2fa_backup_codes($email = '', $genrate = false){ global $globals; if(empty($email)){ return; } $codes = []; $data = $this->plugin->loaddata($this->conf_path); if(!empty($data[$email]['2fa']['backup_codes'])){ $codes = $data[$email]['2fa']['backup_codes']; } if(!empty($genrate)){ $tmp_codes = []; for ($i=0; $i <= 7; $i++) { $code = base64_encode(strtoupper($this->plugin->generateRandStr(10))); $tmp_codes[] = [ 'code' => $code, 'status' => false ]; } $data[$email]['2fa']['backup_codes'] = $tmp_codes; $resp = $this->plugin->writedata($this->conf_path, $data); if(empty($resp)){ return false; } $codes = $tmp_codes; } return $codes; } private function get_2fa_auth_data($email = '', $reset = false){ global $globals; if(empty($email)){ return; } $data = $this->plugin->loaddata($this->conf_path); $app_key = ''; if(!empty($data[$email]['2fa']['auth']['key'])){ $app_key = $data[$email]['2fa']['auth']['key']; } $status = false; if(!empty($data[$email]['2fa']['auth']['status'])){ $status = $data[$email]['2fa']['auth']['status']; } $settings = array(); // For 2fa_app we must be prepared $settings['2fa_key'] = empty($app_key) ? '' : base64_decode($app_key);// Just decode it $settings['2fa_status'] = $status; // We might need to create a 10 char secret KEY for 2fa App based if(empty($settings['2fa_key']) || !empty($reset)){ // Generate $settings['2fa_key'] = strtoupper($this->plugin->generateRandStr(10)); $data[$email]['2fa']['auth']['key'] = base64_encode($settings['2fa_key']); // Update conf $this->plugin->writedata($this->conf_path, $data); } // Base32 Key $settings['2fa_key32'] = Base32::encode($settings['2fa_key']); // The QR Code text $settings['2fa_qr'] = 'otpauth://'.(empty($settings['2fa_type']) ? 'totp' : $settings['2fa_type']).'/'.rawurlencode('Webuzo - Webmail').':'.$email.'?secret='.Base32::encode($settings['2fa_key']).'&issuer='.rawurlencode($globals['sn']).'&counter='; // Time now $settings['2fa_server_time'] = date('Y-m-d H:i:s', time()); // Current OTP $settings['2fa_otp'] = $this->soft2fa_app_key($settings); return $settings; } private function soft2fa_app_key($settings, $length = 6, $counter = 0){ $key = $settings['2fa_key']; $type = (empty($settings['2fa_type']) ? 'totp' : $settings['2fa_type']); if($type == 'hotp'){ $stored_in_db = 1; $counter = !empty($counter) ? $counter : $stored_in_db; $res = HOTP::generateByCounter($key, $counter); }else{ $time = !empty($counter) ? $counter : time(); $res = HOTP::generateByTime($key, 30, $time); } return $res->toHotp($length); } private function valid_ip($ip, $version = '4'){ $flag = ($version == '4' ? FILTER_FLAG_IPV4 : FILTER_FLAG_IPV6); return filter_var($ip, FILTER_VALIDATE_IP, $flag); } // IP range validations public function iprange_validate($start_ip, $end_ip, $cur_list, &$error = array()){ if(!function_exists('inet_ptoi')){ return false; } if(empty($start_ip)){ $cur_error[] = $this->plugin->gettext('error_start_ip'); } // If no end IP we consider only 1 IP if(empty($end_ip)){ $end_ip = $start_ip; } if(!$this->valid_ip($start_ip)){ $cur_error[] = $this->plugin->gettext('error_val_start_ip'); } if(!$this->valid_ip($end_ip)){ $cur_error[] = $this->plugin->gettext('error_val_end_ip'); } if(inet_ptoi($start_ip) > inet_ptoi($end_ip)){ // BUT, if 0.0.0.1 - 255.255.255.255 is given, it will not work if(inet_ptoi($start_ip) >= 0 && inet_ptoi($end_ip) < 0){ // This is right }else{ $cur_error[] = $this->plugin->gettext('error_ip_len'); } } if(!empty($cur_error)){ foreach($cur_error as $rk => $rv){ $error[] = $rv; } return $error; } if(!empty($cur_list)){ foreach($cur_list as $k => $v){ // This is to check if there is any other range exists with the same Start or End IP if(( inet_ptoi($start_ip) <= inet_ptoi($v['start']) && inet_ptoi($v['start']) <= inet_ptoi($end_ip) ) || ( inet_ptoi($start_ip) <= inet_ptoi($v['end']) && inet_ptoi($v['end']) <= inet_ptoi($end_ip) ) ){ $cur_error[] = $this->plugin->gettext('error_ip_confl');; break; } // This is to check if there is any other range exists with the same Start IP if(inet_ptoi($v['start']) <= inet_ptoi($start_ip) && inet_ptoi($start_ip) <= inet_ptoi($v['end'])){ $cur_error[] = $this->plugin->gettext('error_start_ip_exits'); break; } // This is to check if there is any other range exists with the same End IP if(inet_ptoi($v['start']) <= inet_ptoi($end_ip) && inet_ptoi($end_ip) <= inet_ptoi($v['end'])){ $cur_error[] = $this->plugin->gettext('error_end_ip_exits'); break; } } } if(!empty($cur_error)){ foreach($cur_error as $rk => $rv){ $error[] = $rv; } return $error; } return true; } public function check_whitelist(){ $data = $this->plugin->loaddata($this->conf_path); $user_ip = $_SERVER["REMOTE_ADDR"]; // Is IP whitelisted ? $whitelist = $data[$this->email]['2fa']['whitelist']; // Whitelist empty? if(empty($whitelist)){ return false; } $result = 0; foreach($whitelist as $k => $v){ // Is the IP in the whitelist ? if(inet_ptoi($v['start']) <= inet_ptoi($user_ip) && inet_ptoi($user_ip) <= inet_ptoi($v['end'])){ $result = 1; break; } // Is it in a wider range ? if(inet_ptoi($v['start']) >= 0 && inet_ptoi($v['end']) < 0){ if(inet_ptoi($v['start']) <= inet_ptoi($user_ip) || inet_ptoi($user_ip) <= inet_ptoi($v['end'])){ $result = 1; break; } } } // You are whitelisted if(!empty($result)){ return true; } return false; } }