functions.inc.php 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442
  1. <?php
  2. function hash_password($password) {
  3. $salt_str = bin2hex(openssl_random_pseudo_bytes(8));
  4. return "{SSHA256}".base64_encode(hash('sha256', $password . $salt_str, true) . $salt_str);
  5. }
  6. function hasDomainAccess($username, $role, $domain) {
  7. global $pdo;
  8. if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
  9. return false;
  10. }
  11. if (empty($domain) || !is_valid_domain_name($domain)) {
  12. return false;
  13. }
  14. if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') {
  15. return false;
  16. }
  17. try {
  18. $stmt = $pdo->prepare("SELECT `domain` FROM `domain_admins`
  19. WHERE (
  20. `active`='1'
  21. AND `username` = :username
  22. AND (`domain` = :domain1 OR `domain` = (SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain2))
  23. )
  24. OR 'admin' = :role");
  25. $stmt->execute(array(':username' => $username, ':domain1' => $domain, ':domain2' => $domain, ':role' => $role));
  26. $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
  27. }
  28. catch(PDOException $e) {
  29. $_SESSION['return'] = array(
  30. 'type' => 'danger',
  31. 'msg' => 'MySQL: '.$e
  32. );
  33. return false;
  34. }
  35. if (!empty($num_results)) {
  36. return true;
  37. }
  38. return false;
  39. }
  40. function hasMailboxObjectAccess($username, $role, $object) {
  41. global $pdo;
  42. if (!filter_var($username, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
  43. return false;
  44. }
  45. if ($role != 'admin' && $role != 'domainadmin' && $role != 'user') {
  46. return false;
  47. }
  48. if ($username == $object) {
  49. return true;
  50. }
  51. try {
  52. $stmt = $pdo->prepare("SELECT `domain` FROM `mailbox` WHERE `username` = :object");
  53. $stmt->execute(array(':object' => $object));
  54. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  55. if (isset($row['domain']) && hasDomainAccess($username, $role, $row['domain'])) {
  56. return true;
  57. }
  58. }
  59. catch(PDOException $e) {
  60. error_log($e);
  61. return false;
  62. }
  63. return false;
  64. }
  65. function pem_to_der($pem_key) {
  66. // Need to remove BEGIN/END PUBLIC KEY
  67. $lines = explode("\n", trim($pem_key));
  68. unset($lines[count($lines)-1]);
  69. unset($lines[0]);
  70. return base64_decode(implode('', $lines));
  71. }
  72. function generate_tlsa_digest($hostname, $port, $starttls = null) {
  73. if (!is_valid_domain_name($hostname)) {
  74. return "Not a valid hostname";
  75. }
  76. if (empty($starttls)) {
  77. $context = stream_context_create(array("ssl" => array("capture_peer_cert" => true, 'verify_peer' => false, 'allow_self_signed' => true)));
  78. $stream = stream_socket_client('tls://' . $hostname . ':' . $port, $error_nr, $error_msg, 5, STREAM_CLIENT_CONNECT, $context);
  79. // error_nr can be 0, so checking against error_msg
  80. if ($error_msg) {
  81. return $error_nr . ': ' . $error_msg;
  82. }
  83. }
  84. else {
  85. $stream = stream_socket_client('tcp://' . $hostname . ':' . $port, $error_nr, $error_msg, 5);
  86. if ($error_msg) {
  87. return $error_nr . ': ' . $error_msg;
  88. }
  89. $banner = fread($stream, 512 );
  90. if (preg_match("/^220/i", $banner)) { // SMTP
  91. fwrite($stream,"HELO tlsa.generator.local\r\n");
  92. fread($stream, 512);
  93. fwrite($stream,"STARTTLS\r\n");
  94. fread($stream, 512);
  95. }
  96. elseif (preg_match("/imap.+starttls/i", $banner)) { // IMAP
  97. fwrite($stream,"A1 STARTTLS\r\n");
  98. fread($stream, 512);
  99. }
  100. elseif (preg_match("/^\+OK/", $banner)) { // POP3
  101. fwrite($stream,"STLS\r\n");
  102. fread($stream, 512);
  103. }
  104. elseif (preg_match("/^OK/m", $banner)) { // Sieve
  105. fwrite($stream,"STARTTLS\r\n");
  106. fread($stream, 512);
  107. }
  108. else {
  109. return 'Unknown banner: "' . htmlspecialchars(trim($banner)) . '"';
  110. }
  111. // Upgrade connection
  112. stream_set_blocking($stream, true);
  113. stream_context_set_option($stream, 'ssl', 'capture_peer_cert', true);
  114. stream_context_set_option($stream, 'ssl', 'verify_peer', false);
  115. stream_context_set_option($stream, 'ssl', 'allow_self_signed', true);
  116. stream_socket_enable_crypto($stream, true, STREAM_CRYPTO_METHOD_ANY_CLIENT);
  117. stream_set_blocking($stream, false);
  118. }
  119. $params = stream_context_get_params($stream);
  120. if (!empty($params['options']['ssl']['peer_certificate'])) {
  121. $key_resource = openssl_pkey_get_public($params['options']['ssl']['peer_certificate']);
  122. // We cannot get ['rsa']['n'], the binary data would contain BEGIN/END PUBLIC KEY
  123. $key_data = openssl_pkey_get_details($key_resource)['key'];
  124. return '3 1 1 ' . openssl_digest(pem_to_der($key_data), 'sha256');
  125. }
  126. else {
  127. return 'Error: Cannot read peer certificate';
  128. }
  129. }
  130. function verify_ssha256($hash, $password) {
  131. // Remove tag if any
  132. $hash = ltrim($hash, '{SSHA256}');
  133. // Decode hash
  134. $dhash = base64_decode($hash);
  135. // Get first 32 bytes of binary which equals a SHA256 hash
  136. $ohash = substr($dhash, 0, 32);
  137. // Remove SHA256 hash from decoded hash to get original salt string
  138. $osalt = str_replace($ohash, '', $dhash);
  139. // Check single salted SHA256 hash against extracted hash
  140. if (hash('sha256', $password . $osalt, true) == $ohash) {
  141. return true;
  142. }
  143. else {
  144. return false;
  145. }
  146. }
  147. function doveadm_authenticate($hash, $algorithm, $password) {
  148. $descr = array(0 => array('pipe', 'r'), 1 => array('pipe', 'w'), 2 => array('pipe', 'w'));
  149. $pipes = array();
  150. $process = proc_open("/usr/bin/doveadm pw -s ".$algorithm." -t '".$hash."'", $descr, $pipes);
  151. if (is_resource($process)) {
  152. fputs($pipes[0], $password);
  153. fclose($pipes[0]);
  154. while ($f = fgets($pipes[1])) {
  155. if (preg_match('/(verified)/', $f)) {
  156. proc_close($process);
  157. return true;
  158. }
  159. return false;
  160. }
  161. fclose($pipes[1]);
  162. while ($f = fgets($pipes[2])) {
  163. proc_close($process);
  164. return false;
  165. }
  166. fclose($pipes[2]);
  167. proc_close($process);
  168. }
  169. return false;
  170. }
  171. function check_login($user, $pass) {
  172. global $pdo;
  173. if (!filter_var($user, FILTER_VALIDATE_EMAIL) && !ctype_alnum(str_replace(array('_', '.', '-'), '', $user))) {
  174. return false;
  175. }
  176. $user = strtolower(trim($user));
  177. $stmt = $pdo->prepare("SELECT `password` FROM `admin`
  178. WHERE `superadmin` = '1'
  179. AND `username` = :user");
  180. $stmt->execute(array(':user' => $user));
  181. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  182. foreach ($rows as $row) {
  183. if (verify_ssha256($row['password'], $pass)) {
  184. if (get_tfa($user)['name'] != "none") {
  185. $_SESSION['pending_mailcow_cc_username'] = $user;
  186. $_SESSION['pending_mailcow_cc_role'] = "admin";
  187. $_SESSION['pending_tfa_method'] = get_tfa($user)['name'];
  188. unset($_SESSION['ldelay']);
  189. return "pending";
  190. }
  191. else {
  192. unset($_SESSION['ldelay']);
  193. return "admin";
  194. }
  195. }
  196. }
  197. $stmt = $pdo->prepare("SELECT `password` FROM `admin`
  198. WHERE `superadmin` = '0'
  199. AND `active`='1'
  200. AND `username` = :user");
  201. $stmt->execute(array(':user' => $user));
  202. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  203. foreach ($rows as $row) {
  204. if (verify_ssha256($row['password'], $pass) !== false) {
  205. if (get_tfa($user)['name'] != "none") {
  206. $_SESSION['pending_mailcow_cc_username'] = $user;
  207. $_SESSION['pending_mailcow_cc_role'] = "domainadmin";
  208. $_SESSION['pending_tfa_method'] = get_tfa($user)['name'];
  209. unset($_SESSION['ldelay']);
  210. return "pending";
  211. }
  212. else {
  213. unset($_SESSION['ldelay']);
  214. $stmt = $pdo->prepare("UPDATE `tfa` SET `active`='1' WHERE `username` = :user");
  215. $stmt->execute(array(':user' => $user));
  216. return "domainadmin";
  217. }
  218. }
  219. }
  220. $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
  221. WHERE `kind` NOT REGEXP 'location|thing|group'
  222. AND `active`='1'
  223. AND `username` = :user");
  224. $stmt->execute(array(':user' => $user));
  225. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  226. foreach ($rows as $row) {
  227. if (verify_ssha256($row['password'], $pass) !== false) {
  228. unset($_SESSION['ldelay']);
  229. return "user";
  230. }
  231. }
  232. if (!isset($_SESSION['ldelay'])) {
  233. $_SESSION['ldelay'] = "0";
  234. error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
  235. }
  236. elseif (!isset($_SESSION['mailcow_cc_username'])) {
  237. $_SESSION['ldelay'] = $_SESSION['ldelay']+0.5;
  238. error_log("mailcow UI: Invalid password for " . $user . " by " . $_SERVER['REMOTE_ADDR']);
  239. }
  240. sleep($_SESSION['ldelay']);
  241. }
  242. function formatBytes($size, $precision = 2) {
  243. if(!is_numeric($size)) {
  244. return "0";
  245. }
  246. $base = log($size, 1024);
  247. $suffixes = array(' Byte', ' KiB', ' MiB', ' GiB', ' TiB');
  248. if ($size == "0") {
  249. return "0";
  250. }
  251. return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)];
  252. }
  253. function edit_admin_account($postarray) {
  254. global $lang;
  255. global $pdo;
  256. if ($_SESSION['mailcow_cc_role'] != "admin") {
  257. $_SESSION['return'] = array(
  258. 'type' => 'danger',
  259. 'msg' => sprintf($lang['danger']['access_denied'])
  260. );
  261. return false;
  262. }
  263. $username_now = $_SESSION['mailcow_cc_username'];
  264. $username = $postarray['admin_user'];
  265. $password = $postarray['admin_pass'];
  266. $password2 = $postarray['admin_pass2'];
  267. if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) {
  268. $_SESSION['return'] = array(
  269. 'type' => 'danger',
  270. 'msg' => sprintf($lang['danger']['username_invalid'])
  271. );
  272. return false;
  273. }
  274. if (!empty($password) && !empty($password2)) {
  275. if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
  276. $_SESSION['return'] = array(
  277. 'type' => 'danger',
  278. 'msg' => sprintf($lang['danger']['password_complexity'])
  279. );
  280. return false;
  281. }
  282. if ($password != $password2) {
  283. $_SESSION['return'] = array(
  284. 'type' => 'danger',
  285. 'msg' => sprintf($lang['danger']['password_mismatch'])
  286. );
  287. return false;
  288. }
  289. $password_hashed = hash_password($password);
  290. try {
  291. $stmt = $pdo->prepare("UPDATE `admin` SET
  292. `password` = :password_hashed,
  293. `username` = :username1
  294. WHERE `username` = :username2");
  295. $stmt->execute(array(
  296. ':password_hashed' => $password_hashed,
  297. ':username1' => $username,
  298. ':username2' => $username_now
  299. ));
  300. }
  301. catch (PDOException $e) {
  302. $_SESSION['return'] = array(
  303. 'type' => 'danger',
  304. 'msg' => 'MySQL: '.$e
  305. );
  306. return false;
  307. }
  308. }
  309. else {
  310. try {
  311. $stmt = $pdo->prepare("UPDATE `admin` SET
  312. `username` = :username1
  313. WHERE `username` = :username2");
  314. $stmt->execute(array(
  315. ':username1' => $username,
  316. ':username2' => $username_now
  317. ));
  318. }
  319. catch (PDOException $e) {
  320. $_SESSION['return'] = array(
  321. 'type' => 'danger',
  322. 'msg' => 'MySQL: '.$e
  323. );
  324. return false;
  325. }
  326. }
  327. try {
  328. $stmt = $pdo->prepare("UPDATE `domain_admins` SET `domain` = 'ALL', `username` = :username1 WHERE `username` = :username2");
  329. $stmt->execute(array(':username1' => $username, ':username2' => $username_now));
  330. $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username1 WHERE `username` = :username2");
  331. $stmt->execute(array(':username1' => $username, ':username2' => $username_now));
  332. }
  333. catch (PDOException $e) {
  334. $_SESSION['return'] = array(
  335. 'type' => 'danger',
  336. 'msg' => 'MySQL: '.$e
  337. );
  338. return false;
  339. }
  340. $_SESSION['mailcow_cc_username'] = $username;
  341. $_SESSION['return'] = array(
  342. 'type' => 'success',
  343. 'msg' => sprintf($lang['success']['admin_modified'])
  344. );
  345. }
  346. function edit_user_account($postarray) {
  347. global $lang;
  348. global $pdo;
  349. if (isset($postarray['username']) && filter_var($postarray['username'], FILTER_VALIDATE_EMAIL)) {
  350. if (!hasMailboxObjectAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $postarray['username'])) {
  351. $_SESSION['return'] = array(
  352. 'type' => 'danger',
  353. 'msg' => sprintf($lang['danger']['access_denied'])
  354. );
  355. return false;
  356. }
  357. else {
  358. $username = $postarray['username'];
  359. }
  360. }
  361. else {
  362. $username = $_SESSION['mailcow_cc_username'];
  363. }
  364. $password_old = $postarray['user_old_pass'];
  365. if (isset($postarray['user_new_pass']) && isset($postarray['user_new_pass2'])) {
  366. $password_new = $postarray['user_new_pass'];
  367. $password_new2 = $postarray['user_new_pass2'];
  368. }
  369. $stmt = $pdo->prepare("SELECT `password` FROM `mailbox`
  370. WHERE `kind` NOT REGEXP 'location|thing|group'
  371. AND `username` = :user");
  372. $stmt->execute(array(':user' => $username));
  373. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  374. if (!verify_ssha256($row['password'], $password_old)) {
  375. $_SESSION['return'] = array(
  376. 'type' => 'danger',
  377. 'msg' => sprintf($lang['danger']['access_denied'])
  378. );
  379. return false;
  380. }
  381. if (isset($password_new) && isset($password_new2)) {
  382. if (!empty($password_new2) && !empty($password_new)) {
  383. if ($password_new2 != $password_new) {
  384. $_SESSION['return'] = array(
  385. 'type' => 'danger',
  386. 'msg' => sprintf($lang['danger']['password_mismatch'])
  387. );
  388. return false;
  389. }
  390. if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) {
  391. $_SESSION['return'] = array(
  392. 'type' => 'danger',
  393. 'msg' => sprintf($lang['danger']['password_complexity'])
  394. );
  395. return false;
  396. }
  397. $password_hashed = hash_password($password_new);
  398. try {
  399. $stmt = $pdo->prepare("UPDATE `mailbox` SET `password` = :password_hashed WHERE `username` = :username");
  400. $stmt->execute(array(
  401. ':password_hashed' => $password_hashed,
  402. ':username' => $username
  403. ));
  404. }
  405. catch (PDOException $e) {
  406. $_SESSION['return'] = array(
  407. 'type' => 'danger',
  408. 'msg' => 'MySQL: '.$e
  409. );
  410. return false;
  411. }
  412. }
  413. }
  414. $_SESSION['return'] = array(
  415. 'type' => 'success',
  416. 'msg' => sprintf($lang['success']['mailbox_modified'], htmlspecialchars($username))
  417. );
  418. }
  419. function user_get_alias_details($username) {
  420. global $lang;
  421. global $pdo;
  422. if ($_SESSION['mailcow_cc_role'] == "user") {
  423. $username = $_SESSION['mailcow_cc_username'];
  424. }
  425. if (!filter_var($username, FILTER_VALIDATE_EMAIL)) {
  426. return false;
  427. }
  428. try {
  429. $data['address'] = $username;
  430. $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') AS `aliases` FROM `alias`
  431. WHERE `goto` REGEXP :username_goto
  432. AND `address` NOT LIKE '@%'
  433. AND `address` != :username_address");
  434. $stmt->execute(array(':username_goto' => '(^|,)'.$username.'($|,)', ':username_address' => $username));
  435. $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
  436. while ($row = array_shift($run)) {
  437. $data['aliases'] = $row['aliases'];
  438. }
  439. $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(local_part, '@', alias_domain SEPARATOR ', '), '&#10008;') AS `ad_alias` FROM `mailbox`
  440. LEFT OUTER JOIN `alias_domain` on `target_domain` = `domain`
  441. WHERE `username` = :username ;");
  442. $stmt->execute(array(':username' => $username));
  443. $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
  444. while ($row = array_shift($run)) {
  445. $data['ad_alias'] = $row['ad_alias'];
  446. }
  447. $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '&#10008;') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` NOT LIKE '@%';");
  448. $stmt->execute(array(':username' => $username));
  449. $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
  450. while ($row = array_shift($run)) {
  451. $data['aliases_also_send_as'] = $row['send_as'];
  452. }
  453. $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`send_as` SEPARATOR ', '), '&#10008;') AS `send_as` FROM `sender_acl` WHERE `logged_in_as` = :username AND `send_as` LIKE '@%';");
  454. $stmt->execute(array(':username' => $username));
  455. $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
  456. while ($row = array_shift($run)) {
  457. $data['aliases_send_as_all'] = $row['send_as'];
  458. }
  459. $stmt = $pdo->prepare("SELECT IFNULL(GROUP_CONCAT(`address` SEPARATOR ', '), '&#10008;') as `address` FROM `alias` WHERE `goto` REGEXP :username AND `address` LIKE '@%';");
  460. $stmt->execute(array(':username' => '(^|,)'.$username.'($|,)'));
  461. $run = $stmt->fetchAll(PDO::FETCH_ASSOC);
  462. while ($row = array_shift($run)) {
  463. $data['is_catch_all'] = $row['address'];
  464. }
  465. return $data;
  466. }
  467. catch(PDOException $e) {
  468. $_SESSION['return'] = array(
  469. 'type' => 'danger',
  470. 'msg' => 'MySQL: '.$e
  471. );
  472. return false;
  473. }
  474. }
  475. function is_valid_domain_name($domain_name) {
  476. if (empty($domain_name)) {
  477. return false;
  478. }
  479. $domain_name = idn_to_ascii($domain_name);
  480. return (preg_match("/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i", $domain_name)
  481. && preg_match("/^.{1,253}$/", $domain_name)
  482. && preg_match("/^[^\.]{1,63}(\.[^\.]{1,63})*$/", $domain_name));
  483. }
  484. function add_domain_admin($postarray) {
  485. global $lang;
  486. global $pdo;
  487. $username = strtolower(trim($postarray['username']));
  488. $password = $postarray['password'];
  489. $password2 = $postarray['password2'];
  490. $domains = (array)$postarray['domains'];
  491. $active = intval($postarray['active']);
  492. if ($_SESSION['mailcow_cc_role'] != "admin") {
  493. $_SESSION['return'] = array(
  494. 'type' => 'danger',
  495. 'msg' => sprintf($lang['danger']['access_denied'])
  496. );
  497. return false;
  498. }
  499. if (empty($domains)) {
  500. $_SESSION['return'] = array(
  501. 'type' => 'danger',
  502. 'msg' => sprintf($lang['danger']['domain_invalid'])
  503. );
  504. return false;
  505. }
  506. if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username)) || empty ($username)) {
  507. $_SESSION['return'] = array(
  508. 'type' => 'danger',
  509. 'msg' => sprintf($lang['danger']['username_invalid'])
  510. );
  511. return false;
  512. }
  513. try {
  514. $stmt = $pdo->prepare("SELECT `username` FROM `mailbox`
  515. WHERE `username` = :username");
  516. $stmt->execute(array(':username' => $username));
  517. $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
  518. $stmt = $pdo->prepare("SELECT `username` FROM `admin`
  519. WHERE `username` = :username");
  520. $stmt->execute(array(':username' => $username));
  521. $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
  522. $stmt = $pdo->prepare("SELECT `username` FROM `domain_admins`
  523. WHERE `username` = :username");
  524. $stmt->execute(array(':username' => $username));
  525. $num_results[] = count($stmt->fetchAll(PDO::FETCH_ASSOC));
  526. }
  527. catch(PDOException $e) {
  528. $_SESSION['return'] = array(
  529. 'type' => 'danger',
  530. 'msg' => 'MySQL: '.$e
  531. );
  532. return false;
  533. }
  534. foreach ($num_results as $num_results_each) {
  535. if ($num_results_each != 0) {
  536. $_SESSION['return'] = array(
  537. 'type' => 'danger',
  538. 'msg' => sprintf($lang['danger']['object_exists'], htmlspecialchars($username))
  539. );
  540. return false;
  541. }
  542. }
  543. if (!empty($password) && !empty($password2)) {
  544. if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
  545. $_SESSION['return'] = array(
  546. 'type' => 'danger',
  547. 'msg' => sprintf($lang['danger']['password_complexity'])
  548. );
  549. return false;
  550. }
  551. if ($password != $password2) {
  552. $_SESSION['return'] = array(
  553. 'type' => 'danger',
  554. 'msg' => sprintf($lang['danger']['password_mismatch'])
  555. );
  556. return false;
  557. }
  558. $password_hashed = hash_password($password);
  559. foreach ($domains as $domain) {
  560. if (!is_valid_domain_name($domain)) {
  561. $_SESSION['return'] = array(
  562. 'type' => 'danger',
  563. 'msg' => sprintf($lang['danger']['domain_invalid'])
  564. );
  565. return false;
  566. }
  567. try {
  568. $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
  569. VALUES (:username, :domain, :created, :active)");
  570. $stmt->execute(array(
  571. ':username' => $username,
  572. ':domain' => $domain,
  573. ':created' => date('Y-m-d H:i:s'),
  574. ':active' => $active
  575. ));
  576. }
  577. catch (PDOException $e) {
  578. delete_domain_admin(array('username' => $username));
  579. $_SESSION['return'] = array(
  580. 'type' => 'danger',
  581. 'msg' => 'MySQL: '.$e
  582. );
  583. return false;
  584. }
  585. }
  586. try {
  587. $stmt = $pdo->prepare("INSERT INTO `admin` (`username`, `password`, `superadmin`, `active`)
  588. VALUES (:username, :password_hashed, '0', :active)");
  589. $stmt->execute(array(
  590. ':username' => $username,
  591. ':password_hashed' => $password_hashed,
  592. ':active' => $active
  593. ));
  594. }
  595. catch (PDOException $e) {
  596. $_SESSION['return'] = array(
  597. 'type' => 'danger',
  598. 'msg' => 'MySQL: '.$e
  599. );
  600. return false;
  601. }
  602. }
  603. else {
  604. $_SESSION['return'] = array(
  605. 'type' => 'danger',
  606. 'msg' => sprintf($lang['danger']['password_empty'])
  607. );
  608. return false;
  609. }
  610. $_SESSION['return'] = array(
  611. 'type' => 'success',
  612. 'msg' => sprintf($lang['success']['domain_admin_added'], htmlspecialchars($username))
  613. );
  614. }
  615. function delete_domain_admin($postarray) {
  616. global $pdo;
  617. global $lang;
  618. if ($_SESSION['mailcow_cc_role'] != "admin") {
  619. $_SESSION['return'] = array(
  620. 'type' => 'danger',
  621. 'msg' => sprintf($lang['danger']['access_denied'])
  622. );
  623. return false;
  624. }
  625. $usernames = (array)$postarray['username'];
  626. foreach ($usernames as $username) {
  627. if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username))) {
  628. $_SESSION['return'] = array(
  629. 'type' => 'danger',
  630. 'msg' => sprintf($lang['danger']['username_invalid'])
  631. );
  632. return false;
  633. }
  634. try {
  635. $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
  636. $stmt->execute(array(
  637. ':username' => $username,
  638. ));
  639. $stmt = $pdo->prepare("DELETE FROM `admin` WHERE `username` = :username");
  640. $stmt->execute(array(
  641. ':username' => $username,
  642. ));
  643. }
  644. catch (PDOException $e) {
  645. $_SESSION['return'] = array(
  646. 'type' => 'danger',
  647. 'msg' => 'MySQL: '.$e
  648. );
  649. return false;
  650. }
  651. }
  652. $_SESSION['return'] = array(
  653. 'type' => 'success',
  654. 'msg' => sprintf($lang['success']['domain_admin_removed'], htmlspecialchars(implode(', ', $usernames)))
  655. );
  656. }
  657. function get_domain_admins() {
  658. global $pdo;
  659. global $lang;
  660. $domainadmins = array();
  661. if ($_SESSION['mailcow_cc_role'] != "admin") {
  662. $_SESSION['return'] = array(
  663. 'type' => 'danger',
  664. 'msg' => sprintf($lang['danger']['access_denied'])
  665. );
  666. return false;
  667. }
  668. try {
  669. $stmt = $pdo->query("SELECT DISTINCT
  670. `username`
  671. FROM `domain_admins`
  672. WHERE `username` IN (
  673. SELECT `username` FROM `admin`
  674. WHERE `superadmin`!='1'
  675. )");
  676. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  677. while ($row = array_shift($rows)) {
  678. $domainadmins[] = $row['username'];
  679. }
  680. }
  681. catch(PDOException $e) {
  682. $_SESSION['return'] = array(
  683. 'type' => 'danger',
  684. 'msg' => 'MySQL: '.$e
  685. );
  686. }
  687. return $domainadmins;
  688. }
  689. function get_domain_admin_details($domain_admin) {
  690. global $pdo;
  691. global $lang;
  692. $domainadmindata = array();
  693. if (isset($domain_admin) && $_SESSION['mailcow_cc_role'] != "admin") {
  694. return false;
  695. }
  696. if (!isset($domain_admin) && $_SESSION['mailcow_cc_role'] != "domainadmin") {
  697. return false;
  698. }
  699. (!isset($domain_admin)) ? $domain_admin = $_SESSION['mailcow_cc_username'] : null;
  700. if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $domain_admin))) {
  701. return false;
  702. }
  703. try {
  704. $stmt = $pdo->prepare("SELECT
  705. `tfa`.`active` AS `tfa_active_int`,
  706. CASE `tfa`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `tfa_active`,
  707. `domain_admins`.`username`,
  708. `domain_admins`.`created`,
  709. `domain_admins`.`active` AS `active_int`,
  710. CASE `domain_admins`.`active` WHEN 1 THEN '".$lang['mailbox']['yes']."' ELSE '".$lang['mailbox']['no']."' END AS `active`
  711. FROM `domain_admins`
  712. LEFT OUTER JOIN `tfa` ON `tfa`.`username`=`domain_admins`.`username`
  713. WHERE `domain_admins`.`username`= :domain_admin");
  714. $stmt->execute(array(
  715. ':domain_admin' => $domain_admin
  716. ));
  717. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  718. if (empty($row)) {
  719. return false;
  720. }
  721. $domainadmindata['username'] = $row['username'];
  722. $domainadmindata['tfa_active'] = $row['tfa_active'];
  723. $domainadmindata['active'] = $row['active'];
  724. $domainadmindata['tfa_active_int'] = $row['tfa_active_int'];
  725. $domainadmindata['active_int'] = $row['active_int'];
  726. $domainadmindata['modified'] = $row['created'];
  727. // GET SELECTED
  728. $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
  729. WHERE `domain` IN (
  730. SELECT `domain` FROM `domain_admins`
  731. WHERE `username`= :domain_admin)");
  732. $stmt->execute(array(':domain_admin' => $domain_admin));
  733. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  734. while($row = array_shift($rows)) {
  735. $domainadmindata['selected_domains'][] = $row['domain'];
  736. }
  737. // GET UNSELECTED
  738. $stmt = $pdo->prepare("SELECT `domain` FROM `domain`
  739. WHERE `domain` NOT IN (
  740. SELECT `domain` FROM `domain_admins`
  741. WHERE `username`= :domain_admin)");
  742. $stmt->execute(array(':domain_admin' => $domain_admin));
  743. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  744. while($row = array_shift($rows)) {
  745. $domainadmindata['unselected_domains'][] = $row['domain'];
  746. }
  747. if (!isset($domainadmindata['unselected_domains'])) {
  748. $domainadmindata['unselected_domains'] = "";
  749. }
  750. }
  751. catch(PDOException $e) {
  752. $_SESSION['return'] = array(
  753. 'type' => 'danger',
  754. 'msg' => 'MySQL: '.$e
  755. );
  756. }
  757. return $domainadmindata;
  758. }
  759. function set_tfa($postarray) {
  760. global $lang;
  761. global $pdo;
  762. global $yubi;
  763. global $u2f;
  764. global $tfa;
  765. if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
  766. $_SESSION['mailcow_cc_role'] != "admin") {
  767. $_SESSION['return'] = array(
  768. 'type' => 'danger',
  769. 'msg' => sprintf($lang['danger']['access_denied'])
  770. );
  771. return false;
  772. }
  773. $username = $_SESSION['mailcow_cc_username'];
  774. $stmt = $pdo->prepare("SELECT `password` FROM `admin`
  775. WHERE `username` = :user");
  776. $stmt->execute(array(':user' => $username));
  777. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  778. if (!verify_ssha256($row['password'], $postarray["confirm_password"])) {
  779. $_SESSION['return'] = array(
  780. 'type' => 'danger',
  781. 'msg' => sprintf($lang['danger']['access_denied'])
  782. );
  783. return false;
  784. }
  785. switch ($postarray["tfa_method"]) {
  786. case "yubi_otp":
  787. $key_id = (!isset($postarray["key_id"])) ? 'unidentified' : $postarray["key_id"];
  788. $yubico_id = $postarray['yubico_id'];
  789. $yubico_key = $postarray['yubico_key'];
  790. $yubi = new Auth_Yubico($yubico_id, $yubico_key);
  791. if (!$yubi) {
  792. $_SESSION['return'] = array(
  793. 'type' => 'danger',
  794. 'msg' => sprintf($lang['danger']['access_denied'])
  795. );
  796. return false;
  797. }
  798. if (!ctype_alnum($postarray["otp_token"]) || strlen($postarray["otp_token"]) != 44) {
  799. $_SESSION['return'] = array(
  800. 'type' => 'danger',
  801. 'msg' => sprintf($lang['danger']['tfa_token_invalid'])
  802. );
  803. return false;
  804. }
  805. $yauth = $yubi->verify($postarray["otp_token"]);
  806. if (PEAR::isError($yauth)) {
  807. $_SESSION['return'] = array(
  808. 'type' => 'danger',
  809. 'msg' => 'Yubico API: ' . $yauth->getMessage()
  810. );
  811. return false;
  812. }
  813. try {
  814. // We could also do a modhex translation here
  815. $yubico_modhex_id = substr($postarray["otp_token"], 0, 12);
  816. $stmt = $pdo->prepare("DELETE FROM `tfa`
  817. WHERE `username` = :username
  818. AND (`authmech` != 'yubi_otp')
  819. OR (`authmech` = 'yubi_otp' AND `secret` LIKE :modhex)");
  820. $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
  821. $stmt = $pdo->prepare("INSERT INTO `tfa` (`key_id`, `username`, `authmech`, `active`, `secret`) VALUES
  822. (:key_id, :username, 'yubi_otp', '1', :secret)");
  823. $stmt->execute(array(':key_id' => $key_id, ':username' => $username, ':secret' => $yubico_id . ':' . $yubico_key . ':' . $yubico_modhex_id));
  824. }
  825. catch (PDOException $e) {
  826. $_SESSION['return'] = array(
  827. 'type' => 'danger',
  828. 'msg' => 'MySQL: '.$e
  829. );
  830. return false;
  831. }
  832. $_SESSION['return'] = array(
  833. 'type' => 'success',
  834. 'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars($username))
  835. );
  836. break;
  837. case "u2f":
  838. $key_id = (!isset($postarray["key_id"])) ? 'unidentified' : $postarray["key_id"];
  839. try {
  840. $reg = $u2f->doRegister(json_decode($_SESSION['regReq']), json_decode($postarray['token']));
  841. $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `authmech` != 'u2f'");
  842. $stmt->execute(array(':username' => $username));
  843. $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `keyHandle`, `publicKey`, `certificate`, `counter`, `active`) VALUES (?, ?, 'u2f', ?, ?, ?, ?, '1')");
  844. $stmt->execute(array($username, $key_id, $reg->keyHandle, $reg->publicKey, $reg->certificate, $reg->counter));
  845. $_SESSION['return'] = array(
  846. 'type' => 'success',
  847. 'msg' => sprintf($lang['success']['object_modified'], $username)
  848. );
  849. $_SESSION['regReq'] = null;
  850. }
  851. catch (Exception $e) {
  852. $_SESSION['return'] = array(
  853. 'type' => 'danger',
  854. 'msg' => "U2F: " . $e->getMessage()
  855. );
  856. $_SESSION['regReq'] = null;
  857. return false;
  858. }
  859. break;
  860. case "totp":
  861. $key_id = (!isset($postarray["key_id"])) ? 'unidentified' : $postarray["key_id"];
  862. if ($tfa->verifyCode($_POST['totp_secret'], $_POST['totp_confirm_token']) === true) {
  863. try {
  864. $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
  865. $stmt->execute(array(':username' => $username));
  866. $stmt = $pdo->prepare("INSERT INTO `tfa` (`username`, `key_id`, `authmech`, `secret`, `active`) VALUES (?, ?, 'totp', ?, '1')");
  867. $stmt->execute(array($username, $key_id, $_POST['totp_secret']));
  868. }
  869. catch (PDOException $e) {
  870. $_SESSION['return'] = array(
  871. 'type' => 'danger',
  872. 'msg' => 'MySQL: '.$e
  873. );
  874. return false;
  875. }
  876. $_SESSION['return'] = array(
  877. 'type' => 'success',
  878. 'msg' => sprintf($lang['success']['object_modified'], $username)
  879. );
  880. }
  881. else {
  882. $_SESSION['return'] = array(
  883. 'type' => 'danger',
  884. 'msg' => 'TOTP verification failed'
  885. );
  886. }
  887. break;
  888. case "none":
  889. try {
  890. $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username");
  891. $stmt->execute(array(':username' => $username));
  892. }
  893. catch (PDOException $e) {
  894. $_SESSION['return'] = array(
  895. 'type' => 'danger',
  896. 'msg' => 'MySQL: '.$e
  897. );
  898. return false;
  899. }
  900. $_SESSION['return'] = array(
  901. 'type' => 'success',
  902. 'msg' => sprintf($lang['success']['object_modified'], htmlspecialchars($username))
  903. );
  904. break;
  905. }
  906. }
  907. function unset_tfa_key($postarray) {
  908. // Can only unset own keys
  909. // Needs at least one key left
  910. global $pdo;
  911. global $lang;
  912. $id = intval($postarray['unset_tfa_key']);
  913. if ($_SESSION['mailcow_cc_role'] != "domainadmin" &&
  914. $_SESSION['mailcow_cc_role'] != "admin") {
  915. $_SESSION['return'] = array(
  916. 'type' => 'danger',
  917. 'msg' => sprintf($lang['danger']['access_denied'])
  918. );
  919. return false;
  920. }
  921. $username = $_SESSION['mailcow_cc_username'];
  922. try {
  923. if (!is_numeric($id)) {
  924. $_SESSION['return'] = array(
  925. 'type' => 'danger',
  926. 'msg' => sprintf($lang['danger']['access_denied'])
  927. );
  928. return false;
  929. }
  930. $stmt = $pdo->prepare("SELECT COUNT(*) AS `keys` FROM `tfa`
  931. WHERE `username` = :username AND `active` = '1'");
  932. $stmt->execute(array(':username' => $username));
  933. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  934. if ($row['keys'] == "1") {
  935. $_SESSION['return'] = array(
  936. 'type' => 'danger',
  937. 'msg' => sprintf($lang['danger']['last_key'])
  938. );
  939. return false;
  940. }
  941. $stmt = $pdo->prepare("DELETE FROM `tfa` WHERE `username` = :username AND `id` = :id");
  942. $stmt->execute(array(':username' => $username, ':id' => $id));
  943. $_SESSION['return'] = array(
  944. 'type' => 'success',
  945. 'msg' => sprintf($lang['success']['object_modified'], $username)
  946. );
  947. }
  948. catch (PDOException $e) {
  949. $_SESSION['return'] = array(
  950. 'type' => 'danger',
  951. 'msg' => 'MySQL: '.$e
  952. );
  953. return false;
  954. }
  955. }
  956. function get_tfa($username = null) {
  957. global $pdo;
  958. if (isset($_SESSION['mailcow_cc_username'])) {
  959. $username = $_SESSION['mailcow_cc_username'];
  960. }
  961. elseif (empty($username)) {
  962. return false;
  963. }
  964. $stmt = $pdo->prepare("SELECT * FROM `tfa`
  965. WHERE `username` = :username AND `active` = '1'");
  966. $stmt->execute(array(':username' => $username));
  967. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  968. switch ($row["authmech"]) {
  969. case "yubi_otp":
  970. $data['name'] = "yubi_otp";
  971. $data['pretty'] = "Yubico OTP";
  972. $stmt = $pdo->prepare("SELECT `id`, `key_id`, RIGHT(`secret`, 12) AS 'modhex' FROM `tfa` WHERE `authmech` = 'yubi_otp' AND `username` = :username");
  973. $stmt->execute(array(
  974. ':username' => $username,
  975. ));
  976. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  977. while($row = array_shift($rows)) {
  978. $data['additional'][] = $row;
  979. }
  980. return $data;
  981. break;
  982. case "u2f":
  983. $data['name'] = "u2f";
  984. $data['pretty'] = "Fido U2F";
  985. $stmt = $pdo->prepare("SELECT `id`, `key_id` FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = :username");
  986. $stmt->execute(array(
  987. ':username' => $username,
  988. ));
  989. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  990. while($row = array_shift($rows)) {
  991. $data['additional'][] = $row;
  992. }
  993. return $data;
  994. break;
  995. case "hotp":
  996. $data['name'] = "hotp";
  997. $data['pretty'] = "HMAC-based OTP";
  998. return $data;
  999. break;
  1000. case "totp":
  1001. $data['name'] = "totp";
  1002. $data['pretty'] = "Time-based OTP";
  1003. $stmt = $pdo->prepare("SELECT `id`, `key_id`, `secret` FROM `tfa` WHERE `authmech` = 'totp' AND `username` = :username");
  1004. $stmt->execute(array(
  1005. ':username' => $username,
  1006. ));
  1007. $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  1008. while($row = array_shift($rows)) {
  1009. $data['additional'][] = $row;
  1010. }
  1011. return $data;
  1012. break;
  1013. default:
  1014. $data['name'] = 'none';
  1015. $data['pretty'] = "-";
  1016. return $data;
  1017. break;
  1018. }
  1019. }
  1020. function verify_tfa_login($username, $token) {
  1021. global $pdo;
  1022. global $lang;
  1023. global $yubi;
  1024. global $u2f;
  1025. global $tfa;
  1026. $stmt = $pdo->prepare("SELECT `authmech` FROM `tfa`
  1027. WHERE `username` = :username AND `active` = '1'");
  1028. $stmt->execute(array(':username' => $username));
  1029. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  1030. switch ($row["authmech"]) {
  1031. case "yubi_otp":
  1032. if (!ctype_alnum($token) || strlen($token) != 44) {
  1033. return false;
  1034. }
  1035. $yubico_modhex_id = substr($token, 0, 12);
  1036. $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
  1037. WHERE `username` = :username
  1038. AND `authmech` = 'yubi_otp'
  1039. AND `active`='1'
  1040. AND `secret` LIKE :modhex");
  1041. $stmt->execute(array(':username' => $username, ':modhex' => '%' . $yubico_modhex_id));
  1042. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  1043. $yubico_auth = explode(':', $row['secret']);
  1044. $yubi = new Auth_Yubico($yubico_auth[0], $yubico_auth[1]);
  1045. $yauth = $yubi->verify($token);
  1046. if (PEAR::isError($yauth)) {
  1047. $_SESSION['return'] = array(
  1048. 'type' => 'danger',
  1049. 'msg' => 'Yubico Authentication error: ' . $yauth->getMessage()
  1050. );
  1051. return false;
  1052. }
  1053. else {
  1054. $_SESSION['tfa_id'] = $row['id'];
  1055. return true;
  1056. }
  1057. return false;
  1058. break;
  1059. case "u2f":
  1060. try {
  1061. $reg = $u2f->doAuthenticate(json_decode($_SESSION['authReq']), get_u2f_registrations($username), json_decode($token));
  1062. $stmt = $pdo->prepare("UPDATE `tfa` SET `counter` = ? WHERE `id` = ?");
  1063. $stmt->execute(array($reg->counter, $reg->id));
  1064. $_SESSION['tfa_id'] = $reg->id;
  1065. $_SESSION['authReq'] = null;
  1066. return true;
  1067. }
  1068. catch (Exception $e) {
  1069. $_SESSION['return'] = array(
  1070. 'type' => 'danger',
  1071. 'msg' => "U2F: " . $e->getMessage()
  1072. );
  1073. $_SESSION['regReq'] = null;
  1074. return false;
  1075. }
  1076. return false;
  1077. break;
  1078. case "hotp":
  1079. return false;
  1080. break;
  1081. case "totp":
  1082. try {
  1083. $stmt = $pdo->prepare("SELECT `id`, `secret` FROM `tfa`
  1084. WHERE `username` = :username
  1085. AND `authmech` = 'totp'
  1086. AND `active`='1'");
  1087. $stmt->execute(array(':username' => $username));
  1088. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  1089. if ($tfa->verifyCode($row['secret'], $_POST['token']) === true) {
  1090. $_SESSION['tfa_id'] = $row['id'];
  1091. return true;
  1092. }
  1093. return false;
  1094. }
  1095. catch (PDOException $e) {
  1096. $_SESSION['return'] = array(
  1097. 'type' => 'danger',
  1098. 'msg' => 'MySQL: '.$e
  1099. );
  1100. return false;
  1101. }
  1102. break;
  1103. default:
  1104. return false;
  1105. break;
  1106. }
  1107. return false;
  1108. }
  1109. function edit_domain_admin($postarray) {
  1110. global $lang;
  1111. global $pdo;
  1112. if ($_SESSION['mailcow_cc_role'] != "admin" && $_SESSION['mailcow_cc_role'] != "domainadmin") {
  1113. $_SESSION['return'] = array(
  1114. 'type' => 'danger',
  1115. 'msg' => sprintf($lang['danger']['access_denied'])
  1116. );
  1117. return false;
  1118. }
  1119. // Administrator
  1120. if ($_SESSION['mailcow_cc_role'] == "admin") {
  1121. if (!is_array($postarray['username'])) {
  1122. $usernames = array();
  1123. $usernames[] = $postarray['username'];
  1124. }
  1125. else {
  1126. $usernames = $postarray['username'];
  1127. }
  1128. foreach ($usernames as $username) {
  1129. $is_now = get_domain_admin_details($username);
  1130. $domains = (isset($postarray['domains'])) ? (array)$postarray['domains'] : null;
  1131. if (!empty($is_now)) {
  1132. $active = (isset($postarray['active'])) ? $postarray['active'] : $is_now['active_int'];
  1133. $domains = (!empty($domains)) ? $domains : $is_now['selected_domains'];
  1134. $username_new = (!empty($postarray['username_new'])) ? $postarray['username_new'] : $is_now['username'];
  1135. }
  1136. else {
  1137. $_SESSION['return'] = array(
  1138. 'type' => 'danger',
  1139. 'msg' => sprintf($lang['danger']['access_denied'])
  1140. );
  1141. return false;
  1142. }
  1143. $password = $postarray['password'];
  1144. $password2 = $postarray['password2'];
  1145. if (!empty($domains)) {
  1146. foreach ($domains as $domain) {
  1147. if (!is_valid_domain_name($domain)) {
  1148. $_SESSION['return'] = array(
  1149. 'type' => 'danger',
  1150. 'msg' => sprintf($lang['danger']['domain_invalid'])
  1151. );
  1152. return false;
  1153. }
  1154. }
  1155. }
  1156. if (!ctype_alnum(str_replace(array('_', '.', '-'), '', $username_new))) {
  1157. $_SESSION['return'] = array(
  1158. 'type' => 'danger',
  1159. 'msg' => sprintf($lang['danger']['username_invalid'])
  1160. );
  1161. return false;
  1162. }
  1163. if ($username_new != $username) {
  1164. if (!empty(get_domain_admin_details($username_new)['username'])) {
  1165. $_SESSION['return'] = array(
  1166. 'type' => 'danger',
  1167. 'msg' => sprintf($lang['danger']['username_invalid'])
  1168. );
  1169. return false;
  1170. }
  1171. }
  1172. try {
  1173. $stmt = $pdo->prepare("DELETE FROM `domain_admins` WHERE `username` = :username");
  1174. $stmt->execute(array(
  1175. ':username' => $username,
  1176. ));
  1177. }
  1178. catch (PDOException $e) {
  1179. $_SESSION['return'] = array(
  1180. 'type' => 'danger',
  1181. 'msg' => 'MySQL: '.$e
  1182. );
  1183. return false;
  1184. }
  1185. if (!empty($domains)) {
  1186. foreach ($domains as $domain) {
  1187. try {
  1188. $stmt = $pdo->prepare("INSERT INTO `domain_admins` (`username`, `domain`, `created`, `active`)
  1189. VALUES (:username_new, :domain, :created, :active)");
  1190. $stmt->execute(array(
  1191. ':username_new' => $username_new,
  1192. ':domain' => $domain,
  1193. ':created' => date('Y-m-d H:i:s'),
  1194. ':active' => $active
  1195. ));
  1196. }
  1197. catch (PDOException $e) {
  1198. $_SESSION['return'] = array(
  1199. 'type' => 'danger',
  1200. 'msg' => 'MySQL: '.$e
  1201. );
  1202. return false;
  1203. }
  1204. }
  1205. }
  1206. if (!empty($password) && !empty($password2)) {
  1207. if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password)) {
  1208. $_SESSION['return'] = array(
  1209. 'type' => 'danger',
  1210. 'msg' => sprintf($lang['danger']['password_complexity'])
  1211. );
  1212. return false;
  1213. }
  1214. if ($password != $password2) {
  1215. $_SESSION['return'] = array(
  1216. 'type' => 'danger',
  1217. 'msg' => sprintf($lang['danger']['password_mismatch'])
  1218. );
  1219. return false;
  1220. }
  1221. $password_hashed = hash_password($password);
  1222. try {
  1223. $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active, `password` = :password_hashed WHERE `username` = :username");
  1224. $stmt->execute(array(
  1225. ':password_hashed' => $password_hashed,
  1226. ':username_new' => $username_new,
  1227. ':username' => $username,
  1228. ':active' => $active
  1229. ));
  1230. if (isset($postarray['disable_tfa'])) {
  1231. $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
  1232. $stmt->execute(array(':username' => $username));
  1233. }
  1234. else {
  1235. $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
  1236. $stmt->execute(array(':username_new' => $username_new, ':username' => $username));
  1237. }
  1238. }
  1239. catch (PDOException $e) {
  1240. $_SESSION['return'] = array(
  1241. 'type' => 'danger',
  1242. 'msg' => 'MySQL: '.$e
  1243. );
  1244. return false;
  1245. }
  1246. }
  1247. else {
  1248. try {
  1249. $stmt = $pdo->prepare("UPDATE `admin` SET `username` = :username_new, `active` = :active WHERE `username` = :username");
  1250. $stmt->execute(array(
  1251. ':username_new' => $username_new,
  1252. ':username' => $username,
  1253. ':active' => $active
  1254. ));
  1255. if (isset($postarray['disable_tfa'])) {
  1256. $stmt = $pdo->prepare("UPDATE `tfa` SET `active` = '0' WHERE `username` = :username");
  1257. $stmt->execute(array(':username' => $username));
  1258. }
  1259. else {
  1260. $stmt = $pdo->prepare("UPDATE `tfa` SET `username` = :username_new WHERE `username` = :username");
  1261. $stmt->execute(array(':username_new' => $username_new, ':username' => $username));
  1262. }
  1263. }
  1264. catch (PDOException $e) {
  1265. $_SESSION['return'] = array(
  1266. 'type' => 'danger',
  1267. 'msg' => 'MySQL: '.$e
  1268. );
  1269. return false;
  1270. }
  1271. }
  1272. }
  1273. $_SESSION['return'] = array(
  1274. 'type' => 'success',
  1275. 'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars(implode(', ', $usernames)))
  1276. );
  1277. }
  1278. // Domain administrator
  1279. // Can only edit itself
  1280. elseif ($_SESSION['mailcow_cc_role'] == "domainadmin") {
  1281. $username = $_SESSION['mailcow_cc_username'];
  1282. $password_old = $postarray['user_old_pass'];
  1283. $password_new = $postarray['user_new_pass'];
  1284. $password_new2 = $postarray['user_new_pass2'];
  1285. $stmt = $pdo->prepare("SELECT `password` FROM `admin`
  1286. WHERE `username` = :user");
  1287. $stmt->execute(array(':user' => $username));
  1288. $row = $stmt->fetch(PDO::FETCH_ASSOC);
  1289. if (!verify_ssha256($row['password'], $password_old)) {
  1290. $_SESSION['return'] = array(
  1291. 'type' => 'danger',
  1292. 'msg' => sprintf($lang['danger']['access_denied'])
  1293. );
  1294. return false;
  1295. }
  1296. if (!empty($password_new2) && !empty($password_new)) {
  1297. if ($password_new2 != $password_new) {
  1298. $_SESSION['return'] = array(
  1299. 'type' => 'danger',
  1300. 'msg' => sprintf($lang['danger']['password_mismatch'])
  1301. );
  1302. return false;
  1303. }
  1304. if (!preg_match('/' . $GLOBALS['PASSWD_REGEP'] . '/', $password_new)) {
  1305. $_SESSION['return'] = array(
  1306. 'type' => 'danger',
  1307. 'msg' => sprintf($lang['danger']['password_complexity'])
  1308. );
  1309. return false;
  1310. }
  1311. $password_hashed = hash_password($password_new);
  1312. try {
  1313. $stmt = $pdo->prepare("UPDATE `admin` SET `password` = :password_hashed WHERE `username` = :username");
  1314. $stmt->execute(array(
  1315. ':password_hashed' => $password_hashed,
  1316. ':username' => $username
  1317. ));
  1318. }
  1319. catch (PDOException $e) {
  1320. $_SESSION['return'] = array(
  1321. 'type' => 'danger',
  1322. 'msg' => 'MySQL: '.$e
  1323. );
  1324. return false;
  1325. }
  1326. }
  1327. $_SESSION['return'] = array(
  1328. 'type' => 'success',
  1329. 'msg' => sprintf($lang['success']['domain_admin_modified'], htmlspecialchars($username))
  1330. );
  1331. }
  1332. }
  1333. function get_admin_details() {
  1334. // No parameter to be given, only one admin should exist
  1335. global $pdo;
  1336. global $lang;
  1337. $data = array();
  1338. if ($_SESSION['mailcow_cc_role'] != 'admin') {
  1339. return false;
  1340. }
  1341. try {
  1342. $stmt = $pdo->prepare("SELECT `username`, `modified`, `created` FROM `admin` WHERE `superadmin`='1' AND active='1'");
  1343. $stmt->execute();
  1344. $data = $stmt->fetch(PDO::FETCH_ASSOC);
  1345. }
  1346. catch(PDOException $e) {
  1347. $_SESSION['return'] = array(
  1348. 'type' => 'danger',
  1349. 'msg' => 'MySQL: '.$e
  1350. );
  1351. }
  1352. return $data;
  1353. }
  1354. function get_u2f_registrations($username) {
  1355. global $pdo;
  1356. $sel = $pdo->prepare("SELECT * FROM `tfa` WHERE `authmech` = 'u2f' AND `username` = ? AND `active` = '1'");
  1357. $sel->execute(array($username));
  1358. return $sel->fetchAll(PDO::FETCH_OBJ);
  1359. }
  1360. function get_logs($container, $lines = 100) {
  1361. global $lang;
  1362. global $redis;
  1363. if ($_SESSION['mailcow_cc_role'] != "admin") {
  1364. return false;
  1365. }
  1366. $lines = intval($lines);
  1367. if ($container == "dovecot-mailcow") {
  1368. if ($data = $redis->lRange('DOVECOT_MAILLOG', 0, $lines)) {
  1369. foreach ($data as $json_line) {
  1370. $data_array[] = json_decode($json_line, true);
  1371. }
  1372. return $data_array;
  1373. }
  1374. }
  1375. if ($container == "postfix-mailcow") {
  1376. if ($data = $redis->lRange('POSTFIX_MAILLOG', 0, $lines)) {
  1377. foreach ($data as $json_line) {
  1378. $data_array[] = json_decode($json_line, true);
  1379. }
  1380. return $data_array;
  1381. }
  1382. }
  1383. if ($container == "sogo-mailcow") {
  1384. if ($data = $redis->lRange('SOGO_LOG', 0, $lines)) {
  1385. foreach ($data as $json_line) {
  1386. $data_array[] = json_decode($json_line, true);
  1387. }
  1388. return $data_array;
  1389. }
  1390. }
  1391. if ($container == "fail2ban-mailcow") {
  1392. if ($data = $redis->lRange('F2B_LOG', 0, $lines)) {
  1393. foreach ($data as $json_line) {
  1394. $data_array[] = json_decode($json_line, true);
  1395. }
  1396. return $data_array;
  1397. }
  1398. }
  1399. if ($container == "rspamd-history") {
  1400. $curl = curl_init();
  1401. curl_setopt($curl, CURLOPT_URL,"http://rspamd-mailcow:11334/history");
  1402. curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
  1403. $history = curl_exec($curl);
  1404. if (!curl_errno($ch)) {
  1405. $data_array = json_decode($history, true);
  1406. curl_close($curl);
  1407. return $data_array['rows'];
  1408. }
  1409. curl_close($curl);
  1410. return false;
  1411. }
  1412. return false;
  1413. }
  1414. ?>