functions.dkim.inc.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. <?php
  2. function dkim($_action, $_data = null) {
  3. global $redis;
  4. global $lang;
  5. switch ($_action) {
  6. case 'add':
  7. if ($_SESSION['mailcow_cc_role'] != "admin") {
  8. $_SESSION['return'] = array(
  9. 'type' => 'danger',
  10. 'msg' => sprintf($lang['danger']['access_denied'])
  11. );
  12. return false;
  13. }
  14. $key_length = intval($_data['key_size']);
  15. $dkim_selector = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : 'dkim';
  16. $domain = $_data['domain'];
  17. if (!is_valid_domain_name($domain) || !is_numeric($key_length)) {
  18. $_SESSION['return'] = array(
  19. 'type' => 'danger',
  20. 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
  21. );
  22. return false;
  23. }
  24. if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
  25. $_SESSION['return'] = array(
  26. 'type' => 'danger',
  27. 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
  28. );
  29. return false;
  30. }
  31. if (!ctype_alnum($dkim_selector)) {
  32. $_SESSION['return'] = array(
  33. 'type' => 'danger',
  34. 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
  35. );
  36. return false;
  37. }
  38. $config = array(
  39. "digest_alg" => "sha256",
  40. "private_key_bits" => $key_length,
  41. "private_key_type" => OPENSSL_KEYTYPE_RSA,
  42. );
  43. if ($keypair_ressource = openssl_pkey_new($config)) {
  44. $key_details = openssl_pkey_get_details($keypair_ressource);
  45. $pubKey = implode(array_slice(
  46. array_filter(
  47. explode(PHP_EOL, $key_details['key'])
  48. ), 1, -1)
  49. );
  50. // Save public key and selector to redis
  51. try {
  52. $redis->hSet('DKIM_PUB_KEYS', $domain, $pubKey);
  53. $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
  54. }
  55. catch (RedisException $e) {
  56. $_SESSION['return'] = array(
  57. 'type' => 'danger',
  58. 'msg' => 'Redis: '.$e
  59. );
  60. return false;
  61. }
  62. // Export private key and save private key to redis
  63. openssl_pkey_export($keypair_ressource, $privKey);
  64. if (isset($privKey) && !empty($privKey)) {
  65. try {
  66. $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, trim($privKey));
  67. }
  68. catch (RedisException $e) {
  69. $_SESSION['return'] = array(
  70. 'type' => 'danger',
  71. 'msg' => 'Redis: '.$e
  72. );
  73. return false;
  74. }
  75. }
  76. $_SESSION['return'] = array(
  77. 'type' => 'success',
  78. 'msg' => sprintf($lang['success']['dkim_added'])
  79. );
  80. return true;
  81. }
  82. else {
  83. $_SESSION['return'] = array(
  84. 'type' => 'danger',
  85. 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
  86. );
  87. return false;
  88. }
  89. break;
  90. case 'import':
  91. if ($_SESSION['mailcow_cc_role'] != "admin") {
  92. $_SESSION['return'] = array(
  93. 'type' => 'danger',
  94. 'msg' => sprintf($lang['danger']['access_denied'])
  95. );
  96. return false;
  97. }
  98. $private_key_input = trim($_data['private_key_file']);
  99. $private_key_normalized = preg_replace('~\r\n?~', "\n", $private_key_input);
  100. $private_key = openssl_pkey_get_private($private_key_normalized);
  101. if ($ssl_error = openssl_error_string()) {
  102. $_SESSION['return'] = array(
  103. 'type' => 'danger',
  104. 'msg' => 'Private key error: ' . $ssl_error
  105. );
  106. return false;
  107. }
  108. // Explode by nl
  109. $pem_public_key_array = explode(PHP_EOL, trim(openssl_pkey_get_details($private_key)['key']));
  110. // Remove first and last line/item
  111. array_shift($pem_public_key_array);
  112. array_pop($pem_public_key_array);
  113. // Implode as single string
  114. $pem_public_key = implode('', $pem_public_key_array);
  115. $dkim_selector = (isset($_data['dkim_selector'])) ? $_data['dkim_selector'] : 'dkim';
  116. $domain = $_data['domain'];
  117. if (!is_valid_domain_name($domain)) {
  118. $_SESSION['return'] = array(
  119. 'type' => 'danger',
  120. 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
  121. );
  122. return false;
  123. }
  124. if ($redis->hGet('DKIM_PUB_KEYS', $domain)) {
  125. $_SESSION['return'] = array(
  126. 'type' => 'danger',
  127. 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
  128. );
  129. return false;
  130. }
  131. if (!ctype_alnum($dkim_selector)) {
  132. $_SESSION['return'] = array(
  133. 'type' => 'danger',
  134. 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
  135. );
  136. return false;
  137. }
  138. try {
  139. $redis->hSet('DKIM_PUB_KEYS', $domain, $pem_public_key);
  140. $redis->hSet('DKIM_SELECTORS', $domain, $dkim_selector);
  141. $redis->hSet('DKIM_PRIV_KEYS', $dkim_selector . '.' . $domain, $private_key_normalized);
  142. }
  143. catch (RedisException $e) {
  144. $_SESSION['return'] = array(
  145. 'type' => 'danger',
  146. 'msg' => 'Redis: '.$e
  147. );
  148. return false;
  149. }
  150. unset($private_key_normalized);
  151. unset($private_key);
  152. unset($private_key_input);
  153. try {
  154. }
  155. catch (RedisException $e) {
  156. $_SESSION['return'] = array(
  157. 'type' => 'danger',
  158. 'msg' => 'Redis: '.$e
  159. );
  160. return false;
  161. }
  162. $_SESSION['return'] = array(
  163. 'type' => 'success',
  164. 'msg' => sprintf($lang['success']['dkim_added'])
  165. );
  166. return true;
  167. break;
  168. case 'details':
  169. if (!hasDomainAccess($_SESSION['mailcow_cc_username'], $_SESSION['mailcow_cc_role'], $_data)) {
  170. return false;
  171. }
  172. $dkimdata = array();
  173. if ($redis_dkim_key_data = $redis->hGet('DKIM_PUB_KEYS', $_data)) {
  174. $dkimdata['pubkey'] = $redis_dkim_key_data;
  175. if (strlen($dkimdata['pubkey']) < 391) {
  176. $dkimdata['length'] = "1024";
  177. }
  178. elseif (strlen($dkimdata['pubkey']) < 736) {
  179. $dkimdata['length'] = "2048";
  180. }
  181. elseif (strlen($dkimdata['pubkey']) < 1416) {
  182. $dkimdata['length'] = "4096";
  183. }
  184. else {
  185. $dkimdata['length'] = ">= 8192";
  186. }
  187. $dkimdata['dkim_txt'] = 'v=DKIM1;k=rsa;t=s;s=email;p=' . $redis_dkim_key_data;
  188. $dkimdata['dkim_selector'] = $redis->hGet('DKIM_SELECTORS', $_data);
  189. $dkimdata['privkey'] = $redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . $_data);
  190. if ($GLOBALS['SHOW_DKIM_PRIV_KEYS'] === true) {
  191. $dkimdata['privkey'] = base64_encode($redis->hGet('DKIM_PRIV_KEYS', $dkimdata['dkim_selector'] . '.' . $_data));
  192. }
  193. else {
  194. $dkimdata['privkey'] = base64_encode('Please set $SHOW_DKIM_PRIV_KEYS to true to show DKIM private keys.');
  195. }
  196. }
  197. return $dkimdata;
  198. break;
  199. case 'blind':
  200. if ($_SESSION['mailcow_cc_role'] != "admin") {
  201. return false;
  202. }
  203. $blinddkim = array();
  204. foreach ($redis->hKeys('DKIM_PUB_KEYS') as $redis_dkim_domain) {
  205. $blinddkim[] = $redis_dkim_domain;
  206. }
  207. return array_diff($blinddkim, array_merge(mailbox('get', 'domains'), mailbox('get', 'alias_domains')));
  208. break;
  209. case 'delete':
  210. $domains = (array)$_data['domains'];
  211. if ($_SESSION['mailcow_cc_role'] != "admin") {
  212. $_SESSION['return'] = array(
  213. 'type' => 'danger',
  214. 'msg' => sprintf($lang['danger']['access_denied'])
  215. );
  216. return false;
  217. }
  218. foreach ($domains as $domain) {
  219. if (!is_valid_domain_name($domain)) {
  220. $_SESSION['return'] = array(
  221. 'type' => 'danger',
  222. 'msg' => sprintf($lang['danger']['dkim_domain_or_sel_invalid'])
  223. );
  224. return false;
  225. }
  226. try {
  227. $selector = $redis->hGet('DKIM_SELECTORS', $domain);
  228. $redis->hDel('DKIM_PUB_KEYS', $domain);
  229. $redis->hDel('DKIM_PRIV_KEYS', $selector . '.' . $domain);
  230. $redis->hDel('DKIM_SELECTORS', $domain);
  231. }
  232. catch (RedisException $e) {
  233. $_SESSION['return'] = array(
  234. 'type' => 'danger',
  235. 'msg' => 'Redis: '.$e
  236. );
  237. return false;
  238. }
  239. }
  240. $_SESSION['return'] = array(
  241. 'type' => 'success',
  242. 'msg' => sprintf($lang['success']['dkim_removed'], htmlspecialchars(implode(', ', $domains)))
  243. );
  244. break;
  245. }
  246. }