d052612e1fae2b1db09fdaa76985e207583f2a60
[friendica.git/.git] / src / Console / ServerBlock.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2020, Friendica
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Console;
23
24 use Asika\SimpleConsole\CommandArgsException;
25 use Asika\SimpleConsole\Console;
26 use Console_Table;
27 use Friendica\Core\Config\IConfig;
28
29 /**
30  * Manage blocked servers
31  *
32  * With this tool, you can list the current blocked servers
33  * or you can add / remove a blocked server from the list
34  */
35 class ServerBlock extends Console
36 {
37         const DEFAULT_REASON = 'blocked';
38
39         protected $helpOptions = ['h', 'help', '?'];
40
41         /**
42          * @var IConfig
43          */
44         private $config;
45
46         protected function getHelp()
47         {
48                 $help = <<<HELP
49 console serverblock - Manage blocked server domain patterns
50 Usage
51     bin/console serverblock [-h|--help|-?] [-v]
52     bin/console serverblock add <pattern> <reason> [-h|--help|-?] [-v]
53     bin/console serverblock remove <pattern> [-h|--help|-?] [-v]
54     bin/console serverblock export <filename>
55     bin/console serverblock import <filename>
56
57 Description
58     With this tool, you can list the current blocked server domain patterns
59     or you can add / remove a blocked server domain pattern from the list.
60     Using the export and import options you can share your server blocklist
61     with other node admins by CSV files.
62
63     Patterns are case-insensitive shell wildcard comprising the following special characters:
64     - * : Any number of characters
65     - ? : Any single character
66     - [<char1><char2>...] : char1 or char2 or...
67
68 Options
69     -h|--help|-? Show help information
70     -v           Show more debug information.
71 HELP;
72                 return $help;
73         }
74
75         public function __construct(IConfig $config, $argv = null)
76         {
77                 parent::__construct($argv);
78
79                 $this->config = $config;
80         }
81
82         protected function doExecute()
83         {
84                 if (count($this->args) == 0) {
85                         $this->printBlockedServers($this->config);
86                         return 0;
87                 }
88
89                 switch ($this->getArgument(0)) {
90                         case 'add':
91                                 return $this->addBlockedServer($this->config);
92                         case 'remove':
93                                 return $this->removeBlockedServer($this->config);
94                         case 'export':
95                                 return $this->exportBlockedServers($this->config);
96                         case 'import':
97                                 return $this->importBlockedServers($this->config);
98                         default:
99                                 throw new CommandArgsException('Unknown command.');
100                                 break;
101                 }
102         }
103
104         /**
105          * Exports the list of blocked domains including the reason for the
106          * block to a CSV file.
107          *
108          * @param IConfig $config
109          */
110         private function exportBlockedServers(IConfig $config)
111         {
112                 $filename = $this->getArgument(1);
113                 $blocklist = $config->get('system', 'blocklist', []);
114                 $fp = fopen($filename, 'w');
115                 if (!$fp) {
116                         throw new Exception(sprintf('The file "%s" could not be created.', $filename));
117                 }
118                 foreach ($blocklist as $domain) {
119                         fputcsv($fp, $domain);
120                 }
121         }
122         /**
123          * Imports a list of domains and a reason for the block from a CSV
124          * file, e.g. created with the export function.
125          *
126          * @param IConfig $config
127          */
128         private function importBlockedServers(IConfig $config)
129         {
130                 $filename = $this->getArgument(1);
131                 $currBlockList = $config->get('system', 'blocklist', []);
132                 $newBlockList = [];
133                 if (($fp = fopen($filename, 'r')) !== false) {
134                         while (($data = fgetcsv($fp, 1000, ',')) !== false) {
135                                 $domain = $data[0];
136                                 if (count($data) == 0) {
137                                         $reason = self::DEFAULT_REASON;
138                                 } else {
139                                         $reason = $data[1];
140                                 }
141                                 $data = [
142                                         'domain' => $domain,
143                                         'reason' => $reason
144                                 ];
145                                 if (!in_array($data, $newBlockList)) {
146                                         $newBlockList[] = $data;
147                 }
148                         }
149                         foreach ($currBlockList as $blocked) {
150                                 if (!in_array($blocked, $newBlockList)) {
151                                         $newBlockList[] = $blocked;
152                 }
153                         }
154                         if ($config->set('system', 'blocklist', $newBlockList)) {
155                                 $this->out(sprintf("Entries from %s that were not blocked before are now blocked", $filename));
156                                 return 0;
157                         } else {
158                                 $this->out(sprintf("Couldn't save '%s' as blocked server", $domain));
159                                 return 1;
160                         }
161
162                 } else {
163                         throw new Exception(sprintf('The file "%s" could not be opened for importing', $filename));
164                 }
165         }
166
167         /**
168          * Prints the whole list of blocked domains including the reason
169          *
170          /* @param IConfig $config
171          */
172         private function printBlockedServers(IConfig $config)
173         {
174                 $table = new Console_Table();
175                 $table->setHeaders(['Domain', 'Reason']);
176                 $blocklist = $config->get('system', 'blocklist', []);
177                 foreach ($blocklist as $domain) {
178                         $table->addRow($domain);
179                 }
180                 $this->out($table->getTable());
181         }
182
183         /**
184          * Adds a server to the blocked list
185          *
186          * @param IConfig $config
187          *
188          * @return int The return code (0 = success, 1 = failed)
189          */
190         private function addBlockedServer(IConfig $config)
191         {
192                 if (count($this->args) < 2 || count($this->args) > 3) {
193                         throw new CommandArgsException('Add needs a domain and optional a reason.');
194                 }
195
196                 $domain = $this->getArgument(1);
197                 $reason = (count($this->args) === 3) ? $this->getArgument(2) : self::DEFAULT_REASON;
198
199                 $update = false;
200
201                 $currBlockList = $config->get('system', 'blocklist', []);
202                 $newBlockList = [];
203                 foreach ($currBlockList  as $blocked) {
204                         if ($blocked['domain'] === $domain) {
205                                 $update = true;
206                                 $newBlockList[] = [
207                                         'domain' => $domain,
208                                         'reason' => $reason,
209                                 ];
210                         } else {
211                                 $newBlockList[] = $blocked;
212                         }
213                 }
214
215                 if (!$update) {
216                         $newBlockList[] = [
217                                 'domain' => $domain,
218                                 'reason' => $reason,
219                         ];
220                 }
221
222                 if ($config->set('system', 'blocklist', $newBlockList)) {
223                         if ($update) {
224                                 $this->out(sprintf("The domain '%s' is now updated. (Reason: '%s')", $domain, $reason));
225                         } else {
226                                 $this->out(sprintf("The domain '%s' is now blocked. (Reason: '%s')", $domain, $reason));
227                         }
228                         return 0;
229                 } else {
230                         $this->out(sprintf("Couldn't save '%s' as blocked server", $domain));
231                         return 1;
232                 }
233         }
234
235         /**
236          * Removes a server from the blocked list
237          *
238          * @param IConfig $config
239          *
240          * @return int The return code (0 = success, 1 = failed)
241          */
242         private function removeBlockedServer(IConfig $config)
243         {
244                 if (count($this->args) !== 2) {
245                         throw new CommandArgsException('Remove needs a second parameter.');
246                 }
247
248                 $domain = $this->getArgument(1);
249
250                 $found = false;
251
252                 $currBlockList = $config->get('system', 'blocklist', []);
253                 $newBlockList = [];
254                 foreach ($currBlockList as $blocked) {
255                         if ($blocked['domain'] === $domain) {
256                                 $found = true;
257                         } else {
258                                 $newBlockList[] = $blocked;
259                         }
260                 }
261
262                 if (!$found) {
263                         $this->out(sprintf("The domain '%s' is not blocked.", $domain));
264                         return 1;
265                 }
266
267                 if ($config->set('system', 'blocklist', $newBlockList)) {
268                         $this->out(sprintf("The domain '%s' is not more blocked", $domain));
269                         return 0;
270                 } else {
271                         $this->out(sprintf("Couldn't remove '%s' from blocked servers", $domain));
272                         return 1;
273                 }
274         }
275 }