1: <?php declare(strict_types=1);
2: /*
3: * Copyright (C) Apis Networks, Inc - All Rights Reserved.
4: *
5: * Unauthorized copying of this file, via any medium, is
6: * strictly prohibited without consent. Any dissemination of
7: * material herein is prohibited.
8: *
9: * For licensing inquiries email <licensing@apisnetworks.com>
10: *
11: * Written by Matt Saladna <matt@apisnetworks.com>, January 2021
12: */
13:
14: use Opcenter\Dns\Bulk;
15: use Opcenter\Dns\Record;
16: use Opcenter\Mail\Services\Rspamd;
17: use Opcenter\Mail\Services\Rspamd\Dkim;
18:
19: /**
20: * DKIM signing
21: *
22: * @package core
23: */
24: class Dkim_Module extends Module_Skeleton
25: {
26: protected $exportedFunctions = [
27: 'roll' => PRIVILEGE_SITE | PRIVILEGE_ADMIN,
28: 'has' => PRIVILEGE_SITE | PRIVILEGE_ADMIN,
29: 'key' => PRIVILEGE_SITE | PRIVILEGE_ADMIN,
30: 'expire' => PRIVILEGE_SITE | PRIVILEGE_ADMIN,
31: 'selector'=> PRIVILEGE_SITE | PRIVILEGE_ADMIN,
32: 'enable' => PRIVILEGE_SITE,
33: 'disable' => PRIVILEGE_SITE,
34:
35: ];
36:
37: /**
38: * Get DKIM key for site
39: *
40: * @return string|null
41: */
42: public function key(): ?string
43: {
44: if (!IS_CLI) {
45: return $this->query('dkim_key');
46: }
47:
48: if (!$this->has()) {
49: return null;
50: }
51:
52: return Dkim::instantiateContexted($this->getAuthContext())->publicKey();
53: }
54:
55: /**
56: * Disable DKIM signing for account
57: *
58: * @return bool
59: */
60: public function disable(): bool
61: {
62: if (!IS_CLI) {
63: return $this->query('dkim_disable');
64: }
65:
66: if (!$this->dns_configured()) {
67: return error("Cannot automatically manage DKIM on DNS-less site");
68: }
69:
70: if (!$this->has()) {
71: return error("DKIM not enabled");
72: }
73:
74: $dkim = Dkim::instantiateContexted($this->getAuthContext());
75: // bulk change DNS
76: $r = new Record('_bogus-domain.com', [
77: 'name' => $dkim->selectorRecord(),
78: 'rr' => 'TXT'
79: ]);
80: (new Bulk([$this->site]))->remove($r, function (\apnscpFunctionInterceptor $afi, Record $r) {
81: return $afi->email_transport_exists($r->getZone());
82: });
83:
84: return true;
85: }
86:
87: /**
88: * Enable DKIM signing for account
89: *
90: * @return bool
91: */
92: public function enable(): bool
93: {
94: if (!IS_CLI) {
95: return $this->query('dkim_enable');
96: }
97:
98: if (!$this->dns_configured()) {
99: return error("Cannot automatically manage DKIM on DNS-less site");
100: }
101:
102: if (!$this->has()) {
103: return error("DKIM not enabled");
104: }
105:
106: $dkim = Dkim::instantiateContexted($this->getAuthContext());
107: // bulk change DNS
108: $r = new Record('_bogus-domain.com', [
109: 'name' => $dkim->selectorRecord(),
110: 'rr' => 'TXT',
111: 'parameter' => $dkim->dkimRecord()
112: ]);
113: (new Bulk([$this->site]))->add($r, function (\apnscpFunctionInterceptor $afi, Record $r) {
114: return $afi->email_transport_exists($r->getZone());
115: });
116:
117: return true;
118: }
119:
120: /**
121: * Get active selector name
122: *
123: * @return string|null
124: */
125: public function selector(): ?string
126: {
127: if (!IS_CLI) {
128: return $this->query('dkim_selector');
129: }
130:
131: if (!Rspamd::present()) {
132: return null;
133: }
134:
135: return Dkim::instantiateContexted($this->getAuthContext())->selector();
136: }
137:
138: /**
139: * Expire disused DKIM selector
140: *
141: * @param string $selector
142: * @return bool
143: */
144: public function expire(string $selector): bool
145: {
146: if (!IS_CLI && posix_getuid()) {
147: return $this->query('dkim_expire', $selector);
148: }
149:
150: if (!$this->has()) {
151: return error("DKIM not enabled");
152: }
153:
154: if (Dkim::singleKey() && !$this->permission_level & PRIVILEGE_ADMIN) {
155: return error("Site owners cannot expire global DKIM key");
156: }
157:
158: return Dkim::instantiateContexted($this->getAuthContext())->expireSelector($selector);
159: }
160:
161: /**
162: * Account has DKIM key
163: *
164: * @return bool
165: */
166: public function has(): bool
167: {
168: if (!IS_CLI) {
169: return $this->query('dkim_has');
170: }
171:
172: if (($this->permission_level & PRIVILEGE_SITE) && $this->email_get_provider() !== 'builtin') {
173: return false;
174: }
175:
176: return file_exists(Dkim::globalKeyPath($this->selector()));
177: }
178:
179: /**
180: * Rotate DKIM key
181: *
182: * @param string|null $into optional selector to roll into
183: * @return bool
184: */
185: public function roll(string $into = null): bool
186: {
187: if (!IS_CLI && posix_getuid()) {
188: return $this->query('dkim_roll', $into);
189: }
190:
191: if (!$this->has()) {
192: return warn("DKIM not configured");
193: }
194:
195: if (Dkim::singleKey() && !$this->permission_level & PRIVILEGE_ADMIN) {
196: return error("Site owners cannot roll global DKIM key");
197: }
198:
199: return Dkim::instantiateContexted($this->getAuthContext())->rollKey($into);
200: }
201: }
202: