1: <?php
2: declare(strict_types=1);
3: /**
4: * +------------------------------------------------------------+
5: * | apnscp |
6: * +------------------------------------------------------------+
7: * | Copyright (c) Apis Networks |
8: * +------------------------------------------------------------+
9: * | Licensed under Artistic License 2.0 |
10: * +------------------------------------------------------------+
11: * | Author: Matt Saladna (msaladna@apisnetworks.com) |
12: * +------------------------------------------------------------+
13: */
14:
15: use Module\Support\Auth;
16: use Opcenter\Filesystem\Quota;
17: use Opcenter\Map;
18: use Opcenter\Process;
19: use Opcenter\Service\Validators\Billing\ParentInvoice;
20:
21: /**
22: * Provides site administrator-specific functionality
23: *
24: * @package core
25: *
26: */
27: class Site_Module extends Auth
28: implements Module\Skeleton\Contracts\Hookable
29: {
30: use ImpersonableTrait;
31:
32: // @deprecated
33: const AMNESTY_MULTIPLIER = Diskquota_Module::AMNESTY_MULTIPLIER;
34:
35: const DEPENDENCY_MAP = [];
36:
37:
38: protected $exportedFunctions = [
39: '*' => PRIVILEGE_SITE,
40: 'get_admin_email' => PRIVILEGE_SITE | PRIVILEGE_USER,
41: 'ip_address' => PRIVILEGE_SITE | PRIVILEGE_USER,
42: 'split_hostname' => PRIVILEGE_SITE | PRIVILEGE_USER
43: ];
44:
45: /**
46: * bool user_service_enabled(string, string)
47: *
48: * @param string $mUser username
49: * @param string $mSrvc service name, possible values are ssh proftpd and imap
50: * @return bool
51: */
52: public function user_service_enabled(string $user, string $service): bool
53: {
54: $svc_cache = $this->user_svc_cache;
55: $svc_file = $this->domain_fs_path() . '/etc/' . $service . '.pamlist';
56: $site = $this->site_id;
57:
58: if (!file_exists($svc_file)) {
59: return error("Invalid service name `%s'", $service);
60: }
61:
62: /** check local cache */
63: if (!isset($svc_cache[$site]) ||
64: filemtime($svc_file) > $svc_cache[$site]['mtime']
65: ) {
66: $fp = fopen($svc_file, 'r');
67: $contents = fread($fp, filesize($svc_file));
68: foreach (explode("\n", $contents) as $line) {
69: $svc_cache[$site]['users'][trim($line)][$service] = 1;
70: }
71:
72: fclose($fp);
73: $svc_cache[$site]['mtime'] = filemtime($svc_file);
74: }
75:
76: return isset($svc_cache[$site]['users'][$user][$service]);
77:
78: }
79:
80: /**
81: * array get_bandwidth_usage(string)
82: *
83: * @privilege PRIVILEGE_SITE
84: * @param int $type type of bandwidth usage to retrieve
85: * @return array|bool indexes begin, rollover, and threshold
86: */
87: public function get_bandwidth_usage(int $type = null)
88: {
89: deprecated_func('Use bandwidth_usage()');
90:
91: return $this->bandwidth_usage($type);
92: }
93:
94: /**
95: * Retrieve day on which banwidth rolls over to 0
96: *
97: * @return int
98: */
99: public function get_bandwidth_rollover(): int
100: {
101: deprecated_func('Use bandwidth_rollover');
102:
103: return $this->bandwidth_rollover();
104: }
105:
106: // }}}
107:
108: /**
109: * bool set_admin_email(string email)
110: *
111: * @privilege PRIVILEGE_SITE
112: * @return bool true on success, false on failure
113: * @param string $email e-mail address to update the record to
114: * Backend PostgreSQL operation to update it in the db
115: */
116: public function set_admin_email(string $email): bool
117: {
118: if ($this->auth_is_demo()) {
119: return error("Email may not be changed in demo mode");
120: }
121:
122: if (!preg_match(Regex::EMAIL, $email)) {
123: return error('Invalid e-mail address, ' . $email);
124: }
125: $oldemail = $this->getConfig('siteinfo', 'email');
126: $pgdb = \PostgreSQL::initialize();
127: $pgdb->query("UPDATE siteinfo SET email = '" . $email . "' WHERE site_id = '" . $this->site_id . "';");
128: // no need to trigger a costly account config rebuild
129: $this->setConfig('siteinfo', 'email', $email);
130:
131: $ret = $pgdb->affected_rows() > 0;
132: if (!$ret) {
133: return false;
134: }
135: parent::sendNotice('email', [
136: 'email' => $oldemail,
137: 'ip' => \Auth::client_ip()
138: ]);
139:
140: return true;
141: }
142:
143: /**
144: * Destroy all processes matching user
145: *
146: * @param string $user
147: * @return bool
148: */
149: public function kill_user(string $user): bool
150: {
151: if (!IS_CLI) {
152: return $this->query('site_kill_user', $user);
153: }
154:
155: if (!($uid = $this->user_get_uid_from_username($user))) {
156: return error("Failed to lookup user `%s'", $user);
157: }
158:
159: if ($uid < \User_Module::MIN_UID) {
160: return error("User `%s' is system user", $user);
161: }
162:
163: foreach (Process::matchUser($uid) as $pid) {
164: Process::killAs($pid, SIGKILL, $uid);
165: }
166:
167: return true;
168: }
169:
170: /**
171: * Get admin email
172: *
173: * @return string
174: */
175: public function get_admin_email(): string
176: {
177: return $this->getConfig('siteinfo', 'email');
178: }
179:
180:
181: /* }}} */
182:
183: // {{{ ip_address()
184:
185: /**
186: * Get IP address attached to account
187: *
188: * @return string
189: */
190: public function ip_address(): string
191: {
192: $addr = $this->common_get_ip_address() ?: $this->common_get_ip6_address();
193:
194: return is_array($addr) ? array_pop($addr) : $addr;
195: }
196:
197: // }}}
198:
199: /**
200: * Get quota for an account
201: *
202: * qused: disk quota used in KB
203: * qsoft: disk quota soft limit in KB
204: * qhard: disk quota hard limit in KB
205: * fused: files used
206: * fsoft: files soft limit
207: * fhard: files hard limit
208: *
209: * @return array
210: *@see User_Module::get_quota()
211: */
212: public function get_account_quota(): array
213: {
214: if (!IS_CLI) {
215: return $this->query('site_get_account_quota');
216: }
217: return Quota::getGroup($this->group_id);
218: }
219:
220: /**
221: * Get port range allocated to account
222: *
223: * @deprecated see ssh_port_range()
224: * @return array
225: */
226: public function get_port_range(): array
227: {
228: deprecated_func('Use ssh_port_range()');
229: return $this->ssh_port_range();
230: }
231:
232: /**
233: * Wipe an account, reinitializing it to its pristine state
234: *
235: * @param string $token confirmation token
236: * @return bool|string wipe status or confirmation token
237: */
238: public function wipe($token = '')
239: {
240: $token = strtolower((string)$token);
241: $calctoken = $this->_calculateToken();
242: if (!$token) {
243: // allow wiping via AJAX, Account > Settings
244: if (defined('AJAX') && AJAX) {
245: return $calctoken;
246: }
247: $msg = 'This is the most nuclear of options. ' .
248: "Respond with the following token `%s' to confirm";
249:
250: return warn($msg, $calctoken);
251: }
252:
253: if ($token !== $calctoken) {
254: $msg = "provided token `%s' does not match confirmation token `%s'";
255:
256: return error($msg, $token, $calctoken);
257: }
258:
259: if (!IS_CLI) {
260: return $this->query('site_wipe', $token);
261: }
262: if (!Crm_Module::COPY_ADMIN) {
263: return error('Admin reminder address not setup - disallowing account reset');
264: }
265: $editor = new Util_Account_Editor($this->getAuthContext()->getAccount());
266: // assemble domain creation cmd from current config
267: $editor->importConfig();
268: $afi = $this->getApnscpFunctionInterceptor();
269: $modules = $afi->list_all_modules();
270: foreach ($modules as $m) {
271: $c = $afi->get_class_from_module($m);
272: $class = $c::instantiateContexted($this->getAuthContext());
273: $class->_reset($editor);
274: }
275: $addcmd = $editor->setMode('add')->getCommand();
276: // send a copy of the command in case the account gets wiped and
277: // never comes back from the dead
278: Mail::send(Crm_Module::COPY_ADMIN, 'Account Wipe', $addcmd);
279: $delproc = new Util_Account_Editor($this->getAuthContext()->getAccount());
280: if (!$delproc->delete()) {
281: return false;
282: }
283: $proc = new Util_Process_Schedule('now');
284: $ret = $proc->run($addcmd);
285:
286: return $ret['success'];
287: }
288:
289: /**
290: * Token confirmation to delete site
291: *
292: * @return string
293: */
294: private function _calculateToken(): string
295: {
296:
297: $inode = fileinode($this->domain_info_path());
298: $hash = hash('crc32', (string)$inode);
299:
300: return $hash;
301: }
302:
303: /**
304: * Request a temporary bump to account storage
305: *
306: * @see MIN_STORAGE_AMNESTY
307: * @return bool
308: */
309: public function storage_amnesty(): bool
310: {
311: return $this->diskquota_amnesty();
312: }
313:
314: /**
315: * Account is under amnesty
316: *
317: * @return bool
318: */
319: public function amnesty_active(): bool
320: {
321: return $this->diskquota_amnesty_active();
322: }
323:
324: /**
325: * Assume the role of a secondary user
326: *
327: * @param string $user username or domain if parentage supported
328: * @return string
329: */
330: public function hijack(string $user): string
331: {
332: if ($this->user_exists($user)) {
333: if ($user === $this->username) {
334: return $this->session_id;
335: }
336:
337: return $this->impersonateRole($this->site, $user);
338: }
339:
340: if (AUTH_SUBORDINATE_SITE_SSO && ($invoice = $this->getServiceValue('billing', 'invoice'))
341: && ($siteid = \Auth::get_site_id_from_anything($user)))
342: {
343: // permit SSO if config.ini permits, billing,invoice is set, and target site
344: // is within invoice cluster
345: $site = "site${siteid}";
346: $parent = Map::load(ParentInvoice::MAP_FILE, 'r-')->fetch($site);
347: if ($parent === $this->site) {
348: return $this->impersonateRole($site);
349: }
350: }
351:
352: error("unknown user `%s'", $user);
353:
354: return $this->session_id;
355: }
356:
357: public function _create()
358: {
359: return;
360: }
361:
362: public function _delete()
363: {
364: return;
365: }
366:
367: public function _edit()
368: {
369: return;
370: }
371:
372: /**
373: * Configuration verification
374: *
375: * @param \Opcenter\Service\ConfigurationContext $ctx
376: * @return bool
377: */
378: public function _verify_conf(\Opcenter\Service\ConfigurationContext $ctx): bool
379: {
380: return true;
381: }
382:
383: public function _edit_user(string $userold, string $usernew, array $oldpwd)
384: {
385: if ($usernew === $userold) {
386: return;
387: }
388: }
389:
390: public function _create_user(string $user)
391: {
392: }
393:
394: public function _delete_user(string $user)
395: {
396: }
397:
398:
399: }