RoundCube Webmail
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

265 lines
9.9 KiB

  1. <?php
  2. /*
  3. +-------------------------------------------------------------------------+
  4. | Roundcube Webmail IMAP Client |
  5. | |
  6. | Copyright (C) The Roundcube Dev Team |
  7. | |
  8. | This program is free software: you can redistribute it and/or modify |
  9. | it under the terms of the GNU General Public License (with exceptions |
  10. | for skins & plugins) as published by the Free Software Foundation, |
  11. | either version 3 of the License, or (at your option) any later version. |
  12. | |
  13. | This file forms part of the Roundcube Webmail Software for which the |
  14. | following exception is added: Plugins and Skins which merely make |
  15. | function calls to the Roundcube Webmail Software, and for that purpose |
  16. | include it by reference shall not be considered modifications of |
  17. | the software. |
  18. | |
  19. | If you wish to use this file in another project or create a modified |
  20. | version that will not be part of the Roundcube Webmail Software, you |
  21. | may remove the exception above and use this source code under the |
  22. | original version of the license. |
  23. | |
  24. | This program is distributed in the hope that it will be useful, |
  25. | but WITHOUT ANY WARRANTY; without even the implied warranty of |
  26. | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
  27. | GNU General Public License for more details. |
  28. | |
  29. | You should have received a copy of the GNU General Public License |
  30. | along with this program. If not, see https://www.gnu.org/licenses/. |
  31. | |
  32. +-------------------------------------------------------------------------+
  33. | Author: Thomas Bruederli <roundcube@gmail.com> |
  34. | Author: Aleksander Machniak <alec@alec.pl> |
  35. +-------------------------------------------------------------------------+
  36. */
  37. // include environment
  38. require_once __DIR__ . '/../program/include/iniset.php';
  39. // init application, start session, init output class, etc.
  40. $RCMAIL = rcmail::get_instance(0, $GLOBALS['env'] ?? null);
  41. // Make the whole PHP output non-cacheable (#1487797)
  42. $RCMAIL->output->nocacheing_headers();
  43. $RCMAIL->output->common_headers(!empty($_SESSION['user_id']));
  44. // turn on output buffering
  45. ob_start();
  46. // check the initial error state
  47. if ($RCMAIL->config->get_error() || $RCMAIL->db->is_error()) {
  48. rcmail_fatal_error();
  49. }
  50. // error steps
  51. if ($RCMAIL->action == 'error' && !empty($_GET['_code'])) {
  52. rcmail::raise_error(['code' => hexdec($_GET['_code'])], false, true);
  53. }
  54. // check if https is required (for login) and redirect if necessary
  55. if (empty($_SESSION['user_id']) && ($force_https = $RCMAIL->config->get('force_https', false))) {
  56. // force_https can be true, <hostname>, <hostname>:<port>, <port>
  57. if (!is_bool($force_https)) {
  58. [$host, $port] = explode(':', $force_https);
  59. if (is_numeric($host) && empty($port)) {
  60. $port = $host;
  61. $host = '';
  62. }
  63. }
  64. if (empty($port)) {
  65. $port = 443;
  66. }
  67. if (!rcube_utils::https_check($port)) {
  68. if (empty($host)) {
  69. $host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']);
  70. }
  71. if ($port != 443) {
  72. $host .= ':' . $port;
  73. }
  74. header('Location: https://' . $host . $_SERVER['REQUEST_URI']);
  75. exit;
  76. }
  77. }
  78. // trigger startup plugin hook
  79. $startup = $RCMAIL->plugins->exec_hook('startup', ['task' => $RCMAIL->task, 'action' => $RCMAIL->action]);
  80. $RCMAIL->set_task($startup['task']);
  81. $RCMAIL->action = $startup['action'];
  82. $session_error = null;
  83. // try to log in
  84. if ($RCMAIL->task == 'login' && $RCMAIL->action == 'login') {
  85. $request_valid = !empty($_SESSION['temp']) && $RCMAIL->check_request();
  86. $pass_charset = $RCMAIL->config->get('password_charset', 'UTF-8');
  87. // purge the session in case of new login when a session already exists
  88. if ($request_valid) {
  89. $RCMAIL->kill_session();
  90. }
  91. $auth = $RCMAIL->plugins->exec_hook('authenticate', [
  92. 'host' => $RCMAIL->autoselect_host(),
  93. 'user' => trim(rcube_utils::get_input_string('_user', rcube_utils::INPUT_POST)),
  94. 'pass' => rcube_utils::get_input_string('_pass', rcube_utils::INPUT_POST, true, $pass_charset),
  95. 'valid' => $request_valid,
  96. 'error' => null,
  97. 'cookiecheck' => true,
  98. ]);
  99. // Login
  100. if ($auth['valid'] && !$auth['abort']
  101. && $RCMAIL->login($auth['user'], $auth['pass'], $auth['host'], $auth['cookiecheck'])
  102. ) {
  103. // create new session ID, don't destroy the current session
  104. // it was destroyed already by $RCMAIL->kill_session() above
  105. $RCMAIL->session->remove('temp');
  106. $RCMAIL->session->regenerate_id(false);
  107. // send auth cookie if necessary
  108. $RCMAIL->session->set_auth_cookie();
  109. // log successful login
  110. $RCMAIL->log_login();
  111. // restore original request parameters
  112. $query = [];
  113. if ($url = rcube_utils::get_input_string('_url', rcube_utils::INPUT_POST)) {
  114. parse_str($url, $query);
  115. // prevent endless looping on login page
  116. if (!empty($query['_task']) && $query['_task'] == 'login') {
  117. unset($query['_task']);
  118. }
  119. // prevent redirect to compose with specified ID (#1488226)
  120. if (!empty($query['_action']) && $query['_action'] == 'compose' && !empty($query['_id'])) {
  121. $query = ['_action' => 'compose'];
  122. }
  123. }
  124. // allow plugins to control the redirect url after login success
  125. $redir = $RCMAIL->plugins->exec_hook('login_after', $query + ['_task' => 'mail']);
  126. unset($redir['abort'], $redir['_err']);
  127. // send redirect
  128. $RCMAIL->output->redirect($redir, 0, true);
  129. } else {
  130. if (!$auth['valid']) {
  131. $error_code = rcmail::ERROR_INVALID_REQUEST;
  132. } else {
  133. $error_code = is_numeric($auth['error']) ? $auth['error'] : $RCMAIL->login_error();
  134. }
  135. $error_labels = [
  136. rcmail::ERROR_STORAGE => 'storageerror',
  137. rcmail::ERROR_COOKIES_DISABLED => 'cookiesdisabled',
  138. rcmail::ERROR_INVALID_REQUEST => 'invalidrequest',
  139. rcmail::ERROR_INVALID_HOST => 'invalidhost',
  140. rcmail::ERROR_RATE_LIMIT => 'accountlocked',
  141. ];
  142. if (!empty($auth['error']) && !is_numeric($auth['error'])) {
  143. $error_message = $auth['error'];
  144. } else {
  145. $error_message = !empty($error_labels[$error_code]) ? $error_labels[$error_code] : 'loginfailed';
  146. }
  147. $RCMAIL->output->show_message($error_message, 'warning');
  148. // log failed login
  149. $RCMAIL->log_login($auth['user'], true, $error_code);
  150. $RCMAIL->plugins->exec_hook('login_failed', [
  151. 'code' => $error_code,
  152. 'host' => $auth['host'],
  153. 'user' => $auth['user'],
  154. ]);
  155. if (!isset($_SESSION['user_id'])) {
  156. $RCMAIL->kill_session();
  157. }
  158. }
  159. }
  160. // end session
  161. elseif ($RCMAIL->task == 'logout' && !empty($_SESSION['user_id'])) {
  162. $RCMAIL->request_security_check(rcube_utils::INPUT_GET | rcube_utils::INPUT_POST);
  163. $userdata = [
  164. 'user' => $_SESSION['username'],
  165. 'host' => $_SESSION['storage_host'],
  166. 'lang' => $RCMAIL->user->language,
  167. ];
  168. $RCMAIL->output->show_message('loggedout');
  169. $RCMAIL->logout_actions();
  170. $RCMAIL->kill_session();
  171. $RCMAIL->plugins->exec_hook('logout_after', $userdata);
  172. }
  173. // check session and auth cookie
  174. elseif ($RCMAIL->task != 'login' && !empty($_SESSION['user_id'])) {
  175. if (!$RCMAIL->session->check_auth()) {
  176. $RCMAIL->kill_session();
  177. $session_error = 'sessionerror';
  178. }
  179. }
  180. // not logged in -> show login page
  181. if (empty($RCMAIL->user->ID)) {
  182. if (
  183. $session_error
  184. || (!empty($_REQUEST['_err']) && $_REQUEST['_err'] === 'session')
  185. || ($session_error = $RCMAIL->session_error())
  186. ) {
  187. $RCMAIL->output->show_message($session_error ?: 'sessionerror', 'error', null, true, -1);
  188. }
  189. if ($RCMAIL->output->ajax_call || $RCMAIL->output->get_env('framed')) {
  190. $RCMAIL->output->command('session_error', $RCMAIL->url(['_err' => 'session']));
  191. $RCMAIL->output->send('iframe');
  192. }
  193. // Display a warning if installer is active
  194. if ($RCMAIL->config->get('enable_installer') && is_readable(__DIR__ . '/installer.php')) {
  195. $RCMAIL->output->add_footer(rcmail_install::logonWarning());
  196. }
  197. $plugin = $RCMAIL->plugins->exec_hook('unauthenticated', [
  198. 'task' => 'login',
  199. 'error' => $session_error,
  200. // Return 401 only on failed logins (#7010)
  201. 'http_code' => empty($session_error) && !empty($error_message) ? 401 : 200,
  202. ]);
  203. $RCMAIL->set_task($plugin['task']);
  204. if ($plugin['http_code'] == 401) {
  205. http_response_code(401);
  206. }
  207. $RCMAIL->output->send($plugin['task']);
  208. } else {
  209. // CSRF prevention
  210. $RCMAIL->request_security_check();
  211. // check access to disabled actions
  212. $disabled_actions = (array) $RCMAIL->config->get('disabled_actions');
  213. if (in_array($RCMAIL->task . '.' . ($RCMAIL->action ?: 'index'), $disabled_actions)) {
  214. rcube::raise_error([
  215. 'code' => 404,
  216. 'message' => 'Action disabled',
  217. ], true, true);
  218. }
  219. }
  220. $RCMAIL->action_handler();