src/Controller/IndexController.php line 46

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use App\Model\Account;
  4. use App\Security\User;
  5. use App\Util\AccountTools;
  6. use App\Util\Api;
  7. use App\Util\AppLogger;
  8. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  9. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  10. use Symfony\Component\HttpFoundation\Cookie;
  11. use Symfony\Component\HttpFoundation\Request;
  12. use Symfony\Component\HttpFoundation\RequestStack;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpFoundation\RedirectResponse;
  15. use Symfony\Component\Routing\Annotation\Route;
  16. use Symfony\Component\Security\Core\Security;
  17. use Symfony\Component\Security\Core\Authentication\Token\SwitchUserToken;
  18. use Symfony\Component\HttpFoundation\Session\SessionInterface;
  19. use Symfony\Contracts\Translation\TranslatorInterface;
  20. class IndexController extends AbstractController
  21. {
  22.     private $params;
  23.     private $security;
  24.     private $session;
  25.     private $logger;
  26.     private $translator;
  27.     private $requestStack;
  28.     
  29.     public function __construct(ParameterBagInterface $paramsSecurity $securityAppLogger $logger,
  30.         TranslatorInterface $translatorRequestStack $requestStack)
  31.     {
  32.         $this->params $params;
  33.         $this->security $security;
  34.         $this->logger $logger;
  35.         $this->translator $translator;
  36.         $this->requestStack $requestStack;
  37.         $this->session $this->requestStack->getSession();
  38.     }
  39.     
  40.     /**
  41.      * @Route("/", name="index")
  42.      */
  43.     public function index(Request $request)
  44.     {
  45.         return $this->redirectToRoute('qu_main');
  46.     }
  47.     
  48.     /**
  49.      * for session refreshing (AJAX calls)
  50.      * @Route("/refresh", name="refresh")
  51.      */
  52.     public function refresh(Request $request)
  53.     {        
  54.         $account $this->security->getUser()->getAccount();   
  55.         $sessionTime $account->findSOption('cfgSessionTime') == null $this->params->get('session_max_idle_time') : $account->findSOption('cfgSessionTime');
  56.         $sessionEndTime time() + $sessionTime;
  57.         return new Response($sessionEndTime);
  58.     }
  59.     
  60.     /**
  61.      * @Route("/remind-password", name="remind-password")
  62.      */
  63.     public function passwordRemind(Request $request)
  64.     {
  65.         return $this->render('security/password_remind.html.twig');
  66.     }
  67.     
  68.     /**
  69.      * @Route("/change_lang/{lang}", name="change_lang", requirements={"lang"="en|pl|es"})
  70.      */
  71.     public function changeLanguage($langRequest $request)
  72.     {
  73.         $referer $request->headers->get('referer');
  74.         
  75.         if($referer == null || $referer == "") {
  76.             $referer "/";
  77.         }
  78.         
  79.         $this->requestStack->getSession()->set('sessionlocale'$lang);
  80.         $cookie Cookie::create(User::LOCALE_COOKIE_NAME$langtime() + 24*7*3600'/');
  81.         $response $this->redirect($referer);
  82.         $response->headers->setCookie($cookie);
  83.         return $response;
  84.     }
  85.     
  86.     /**
  87.      * @Route("/app_init.js", name="js_init")
  88.      */
  89.     public function jsAppInit(Request $request)
  90.     {
  91.         $sessionTime $this->params->get('session_max_idle_time');
  92.         $paginationItems Account::NUMBER_OF_ITEMS;
  93.         $userPaginationItems Account::NUMBER_OF_ITEMS;
  94.         $historyPaginationItems Account::NUMBER_OF_ITEMS;
  95.         $passComplexity '';
  96.         $options = [];
  97.         $totalMem = [];
  98.         
  99.         if($this->security->getUser() != null) {
  100.             $account $this->security->getUser()->getAccount();
  101.             $passComplexity $this->security->getUser()->getPassComplexity();
  102.             $paginationItems $account->findSOption('cfgPagination') == null ?
  103.             (isset($_SERVER['APP_DEFAULT_PER_PAGE']) ? $_SERVER['APP_DEFAULT_PER_PAGE'] : Account::NUMBER_OF_ITEMS)
  104.             : $account->findSOption('cfgPagination');
  105.             $userPaginationItems $account->findSOption('cfgUserPagination') == null ?
  106.             (isset($_SERVER['APP_DEFAULT_PER_PAGE']) ? $_SERVER['APP_DEFAULT_PER_PAGE'] : Account::NUMBER_OF_ITEMS)
  107.             : $account->findSOption('cfgUserPagination');
  108.             $historyPaginationItems $account->findSOption('cfgHistoryPagination') == null ?
  109.             (isset($_SERVER['APP_DEFAULT_PER_PAGE']) ? $_SERVER['APP_DEFAULT_PER_PAGE'] : Account::NUMBER_OF_ITEMS)
  110.             : $account->findSOption('cfgHistoryPagination');
  111.             $sessionTime $account->findSOption('cfgSessionTime') == null $this->params->get('session_max_idle_time') : $account->findSOption('cfgSessionTime');            
  112.             $options $account->getSOptions();
  113.             
  114.             $totalMem = [];
  115.             $totalMem['mem'] = $totalMem['swap'] = 0;
  116.             
  117.             $userRoles $this->security->getUser()->getRoles();
  118.             
  119.             // total mem only for admin's chars
  120.             if(array_key_exists('HTTP_REFERER'$_SERVER) && str_contains($_SERVER['HTTP_REFERER'], 'charts')) {
  121.                 foreach($userRoles as $role) {
  122.                     if($role == 'ROLE_ADMIN') {
  123.                         $mem Api::getMemoryStats();
  124.                         
  125.                         $totalMem['mem'] = $mem['mem']['total'];
  126.                         $totalMem['swap'] = $mem['swap']['total'];
  127.                         break;
  128.                     } 
  129.                 }          
  130.             }
  131.         }
  132.         
  133.         $clearPasswords 'false';
  134.         
  135.         if(isset($_SERVER['APP_ALLOW_CLEAR_PASS'])) {
  136.             if($_SERVER['APP_ALLOW_CLEAR_PASS'] == 'true') {
  137.                 $clearPasswords 'true';
  138.             }
  139.         }
  140.         
  141.         $catchallAccounts = [];
  142.         
  143.         if($this->session->has('catchallAccounts')) {
  144.             $catchallAccounts $this->session->get('catchallAccounts');
  145.         }
  146.         else {
  147.             try {
  148.                 $cas Api::getCatchalls();
  149.                 
  150.                 foreach($cas as $ca) {
  151.                     $catchallAccounts[$ca->param->svalue] = $ca->user_name '@' $ca->user_domain;
  152.                 }
  153.                 
  154.                 $this->session->set('catchallAccounts'$catchallAccounts);
  155.             }
  156.             catch(\Exception $e) {}
  157.         }
  158.             
  159.         $sessionEndTime time() + $sessionTime;
  160.         $systemAliases = [];
  161.         
  162.         if(isset($_SERVER['APP_SYSTEM_ALIASES'])) {
  163.             $systemAliases AccountTools::envToArray($_SERVER['APP_SYSTEM_ALIASES']);
  164.         }
  165.         
  166.         $passGenReq = ['lower''upper''digit''special'];
  167.         
  168.         if(isset($_SERVER['APP_PASS_GEN_REQ'])) {
  169.             $passGenReq AccountTools::envToArray($_SERVER['APP_PASS_GEN_REQ']);
  170.         }
  171.         
  172.         $passGenLength 8;
  173.         
  174.         if(isset($_SERVER['APP_PASS_GEN_LENGTH'])) {
  175.             $passGenLength $_SERVER['APP_PASS_GEN_LENGTH'];
  176.         }
  177.         
  178.         
  179.         
  180.         return $this->render('app_init.js.twig', [
  181.             'apiEndpoint' => Api::getEndpoint(),
  182.             'sessionEndTime' => $sessionEndTime,
  183.             'passComplexity' => $passComplexity,
  184.             'paginationItems' => $paginationItems,
  185.             'userPaginationItems' => $userPaginationItems,
  186.             'historyPaginationItems' => $historyPaginationItems,
  187.             'locale' => $this->requestStack->getSession()->get('sessionlocale'),
  188.             'userOptions' => json_encode($options),
  189.             'clearPasswords' => $clearPasswords,
  190.             'catchallAccounts' => $catchallAccounts,
  191.             'systemAliases' => $systemAliases,
  192.             'passGenLength' => $passGenLength,
  193.             'passGenReq' => $passGenReq,
  194.             'totalMem' => $totalMem,
  195.         ]);
  196.     }
  197.     
  198.     /**
  199.      * @Route("/relog-webmail/{forceUserPanel}/{forceLang}/{msg}", defaults={"forceLang"="", "forceUserPanel"=0, "msg"=""}, name="relog-webmail", requirements={"forceLang"="[a-z-]{2}"})
  200.      * 
  201.      * @param $forceLang string - force language in webmail (pl / en / es) or '--' if no forcing
  202.      * @param $forceUserPanel - force return URL to point to panel start page (1). If 0 than return URL is referrer
  203.      * @param $msg - if not null show message before relog
  204.      */
  205.     public function relogWebmail(Request $request$forceLang$forceUserPanel$msg)
  206.     {
  207.         $systemHealth Api::checkSystemHealth();
  208.         
  209.         // allow login while API is not responding
  210.         if($systemHealth == 1) {
  211.             $this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
  212.         }
  213.         $account $this->security->getUser()->getAccount();
  214.         $allowedServices $account->getAllowedServices();
  215.         if(!in_array('Webmail'$allowedServices)) {
  216.             $this->addFlash('error'$this->translator->trans('Webmail access is not allowed.'));
  217.             return $this->redirectToRoute("qu_main");
  218.         }
  219.         
  220.         $referer $request->headers->get('referer');
  221.         
  222.         if(!$this->requestStack->getSession()->has('storedPassword')) {
  223.             if(strlen($referer)) {
  224.                 return $this->redirect($referer);
  225.             }
  226.                 
  227.             return $this->redirectToRoute("app_login");
  228.         }
  229.             
  230.         if(!isset($_SERVER['ROUNDCUBE_URL'])) {
  231.             die('ROUNDCUBE_URL not defined');
  232.         }
  233.         
  234.         $imapUser AccountTools::decryptString($this->requestStack->getSession()->get('storedUsername'));
  235.         $imapPass urlencode(AccountTools::decryptString($this->requestStack->getSession()->get('storedPassword')));
  236.         
  237.         if($systemHealth == 1) {
  238.             // impersonated login?
  239.             if($this->isGranted('IS_IMPERSONATOR')) {
  240.                 $token $this->security->getToken();
  241.                 
  242.                 if ($token instanceof SwitchUserToken) {
  243.                     $impersonatorUser $token->getOriginalToken()->getUser();
  244.                 }
  245.                 
  246.                 $imapUser $this->security->getUser()->getUsername() . '*' $impersonatorUser->getUsername();
  247.             }
  248.         }
  249.         
  250.         // check if IMAP is working
  251.         if($systemHealth == 1) {
  252.             $systemStatus Api::getSystemStatus();
  253.             
  254.             if(($systemStatus['imap_143_status'] != 'OK') && ($systemStatus['imap_993_status'] != 'OK')) {
  255.                 $this->addFlash('error'$this->translator->trans('IMAP server not responding, relog to webmail not possible.'));
  256.                 return $this->redirectToRoute("qu_main");
  257.             }
  258.         }
  259.         
  260.         
  261.         $ch curl_init();
  262.         $cmd $_SERVER['ROUNDCUBE_INTERNAL_URL'] . '?_task=login&_qmailux=true';
  263.         $data '_task=login&_action=login&_timezone=_default_&_user=' $imapUser;
  264.         $data .= '&_pass=' $imapPass '&_qmailux=true';
  265.         $data .= '&submit';
  266.         
  267.         curl_setopt($chCURLOPT_POSTFIELDS$data);
  268.         curl_setopt($chCURLOPT_HTTPHEADER, array('Content-Type:application/x-www-form-urlencoded'));
  269.         curl_setopt($chCURLOPT_HEADERtrue); // return header, too
  270.         curl_setopt($chCURLOPT_URL$cmd);
  271.         curl_setopt($chCURLOPT_RETURNTRANSFER1);
  272.         curl_setopt($chCURLOPT_POST1);
  273.         
  274.         if(strstr($_SERVER['ROUNDCUBE_INTERNAL_URL'], 'local_internal') === false) {
  275.             curl_setopt($chCURLOPT_SSL_VERIFYHOST2);
  276.             curl_setopt($chCURLOPT_SSL_VERIFYPEER1);    
  277.         }
  278.         else {
  279.             curl_setopt($chCURLOPT_SSL_VERIFYHOST0);
  280.             curl_setopt($chCURLOPT_SSL_VERIFYPEER0);
  281.         }
  282.         
  283.         if(($output curl_exec($ch)) === false) {
  284.             $this->addFlash('error'$this->translator->trans('Relog to webmail not possible, contact administrator.'));
  285.             $this->logger->log('IndexController relogWebmail curl error: '$request, ['error_code' => curl_errno($ch), 'error_message' => curl_error($ch)], 'critical');
  286.             return $this->redirectToRoute("qu_main");
  287.         }
  288.         
  289.         curl_close($ch);
  290.         
  291.         $outputArr explode("\n"$output);
  292.         $location '';
  293.         
  294.         foreach($outputArr as $line) {
  295.             if(preg_match('/^Set-Cookie:/i'$line)) {
  296.                 //echo "line = $line<br>";
  297.                 $cookieArr explode('; 'str_ireplace('Set-Cookie: '''$line));
  298.                 $cookieNameVal explode('='$cookieArr[0]);
  299.                 
  300.                 if($cookieNameVal[1] == '-del-') {
  301.                     setcookie($cookieNameVal[0], $cookieNameVal[1], time() - 3600'/');
  302.                 }
  303.                 else {
  304.                     setcookie($cookieNameVal[0], $cookieNameVal[1], null'/');
  305.                 }
  306.             }
  307.             
  308.             if(preg_match('/^Location:/i'$line)) {
  309.                 $location str_ireplace('Location: '''$line);
  310.                 $location trim($location);
  311.             }   
  312.                         
  313.         }
  314.         
  315.         if((!array_key_exists('DONT_FORCE_LANG_ROUNDCUBE'$_SERVER)) || ($_SERVER['DONT_FORCE_LANG_ROUNDCUBE'] == false)) {
  316.             if($forceLang != '' && $forceLang != '--') {
  317.                 $forceLang $this->_localeToLang($forceLang);
  318.                 setcookie('forcelang'$forceLangnull'/');
  319.             }
  320.         }
  321.         
  322.         // set cookie if this is impersonated session
  323.         if($this->isGranted('IS_IMPERSONATOR')) {
  324.             setcookie('impersonated'10'/');
  325.         }
  326.         else {
  327.             setcookie('impersonated'''time() - 3600'/');
  328.         }
  329.         
  330.         if($forceUserPanel) {
  331.             setcookie('panelreturnurl'$this->generateUrl('qu_main'), null'/');
  332.         }
  333.         else {
  334.             if($referer != '') {
  335.                 // if referer is login page set main panel page intead
  336.                 if(strstr($referer$this->generateUrl('app_login')) || strstr($referer$this->generateUrl('2fa_login'))) {
  337.                     if($this->security->isGranted('ROLE_POSTMASTER')) {
  338.                         setcookie('panelreturnurl'$this->generateUrl('qp_main'), null'/');
  339.                     }
  340.                     else {
  341.                         setcookie('panelreturnurl'$this->generateUrl('qu_main'), null'/');
  342.                     }
  343.                     
  344.                 }
  345.                 else {
  346.                     setcookie('panelreturnurl'$referer0'/');
  347.                 }
  348.                 
  349.             }
  350.             else {
  351.                 setcookie('panelreturnurl'''time() - 3600'/');
  352.             }
  353.         }
  354.         
  355.         if((strlen($msg) > 0) && array_key_exists('APP_LOGIN_TARGET_MSG'$_SERVER) && $_SERVER['APP_LOGIN_TARGET_MSG'] == 'true') {
  356.             return $this->forward('\App\Controller\IndexController::relogWebmailShowMsg', [
  357.                 'retLocation' => $location,
  358.                 'msg' => urlencode($msg),
  359.             ]);
  360.         }
  361.         else {
  362.             return $this->redirect($_SERVER['ROUNDCUBE_URL'] . $location);
  363.         }
  364.     }
  365.     
  366.     /**
  367.      * @Route("/relog-webmail-show-msg/{retLocation}/{msg}", name="relog-webmail-show-msg")
  368.      *
  369.      * @param $retLocation - where to redirect inside webmail
  370.      * @param $msg - message to show before relog
  371.      */
  372.     public function relogWebmailShowMsg($retLocation$msg)
  373.     {
  374.         return $this->render('index/relog_msg.html.twig', [
  375.             'retLocation' => $_SERVER['ROUNDCUBE_URL'] . $retLocation,
  376.             'msg' => urldecode($msg),
  377.         ]);   
  378.     }
  379.     
  380.     /**
  381.      * @Route("/general-error/", name="general-error")
  382.      */
  383.     public function generalError(Request $request)
  384.     {        
  385.         $account $this->security->getUser()->getAccount();
  386.         $skin $account->findSOption('cfgSkin') == null 'standard' $account->findSOption('cfgSkin');
  387.         return $this->render('skins/' $skin '/general_error.html.twig');
  388.     }
  389.     
  390.     /**
  391.      * catch-all routing in routes.yaml
  392.      */
  393.     public function apiPassThrough(Request $request)
  394.     {
  395.         $uri $request->getRequestUri();
  396.                 
  397.         $cmd preg_replace('/.*?\/api\//'''$uri);
  398.         $cmdData = [];
  399.         $cmdData['command'] = $cmd;
  400.         
  401.         if(($data $request->request->all())) {
  402.             $cmdData['data'] = $data;
  403.         }
  404.         
  405.         $this->denyAccessUnlessGranted('API_PASS_THROUGH'$cmdData);
  406.         
  407.         
  408.         if($data) {
  409.             $ret Api::passThrough($cmdjson_encode($data));
  410.         }
  411.         else {
  412.             $ret Api::passThrough($cmd);
  413.         }
  414.         
  415.         return new Response($ret);
  416.     }
  417.     /**
  418.      * @Route("/check2fa/{token}", name="check-two-factor", requirements={"token"="^[a-z0-9]{48}$"})
  419.      * 
  420.      * checks if the token is valid when user adds new alternative email address for 2FA
  421.      * 
  422.      * @param $token - token to check
  423.      */
  424.     public function checkTwoFactor(Request $requeststring $token)
  425.     {
  426.         $skin 'raw';
  427.         if(Api::checkTwoFactorToken($token)) {
  428.             $userEmail Api::getEmailForTwoFactorToken($token);
  429.             $userArr explode('@'$userEmail);
  430.             if(count($userArr) != 2) {
  431.                 $this->addFlash('error'$this->translator->trans('Email for two factor authentication NOT linked - contact administrator'));
  432.                 $this->logger->log('Error while linking email for two factor authentication - email has no correct format'$request, ['userEmail' => $userEmail], 'error');
  433.             }
  434.             $account Api::getAccount($userArr[0], $userArr[1]);
  435.             if(!is_null($account)) {
  436.                 $account->setSOption('emailForAuthConfirmed''1');
  437.                 $account->setSOption('cfgTwoFactorEnabled''1');
  438.                 if(Api::saveAccountParameters($account)) {
  439.                     $this->addFlash('notice'$this->translator->trans('Email for two factor authentication successfuly linked. Two factor authentication is now enabled.'));
  440.                     $this->logger->log('Email for two factor authentication linked'$request, ['userEmail' => $userEmail]);
  441.                 }
  442.                 else {
  443.                     $this->addFlash('error'$this->translator->trans('Email for two factor authentication NOT linked, error saving configuration. Contact administrator.'));
  444.                     $this->logger->log('Error while linking email for two factor authentication'$request, ['userEmail' => $userEmail], 'error');
  445.                 }
  446.                 $skin $account->findSOption('cfgSkin') == null 'standard' $account->findSOption('cfgSkin');
  447.                 // if already logged in - reload config
  448.                 if($this->isGranted('ROLE_USER')) {
  449.                     $this->getUser()->loadAccount();
  450.                     $this->getUser()->setForceCodeNextTime();
  451.                 }
  452.             }
  453.         }
  454.         else {
  455.             $this->addFlash('error'$this->translator->trans('Two factor authentication token not valid or expired'));
  456.         }
  457.         
  458.         return $this->render('skins/' $skin '/check2fa_token.html.twig');
  459.     }
  460.     
  461.     private function _localeToLang($locale)
  462.     {
  463.         if($locale == 'pl') {
  464.             return 'pl_PL';
  465.         }
  466.         
  467.         if($locale == 'en') {
  468.             return 'en_US';
  469.         }
  470.         
  471.         if($locale == 'es') {
  472.             return 'es_ES';
  473.         }
  474.         
  475.         return $locale;
  476.     }
  477. }