1: | <?php |
2: | declare(strict_types=1); |
3: | |
4: | |
5: | |
6: | |
7: | |
8: | |
9: | |
10: | |
11: | |
12: | |
13: | |
14: | |
15: | use Module\Support\Aliases; |
16: | use Module\Support\Webapps\MetaManager; |
17: | use Opcenter\License; |
18: | use Opcenter\Map; |
19: | use Opcenter\Service\ConfigurationContext; |
20: | |
21: | |
22: | |
23: | |
24: | |
25: | |
26: | class Aliases_Module extends Aliases |
27: | { |
28: | const DEPENDENCY_MAP = [ |
29: | 'siteinfo', |
30: | 'apache', |
31: | 'users' |
32: | ]; |
33: | |
34: | |
35: | const DNS_VERIFICATION_RECORD = 'newacct'; |
36: | |
37: | |
38: | |
39: | |
40: | |
41: | |
42: | public function __construct() |
43: | { |
44: | $this->exportedFunctions = array( |
45: | '*' => PRIVILEGE_SITE, |
46: | 'add_domain_backend' => PRIVILEGE_SERVER_EXEC | PRIVILEGE_SITE, |
47: | 'map_domain' => PRIVILEGE_SERVER_EXEC, |
48: | ); |
49: | parent::__construct(); |
50: | } |
51: | |
52: | |
53: | |
54: | |
55: | |
56: | |
57: | |
58: | |
59: | public function add_domain_backend(string $domain, string $path): bool |
60: | { |
61: | $parent = dirname($path); |
62: | |
63: | if (!file_exists($this->domain_fs_path() . $parent)) { |
64: | warn('%s: parent directory does not exist', $parent); |
65: | if (!$this->file_create_directory($parent, 0755, true)) { |
66: | return error('failed to create parent directory'); |
67: | } |
68: | } |
69: | if (!$this->createDocumentRoot($path, $domain)) { |
70: | return error("failed to create document root `%s'", $path); |
71: | } |
72: | $stat = $this->file_stat($path); |
73: | $user = null; |
74: | if (isset($stat['owner'])) { |
75: | $user = $stat['owner']; |
76: | if (ctype_digit($user)) { |
77: | warn("no such user found for domain `%s' uid `%d'", $domain, $user); |
78: | $user = null; |
79: | } |
80: | } else { |
81: | Error_Reporter::report('Bad stat response: ' . var_export($stat, true)); |
82: | } |
83: | |
84: | if (!$user && $stat['uid'] < \User_Module::MIN_UID) { |
85: | return error("unable to determine ownership of docroot `%s' for `%s'", |
86: | $path, $domain); |
87: | } else if (!$user) { |
88: | warn("invalid uid `%d' detected on `%s', squashed to account uid `%d'", |
89: | $stat['uid'], |
90: | $domain, |
91: | $this->user_id |
92: | ); |
93: | $this->file_chown($path, $this->user_id, true); |
94: | $user = $this->username; |
95: | } |
96: | |
97: | $ret = $this->add_alias($domain); |
98: | if (!$ret) { |
99: | file_exists($path) && unlink($path); |
100: | |
101: | return error("failed to add domain alias configuration `%s'", $domain); |
102: | } |
103: | |
104: | $this->notify_admin($domain, $path); |
105: | |
106: | if (!$this->map_domain('add', $domain, $path, $user)) { |
107: | return error("failed to map domain `%s' in http configuration", $domain); |
108: | } |
109: | $this->removeBypass($domain); |
110: | |
111: | return true; |
112: | } |
113: | |
114: | |
115: | |
116: | |
117: | |
118: | |
119: | |
120: | |
121: | |
122: | |
123: | protected function add_alias(string $alias): bool |
124: | { |
125: | if (!IS_CLI) { |
126: | return error('%s should be called from backend', __METHOD__); |
127: | } |
128: | |
129: | $alias = strtolower($alias); |
130: | if (!preg_match(Regex::DOMAIN, $alias)) { |
131: | return error('%s: invalid domain', $alias); |
132: | } |
133: | |
134: | $license = License::get(); |
135: | |
136: | if ($license->isDevelopment() && substr($alias, -5) !== '.test') { |
137: | return error("License permits only .test TLDs. `%s' provided.", $alias); |
138: | } |
139: | |
140: | $aliases = (array)$this->getServiceValue('aliases', 'aliases'); |
141: | if (in_array($alias, $aliases, true)) { |
142: | return true; |
143: | } |
144: | $aliases[] = $alias; |
145: | $limit = $this->getServiceValue('aliases', 'max', null); |
146: | if (null !== $limit && count($aliases) > $limit) { |
147: | return error("account has reached max amount of addon domains, `%d'", $limit); |
148: | } |
149: | |
150: | $count = \count(Map::load(Map::home() . '/' . Map::DOMAIN_MAP, 'r-')->fetchAll()); |
151: | if ($license->hasDomainCountRestriction() && ++$count > $license->getDomainLimit()) { |
152: | return error('License limit reached for domain count: %(limit)d', |
153: | ['limit' => $license->getDomainLimit()]); |
154: | } |
155: | |
156: | return $this->setConfigJournal('aliases', 'enabled', 1) && |
157: | $this->setConfigJournal('aliases', 'aliases', $aliases); |
158: | } |
159: | |
160: | |
161: | |
162: | |
163: | |
164: | |
165: | |
166: | |
167: | protected function notify_admin(string $domain, string $path): bool |
168: | { |
169: | if (!DOMAINS_NOTIFY) { |
170: | return false; |
171: | } |
172: | |
173: | \Lararia\Bootstrapper::minstrap(); |
174: | $mail = \Illuminate\Support\Facades\Mail::to(Crm_Module::COPY_ADMIN); |
175: | $vars = [ |
176: | 'domain' => $domain, |
177: | 'path' => $path, |
178: | 'authdomain' => $this->domain, |
179: | 'authuser' => $this->username, |
180: | 'siteid' => $this->site_id, |
181: | ]; |
182: | |
183: | $mail->send((new \Lararia\Mail\Simple('email.aliases.domain-add', $vars))->setSubject(_("Domain Changed"))); |
184: | |
185: | return true; |
186: | } |
187: | |
188: | |
189: | |
190: | |
191: | |
192: | |
193: | |
194: | |
195: | |
196: | |
197: | |
198: | |
199: | public function map_domain(string $mode, string $domain, string $path = null, string $user = null): bool |
200: | { |
201: | if (!IS_CLI) { |
202: | return $this->query('aliases_map_domain', |
203: | $mode, |
204: | $domain, |
205: | $path, |
206: | $user); |
207: | } |
208: | |
209: | $mode = substr($mode, 0, 3); |
210: | if (!preg_match(Regex::DOMAIN, $domain)) { |
211: | return error($domain . ': invalid domain'); |
212: | } |
213: | if ($mode != 'add' && $mode != 'del') { |
214: | return error($mode . ': invalid map operation'); |
215: | } |
216: | if ($mode == 'del') { |
217: | return $this->removeMap($domain) && |
218: | $this->file_delete('/home/*/all_domains/' . $domain); |
219: | } else if ($mode == 'add') { |
220: | if (!$user) { |
221: | $stat = $this->file_stat($path); |
222: | if ($stat instanceof Exception) { |
223: | return error($stat->getMessage()); |
224: | } |
225: | |
226: | $user = $this->user_get_username_from_uid($stat['uid']); |
227: | } |
228: | if ($user) { |
229: | if ($user == $this->tomcat_system_user()) { |
230: | $user = $this->username; |
231: | $uid = $this->user_get_uid_from_username($user); |
232: | } else { |
233: | $uid = $this->user_get_uid_from_username($user); |
234: | if ($uid < User_Module::MIN_UID) { |
235: | $user = $this->username; |
236: | } |
237: | } |
238: | |
239: | $user_home = '/home/' . $user; |
240: | $user_home_abs = $this->domain_fs_path() . $user_home; |
241: | |
242: | if (!file_exists($this->domain_fs_path() . $path)) { |
243: | warn($path . ': path does not exist, creating link'); |
244: | } |
245: | if (!file_exists($user_home_abs . '/all_domains')) { |
246: | $this->file_create_directory($user_home . '/all_domains'); |
247: | $this->file_chown($user_home . '/all_domains', $user); |
248: | } |
249: | |
250: | $fullpath = $this->domain_fs_path() . $user_home . '/all_domains/' . $domain; |
251: | |
252: | |
253: | clearstatcache(true, $fullpath); |
254: | if (is_link($fullpath)) { |
255: | unlink($fullpath); |
256: | } else if (is_dir($fullpath)) { |
257: | Error_Reporter::mute_warning(true); |
258: | if (!rmdir($fullpath)) { |
259: | warn('not creating symlink all_domains/%s; a directory was found within ' . |
260: | 'that contains files', $domain); |
261: | } |
262: | Error_Reporter::unmute_warning(); |
263: | } |
264: | |
265: | |
266: | $localpath = $user_home . '/all_domains/' . $domain; |
267: | if (!file_exists($fullpath)) { |
268: | $this->file_symlink($path, $localpath); |
269: | } else { |
270: | warn('cannot make symlink %s - file exists, possibly misplaced docroot?', |
271: | $localpath |
272: | ); |
273: | } |
274: | } else { |
275: | warn($domain . ': cannot determine user for domain mapping'); |
276: | } |
277: | } |
278: | if ($mode == 'add') { |
279: | return $this->addMap($domain, $path); |
280: | } |
281: | |
282: | return $this->removeMap($domain); |
283: | } |
284: | |
285: | |
286: | |
287: | |
288: | |
289: | |
290: | |
291: | public function bypass_exists(string $domain): bool |
292: | { |
293: | return $this->isBypass($domain); |
294: | } |
295: | |
296: | |
297: | |
298: | |
299: | |
300: | |
301: | |
302: | |
303: | public function modify_domain(string $domain, array $newparams): bool |
304: | { |
305: | if (!IS_CLI) { |
306: | $ret = $this->query('aliases_modify_domain', $domain, $newparams); |
307: | if (!$this->inContext()) { |
308: | \Preferences::reload(); |
309: | } |
310: | $this->web_purge(); |
311: | |
312: | return $ret; |
313: | } |
314: | if (!$this->domain_exists($domain)) { |
315: | return error("domain `$domain' is not attached to account"); |
316: | } |
317: | if ($this->shared_domain_hosted($domain)) { |
318: | return error("domain `$domain' is hosted by another account"); |
319: | } |
320: | if ($domain === $this->getConfig('siteinfo', 'domain')) { |
321: | return error('cannot modify primary domain'); |
322: | } |
323: | |
324: | if (isset($newparams['owner'])) { |
325: | $newowner = $newparams['owner']; |
326: | if (!$this->_change_owner($domain, $newowner)) { |
327: | return false; |
328: | } |
329: | } |
330: | |
331: | if (isset($newparams['path'])) { |
332: | $path = $newparams['path']; |
333: | if (!$this->_change_path($domain, $path)) { |
334: | return false; |
335: | } |
336: | } |
337: | |
338: | if (isset($newparams['domain'])) { |
339: | if (License::get()->isDevelopment() && substr($newparams['domain'], -5) !== '.test') { |
340: | return error("License permits only .test TLDs. `%s' provided.", $newparams['domain']); |
341: | } |
342: | |
343: | $newdomain = $newparams['domain']; |
344: | if (!$this->_change_domain($domain, $newdomain)) { |
345: | return false; |
346: | } |
347: | } |
348: | $this->web_purge(); |
349: | |
350: | return true; |
351: | } |
352: | |
353: | |
354: | |
355: | |
356: | |
357: | |
358: | |
359: | public function domain_exists(string $domain): bool |
360: | { |
361: | return $domain === $this->getConfig('siteinfo', 'domain') || |
362: | in_array($domain, $this->getConfig('aliases', 'aliases'), true); |
363: | } |
364: | |
365: | |
366: | |
367: | |
368: | |
369: | |
370: | public function list_shared_domains(): array |
371: | { |
372: | if (!IS_CLI) { |
373: | $cache = \Cache_Account::spawn($this->getAuthContext()); |
374: | if (false !== ($aliases = $cache->get(static::CACHE_KEY))) { |
375: | return $aliases; |
376: | } |
377: | |
378: | return $this->query('aliases_list_shared_domains'); |
379: | } |
380: | |
381: | return $this->transformMap(); |
382: | } |
383: | |
384: | |
385: | |
386: | |
387: | |
388: | |
389: | |
390: | public function shared_domain_hosted(string $domain): bool |
391: | { |
392: | $domain = strtolower($domain); |
393: | if ($this->dns_domain_hosted($domain, true)) { |
394: | return true; |
395: | } |
396: | $id = Auth::get_site_id_from_domain($domain); |
397: | if ($id && $id != $this->site_id) { |
398: | return true; |
399: | } |
400: | |
401: | return false; |
402: | } |
403: | |
404: | private function _change_owner(string $domain, string $user): bool |
405: | { |
406: | $users = $this->user_get_users(); |
407: | if (!isset($users[$user])) { |
408: | return error("user `$user' not found"); |
409: | } |
410: | $map = $this->list_shared_domains(); |
411: | if (!array_key_exists($domain, $map)) { |
412: | return error("domain `$domain' not found in domain map"); |
413: | } |
414: | |
415: | $path = $map[$domain]; |
416: | return $this->file_chown($path, $user, true); |
417: | } |
418: | |
419: | private function _change_path(string $domain, string $newpath): bool |
420: | { |
421: | $map = $this->list_shared_domains(); |
422: | if (!array_key_exists($domain, $map)) { |
423: | return error("domain `%s' not found in domain map", $domain); |
424: | } |
425: | |
426: | if (!preg_match(Regex::ADDON_DOMAIN_PATH, $newpath)) { |
427: | return error($newpath . ': invalid path'); |
428: | } |
429: | $oldpath = $map[$domain]; |
430: | if (!$this->removeMap($domain)) { |
431: | return false; |
432: | } |
433: | if (!file_exists($this->domain_fs_path() . $newpath)) { |
434: | $this->createDocumentRoot($newpath, $domain); |
435: | } |
436: | if (!$this->addMap($domain, $newpath)) { |
437: | |
438: | $this->addMap($domain, $oldpath); |
439: | |
440: | return error("domain `$domain' path change failure - reverting"); |
441: | } |
442: | |
443: | if ($oldpath === $newpath) { |
444: | return true; |
445: | } |
446: | |
447: | return true; |
448: | } |
449: | |
450: | private function _change_domain(string $domain, string $newdomain): bool |
451: | { |
452: | $map = $this->list_shared_domains(); |
453: | if (!array_key_exists($domain, $map)) { |
454: | return error("domain `$domain' not found in domain map"); |
455: | } |
456: | $path = $map[$domain]; |
457: | MetaManager::instantiateContexted($this->getAuthContext()) |
458: | ->merge($path, ['host' => $newdomain])->sync(); |
459: | $ret = $this->remove_domain($domain) |
460: | && $this->_synchronize_changes() && |
461: | $this->add_domain($newdomain, $path); |
462: | if ($ret) { |
463: | warn('activate configuration changes for new domain to take effect'); |
464: | } |
465: | |
466: | return $ret; |
467: | } |
468: | |
469: | |
470: | |
471: | |
472: | |
473: | |
474: | |
475: | public function remove_domain(string $domain): bool |
476: | { |
477: | if (!IS_CLI) { |
478: | $docroot = $this->web_get_docroot($domain); |
479: | $status = $this->query('aliases_remove_domain', $domain); |
480: | if ($status && $docroot) { |
481: | MetaManager::factory($this->getAuthContext()) |
482: | ->forget($docroot)->sync(); |
483: | } |
484: | return $status; |
485: | } |
486: | $domain = strtolower($domain); |
487: | if (!preg_match(Regex::DOMAIN, $domain)) { |
488: | return error("Invalid domain `$domain'"); |
489: | } |
490: | $this->map_domain('delete', $domain); |
491: | $journaledCheck = array_get($this->getNewServices('aliases'), 'aliases', [$domain]); |
492: | if (!\in_array($domain, $journaledCheck, true)) { |
493: | return warn("Domain `%s' already removed administratively but previously added by site administrator", $domain); |
494: | } |
495: | if (!$this->remove_alias($domain)) { |
496: | return false; |
497: | } |
498: | |
499: | |
500: | |
501: | |
502: | |
503: | return true; |
504: | } |
505: | |
506: | public function remove_alias(string $alias): bool |
507: | { |
508: | if (!IS_CLI) { |
509: | return $this->query('aliases_remove_alias', $alias); |
510: | } |
511: | $alias = strtolower(trim($alias)); |
512: | if (!preg_match(Regex::DOMAIN, $alias)) { |
513: | return error('Invalid domain'); |
514: | } |
515: | |
516: | $aliases = (array)array_get($this->getNewServices('aliases'), 'aliases', $this->getServiceValue('aliases', 'aliases')); |
517: | |
518: | $key = array_search($alias, $aliases, true); |
519: | if ($key === false) { |
520: | return error("domain `%s' not found", $alias); |
521: | } |
522: | |
523: | unset($aliases[$key]); |
524: | return $this->setConfigJournal('aliases', 'aliases', $aliases) instanceof Auth_Info_Account; |
525: | } |
526: | |
527: | private function _synchronize_changes(): bool |
528: | { |
529: | if ($this->auth_is_inactive()) { |
530: | return error('account is suspended, will not resync'); |
531: | } |
532: | $cmd = new Util_Account_Editor($this->getAuthContext()->getAccount(), $this->getAuthContext()); |
533: | |
534: | $cmd->importConfig(); |
535: | $status = $cmd->edit(); |
536: | if (!$status) { |
537: | return error('failed to activate domain changes'); |
538: | } |
539: | info('Hang tight! Domain changes will be active within a few minutes, but may take up to 24 hours to work properly.'); |
540: | return true; |
541: | } |
542: | |
543: | public function add_domain(string $domain, string $path): bool |
544: | { |
545: | if (!IS_CLI) { |
546: | return $this->query('aliases_add_domain', $domain, $path); |
547: | } |
548: | $domain = preg_replace('/^www\./', '', strtolower($domain)); |
549: | $path = rtrim(strtr($path, ['..' => '']), '/') . '/'; |
550: | |
551: | if (!preg_match(Regex::DOMAIN, $domain)) { |
552: | return error($domain . ': invalid domain'); |
553: | } |
554: | if (!preg_match(Regex::ADDON_DOMAIN_PATH, $path)) { |
555: | return error($path . ': invalid path'); |
556: | } |
557: | if ($domain === $this->getServiceValue('siteinfo', 'domain')) { |
558: | return error('Primary domain may not be replicated as a shared domain'); |
559: | } |
560: | if ($domain === SERVER_NAME) { |
561: | return error('Domain may not duplicate system hostname'); |
562: | } |
563: | |
564: | if (!$this->_verify($domain)) { |
565: | return false; |
566: | } |
567: | |
568: | return $this->query('aliases_add_domain_backend', $domain, $path); |
569: | } |
570: | |
571: | protected function _verify(string $domain): bool |
572: | { |
573: | if (file_exists($file = $this->domain_info_path('new/aliases'))) { |
574: | |
575: | |
576: | |
577: | if ($domain === $this->getConfig('siteinfo', 'domain') || in_array($domain, \Util_Conf::parse_ini($file), true)) { |
578: | return error("domain `%s' exists", $domain); |
579: | } |
580: | } else if (!file_exists($file) && $this->domain_exists($domain)) { |
581: | return error("domain `%s' exists", $domain); |
582: | } |
583: | if (!$this->dns_verified($domain)) { |
584: | return error("Domain must be verified through the DNS service first"); |
585: | } |
586: | if (\in_array($domain, (array)$this->getServiceValue('aliases', 'aliases'), true)) { |
587: | |
588: | return true; |
589: | } |
590: | if ($this->shared_domain_hosted($domain)) { |
591: | return error("`%s': domain is already hosted by another account", $domain); |
592: | } |
593: | |
594: | if (!DOMAINS_DNS_CHECK) { |
595: | return true; |
596: | } |
597: | |
598: | if (!$this->dns_domain_on_account($domain) && |
599: | !$this->_verify_dns($domain) && !$this->_verify_url($domain) |
600: | ) { |
601: | $nameservers = $this->dns_get_authns_from_host($domain); |
602: | $cpnameservers = $this->dns_get_hosting_nameservers($domain); |
603: | $hash = $this->challenge_token($domain); |
604: | $script = $hash . '.html'; |
605: | |
606: | return error("`%s': domain has DNS records delegated to nameservers %s. " . |
607: | 'Domain cannot be added to this account for security. Complete one of the following options to ' . |
608: | 'verify ownership:' . "\r\n\r\n" . |
609: | '(1) Change nameservers to %s within the domain registrar' . "\r\n" . |
610: | "(2) Upload a html file to your old hosting provider accessible via http://%s/%s with the content:\r\n\t%s" . "\r\n" . |
611: | "(3) Create a temporary DNS record named %s.%s with an `A' resource record that points to %s" . "\r\n\r\n" . |
612: | 'Please contact your previous hosting provider for assistance with performing any of ' . |
613: | 'these verification options.', |
614: | $domain, |
615: | join(', ', $nameservers), |
616: | join(', ', $cpnameservers), |
617: | $domain, |
618: | $script, |
619: | $hash, |
620: | self::DNS_VERIFICATION_RECORD, |
621: | $domain, |
622: | $this->dns_get_public_ip() |
623: | ); |
624: | } |
625: | |
626: | return true; |
627: | } |
628: | |
629: | |
630: | |
631: | |
632: | |
633: | |
634: | |
635: | protected function _verify_dns(string $domain): bool |
636: | { |
637: | |
638: | |
639: | |
640: | |
641: | |
642: | |
643: | |
644: | if ($this->isBypass($domain)) { |
645: | return true; |
646: | } |
647: | |
648: | $ip = silence(function () use ($domain) { |
649: | return parent::__call('dns_gethostbyname_t', [$domain, 5000]); |
650: | }); |
651: | if (!$ip) { |
652: | return true; |
653: | } |
654: | |
655: | $myip = (array)$this->dns_get_public_ip(); |
656: | foreach ($myip as $testip) { |
657: | if ($ip === $testip) { |
658: | |
659: | return true; |
660: | } |
661: | } |
662: | if ($this->domain_is_delegated($domain)) { |
663: | return true; |
664: | } |
665: | $record = self::DNS_VERIFICATION_RECORD . '.' . $domain; |
666: | $tmp = $this->dns_gethostbyname_t($record, 1500); |
667: | if ($tmp && \in_array($tmp, $myip, true)) { |
668: | return true; |
669: | } |
670: | |
671: | return false; |
672: | } |
673: | |
674: | |
675: | |
676: | |
677: | |
678: | |
679: | |
680: | protected function domain_is_delegated(string $domain): int |
681: | { |
682: | if ($this->dns_domain_uses_nameservers($domain)) { |
683: | return 1; |
684: | } |
685: | $ns = $this->dns_get_authns_from_host($domain); |
686: | |
687: | |
688: | |
689: | |
690: | if (is_null($ns)) { |
691: | return -1; |
692: | } |
693: | $hostingns = $this->dns_get_hosting_nameservers($domain); |
694: | |
695: | |
696: | foreach ($ns as $n) { |
697: | if (in_array($n, $hostingns)) { |
698: | return 1; |
699: | } |
700: | } |
701: | |
702: | return 0; |
703: | } |
704: | |
705: | protected function _verify_url(string $domain): bool |
706: | { |
707: | $hash = $this->challenge_token($domain); |
708: | $url = 'http://' . $domain . '/' . $hash . '.html'; |
709: | if (extension_loaded('curl')) { |
710: | $adapter = new HTTP_Request2_Adapter_Curl(); |
711: | } else { |
712: | $adapter = new HTTP_Request2_Adapter_Socket(); |
713: | } |
714: | |
715: | $http = new HTTP_Request2( |
716: | $url, |
717: | HTTP_Request2::METHOD_GET, |
718: | array( |
719: | 'adapter' => $adapter, |
720: | 'follow_redirects' => true |
721: | ) |
722: | ); |
723: | |
724: | try { |
725: | $response = $http->send(); |
726: | $code = $response->getStatus(); |
727: | switch ($code) { |
728: | case 303: |
729: | case 302: |
730: | case 301: |
731: | case 200: |
732: | break; |
733: | case 403: |
734: | return error('Verification URL request forbidden by server'); |
735: | case 404: |
736: | return false; |
737: | default: |
738: | return error("Verification URL request failed, code `%d': %s", |
739: | $code, $response->getReasonPhrase()); |
740: | } |
741: | $content = $response->getBody(); |
742: | } catch (HTTP_Request2_Exception $e) { |
743: | return error("Fatal error retrieving verification URL: `%s'", $e->getMessage()); |
744: | } |
745: | |
746: | if (!preg_match("!^https?://$domain/$hash.html!", $response->getEffectiveUrl())) { |
747: | return error( |
748: | 'Verification URL request moved to different location other than accepted: %s', |
749: | $response->getEffectiveUrl() |
750: | ); |
751: | } |
752: | return trim(strip_tags($content)) === $hash; |
753: | } |
754: | |
755: | |
756: | |
757: | |
758: | |
759: | |
760: | public function challenge_token(): string |
761: | { |
762: | if (!IS_CLI) { |
763: | return $this->query('aliases_challenge_token'); |
764: | } |
765: | $str = (string)fileinode($this->domain_info_path('users')); |
766: | |
767: | return sha1($str); |
768: | } |
769: | |
770: | public function remove_shared_domain(string $domain): bool |
771: | { |
772: | deprecated_func('Use remove_domain'); |
773: | |
774: | return $this->remove_domain($domain); |
775: | } |
776: | |
777: | public function add_shared_domain(string $domain, string $path): bool |
778: | { |
779: | deprecated_func('Use add_domain'); |
780: | |
781: | return $this->add_domain($domain, $path); |
782: | } |
783: | |
784: | public function shared_domain_exists($domain): bool |
785: | { |
786: | deprecated_func('use domain_exists'); |
787: | |
788: | return $this->domain_exists($domain); |
789: | } |
790: | |
791: | |
792: | |
793: | |
794: | |
795: | |
796: | public function list_unsynchronized_domains(): array |
797: | { |
798: | $active = parent::getActiveServices('aliases'); |
799: | $active = $active['aliases']; |
800: | $pending = parent::getNewServices('aliases'); |
801: | if ($pending === null) { |
802: | return ['add' => [], 'remove' => []]; |
803: | |
804: | } |
805: | if ($pending) { |
806: | $pending = $pending['aliases']; |
807: | } |
808: | $domains = array_keys($this->list_shared_domains()); |
809: | $changes = array( |
810: | 'add' => array_diff($pending, $active), |
811: | 'remove' => array_diff($active, $domains) |
812: | ); |
813: | |
814: | return $changes; |
815: | } |
816: | |
817: | |
818: | |
819: | |
820: | |
821: | |
822: | public function changes_pending(): bool |
823: | { |
824: | if (!IS_CLI) { |
825: | |
826: | return $this->query('aliases_changes_pending'); |
827: | } |
828: | return file_exists($this->domain_info_path('new/aliases')); |
829: | } |
830: | |
831: | public function synchronize_changes(): bool |
832: | { |
833: | if (!IS_CLI) { |
834: | $ret = $this->query('aliases_synchronize_changes'); |
835: | $this->freshenAuthContext(); |
836: | return $ret; |
837: | } |
838: | $aliases = array_keys($this->list_shared_domains()); |
839: | |
840: | $this->setConfigJournal('aliases', 'aliases', $aliases); |
841: | return $this->_synchronize_changes(); |
842: | } |
843: | |
844: | |
845: | |
846: | |
847: | |
848: | |
849: | public function list_aliases(): array |
850: | { |
851: | $values = $this->getServiceValue('aliases', 'aliases'); |
852: | |
853: | return (array)$values; |
854: | } |
855: | |
856: | public function _reset(Util_Account_Editor &$editor = null) |
857: | { |
858: | $module = 'aliases'; |
859: | $params = array('aliases' => array()); |
860: | if (!platform_is('7.5')) { |
861: | $params['enabled'] = 0; |
862: | } |
863: | if ($editor) { |
864: | foreach ($params as $k => $v) { |
865: | $editor->setConfig($module, $k, $v); |
866: | } |
867: | } |
868: | |
869: | return array($module => $params); |
870: | } |
871: | |
872: | public function _edit() |
873: | { |
874: | $conf_old = $this->getAuthContext()->conf('siteinfo', 'old'); |
875: | $conf_new = $this->getAuthContext()->conf('siteinfo', 'new'); |
876: | $domainold = $conf_old['domain']; |
877: | $domainnew = $conf_new['domain']; |
878: | |
879: | |
880: | if ($domainold !== $domainnew && $this->isBypass($domainnew)) { |
881: | $this->removeBypass($domainnew); |
882: | } |
883: | $aliasesnew = array_get($this->getAuthContext()->conf('aliases', 'new'), 'aliases', []); |
884: | $aliasesold = array_get($this->getAuthContext()->conf('aliases', 'old'), 'aliases', []); |
885: | |
886: | $add = array_diff($aliasesnew, $aliasesold); |
887: | $rem = array_diff($aliasesold, $aliasesnew); |
888: | $db = Map::load(Map::DOMAIN_MAP, 'wd'); |
889: | |
890: | foreach ($add as $a) { |
891: | $db->insert($a, $this->site); |
892: | } |
893: | |
894: | foreach ($rem as $r) { |
895: | if ($r === $this->domain) { |
896: | |
897: | continue; |
898: | } |
899: | $db->delete($r); |
900: | } |
901: | $db->close(); |
902: | |
903: | return; |
904: | } |
905: | |
906: | public function _create() |
907: | { |
908: | $db = Map::write(Map::DOMAIN_MAP); |
909: | $conf = array_get($this->getAuthContext()->conf('aliases'), 'aliases', []); |
910: | foreach ($conf as $domain) { |
911: | $db->insert($domain, $this->site); |
912: | } |
913: | $db->close(); |
914: | } |
915: | |
916: | public function _delete() |
917: | { |
918: | if (platform_is('7.5')) { |
919: | return; |
920: | } |
921: | $db = Map::write(Map::DOMAIN_MAP); |
922: | $conf = array_get($this->getAuthContext()->conf('aliases'), 'aliases', []); |
923: | foreach ($conf as $domain) { |
924: | $db->delete($domain); |
925: | } |
926: | $db->close(); |
927: | } |
928: | |
929: | public function _edit_user(string $user, string $usernew, array $oldpwd) |
930: | { |
931: | if ($user === $usernew) { |
932: | return; |
933: | } |
934: | |
935: | $domains = $this->list_shared_domains(); |
936: | $home = $oldpwd['home']; |
937: | $newhome = preg_replace('!' . DIRECTORY_SEPARATOR . $user . '!', DIRECTORY_SEPARATOR . $usernew, $home, 1); |
938: | foreach ($domains as $domain => $path) { |
939: | if (0 !== strpos($path, $home)) { |
940: | continue; |
941: | } |
942: | $newpath = preg_replace('!^' . $home . '!', $newhome, $path); |
943: | if (!$this->_change_path($domain, $newpath)) { |
944: | warn("failed to update domain `%s'", $domain); |
945: | } |
946: | } |
947: | $this->web_purge(); |
948: | |
949: | return true; |
950: | } |
951: | |
952: | public function _verify_conf(ConfigurationContext $ctx): bool |
953: | { |
954: | return true; |
955: | } |
956: | |
957: | public function _create_user(string $user) |
958: | { |
959: | return true; |
960: | } |
961: | |
962: | public function _delete_user(string $user) |
963: | { |
964: | return true; |
965: | } |
966: | |
967: | |
968: | } |