]> git.proxmox.com Git - pmg-api.git/blob - src/PMG/API2/Config.pm
fix #2437: api: Add endpoint for managing tls_inbound_domains entries
[pmg-api.git] / src / PMG / API2 / Config.pm
1 package PMG::API2::Config;
2
3 use strict;
4 use warnings;
5 use Data::Dumper;
6
7 use PVE::SafeSyslog;
8 use PVE::Tools qw(extract_param);
9 use HTTP::Status qw(:constants);
10 use Storable qw(dclone);
11 use PVE::JSONSchema qw(get_standard_option);
12 use PVE::RESTHandler;
13 use Time::HiRes qw();
14
15 use PMG::Config;
16 use PMG::API2::RuleDB;
17 use PMG::API2::LDAP;
18 use PMG::API2::Domains;
19 use PMG::API2::Transport;
20 use PMG::API2::Cluster;
21 use PMG::API2::MyNetworks;
22 use PMG::API2::SMTPWhitelist;
23 use PMG::API2::MimeTypes;
24 use PMG::API2::Fetchmail;
25 use PMG::API2::DestinationTLSPolicy;
26 use PMG::API2::InboundTLSDomains;
27 use PMG::API2::DKIMSign;
28 use PMG::API2::SACustom;
29 use PMG::API2::PBS::Remote;
30 use PMG::API2::ACME;
31 use PMG::API2::TFAConfig;
32
33 use base qw(PVE::RESTHandler);
34
35 my $section_type_enum = PMG::Config::Base->lookup_types();
36
37 __PACKAGE__->register_method ({
38 subclass => "PMG::API2::RuleDB",
39 path => 'ruledb',
40 });
41
42 __PACKAGE__->register_method ({
43 subclass => "PMG::API2::SMTPWhitelist",
44 path => 'whitelist',
45 });
46
47 __PACKAGE__->register_method ({
48 subclass => "PMG::API2::LDAP",
49 path => 'ldap',
50 });
51
52 __PACKAGE__->register_method ({
53 subclass => "PMG::API2::Domains",
54 path => 'domains',
55 });
56
57 __PACKAGE__->register_method ({
58 subclass => "PMG::API2::Fetchmail",
59 path => 'fetchmail',
60 });
61
62 __PACKAGE__->register_method ({
63 subclass => "PMG::API2::Transport",
64 path => 'transport',
65 });
66
67 __PACKAGE__->register_method ({
68 subclass => "PMG::API2::MyNetworks",
69 # set fragment delimiter (no subdirs) - we need that, because CIDRs
70 # contain a slash '/'
71 fragmentDelimiter => '',
72 path => 'mynetworks',
73 });
74
75 __PACKAGE__->register_method ({
76 subclass => "PMG::API2::Cluster",
77 path => 'cluster',
78 });
79
80 __PACKAGE__->register_method ({
81 subclass => "PMG::API2::MimeTypes",
82 path => 'mimetypes',
83 });
84
85 __PACKAGE__->register_method ({
86 subclass => "PMG::API2::DestinationTLSPolicy",
87 path => 'tlspolicy',
88 });
89
90 __PACKAGE__->register_method ({
91 subclass => "PMG::API2::InboundTLSDomains",
92 path => 'tlsinbounddomains',
93 });
94
95 __PACKAGE__->register_method({
96 subclass => "PMG::API2::DKIMSign",
97 path => 'dkim',
98 });
99
100 __PACKAGE__->register_method({
101 subclass => "PMG::API2::SACustom",
102 path => 'customscores',
103 });
104
105 __PACKAGE__->register_method ({
106 subclass => "PMG::API2::PBS::Remote",
107 path => 'pbs',
108 });
109
110 __PACKAGE__->register_method ({
111 subclass => "PMG::API2::ACME",
112 path => 'acme',
113 });
114
115 __PACKAGE__->register_method ({
116 subclass => "PMG::API2::TFAConfig",
117 path => 'tfa',
118 });
119
120 __PACKAGE__->register_method ({
121 name => 'index',
122 path => '',
123 method => 'GET',
124 description => "Directory index.",
125 parameters => {
126 additionalProperties => 0,
127 properties => {},
128 },
129 returns => {
130 type => 'array',
131 items => {
132 type => "object",
133 properties => { section => { type => 'string'} },
134 },
135 links => [ { rel => 'child', href => "{section}" } ],
136 },
137 code => sub {
138 my ($param) = @_;
139
140 my $res = [ map { { section => $_ } } $section_type_enum->@* ];
141
142 push @$res, { section => 'acme' };
143 push @$res, { section => 'cluster' };
144 push @$res, { section => 'dkim' };
145 push @$res, { section => 'domains' };
146 push @$res, { section => 'fetchmail' };
147 push @$res, { section => 'ldap' };
148 push @$res, { section => 'mimetypes' };
149 push @$res, { section => 'mynetworks' };
150 push @$res, { section => 'pbs' };
151 push @$res, { section => 'regextest' };
152 push @$res, { section => 'ruledb' };
153 push @$res, { section => 'tfa' };
154 push @$res, { section => 'tlspolicy' };
155 push @$res, { section => 'tlsinbounddomains' };
156 push @$res, { section => 'transport' };
157 push @$res, { section => 'users' };
158 push @$res, { section => 'whitelist' };
159
160 return $res;
161 }});
162
163 my $api_read_config_section = sub {
164 my ($section) = @_;
165
166 my $cfg = PMG::Config->new();
167
168 my $data = dclone($cfg->{ids}->{$section} // {});
169 $data->{digest} = $cfg->{digest};
170 delete $data->{type};
171
172 return $data;
173 };
174
175 my $api_update_config_section = sub {
176 my ($section, $param) = @_;
177
178 my $code = sub {
179 my $cfg = PMG::Config->new();
180 my $ids = $cfg->{ids};
181
182 my $digest = extract_param($param, 'digest');
183 PVE::SectionConfig::assert_if_modified($cfg, $digest);
184
185 my $delete_str = extract_param($param, 'delete');
186 die "no options specified\n"
187 if !$delete_str && !scalar(keys %$param);
188
189 foreach my $opt (PVE::Tools::split_list($delete_str)) {
190 delete $ids->{$section}->{$opt};
191 }
192
193 my $plugin = PMG::Config::Base->lookup($section);
194 my $config = $plugin->check_config($section, $param, 0, 1);
195
196 foreach my $p (keys %$config) {
197 $ids->{$section}->{$p} = $config->{$p};
198 }
199
200 $cfg->write();
201
202 $cfg->rewrite_config(undef, 1);
203 };
204
205 PMG::Config::lock_config($code, "update config section '$section' failed");
206 };
207
208 foreach my $section (@$section_type_enum) {
209
210 my $plugin = PMG::Config::Base->lookup($section);
211
212 __PACKAGE__->register_method ({
213 name => "read_${section}_section",
214 path => $section,
215 method => 'GET',
216 proxyto => 'master',
217 permissions => { check => [ 'admin', 'audit' ] },
218 description => "Read $section configuration properties.",
219 parameters => {
220 additionalProperties => 0,
221 properties => {},
222 },
223 returns => { type => 'object' },
224 code => sub {
225 my ($param) = @_;
226
227 return $api_read_config_section->($section);
228 }});
229
230 __PACKAGE__->register_method ({
231 name => "update_${section}_section",
232 path => $section,
233 method => 'PUT',
234 proxyto => 'master',
235 protected => 1,
236 permissions => { check => [ 'admin' ] },
237 description => "Update $section configuration properties.",
238 parameters => $plugin->updateSchema(1),
239 returns => { type => 'null' },
240 code => sub {
241 my ($param) = @_;
242
243 $api_update_config_section->($section, $param);
244
245 return undef;
246 }});
247 }
248
249 __PACKAGE__->register_method({
250 name => 'regextest',
251 path => 'regextest',
252 method => 'POST',
253 protected => 0,
254 permissions => { check => [ 'admin', 'qmanager', 'audit' ] },
255 description => "Test Regex ignoring case",
256 parameters => {
257 additionalProperties => 0,
258 properties => {
259 regex => {
260 type => 'string',
261 description => 'The Regex to test',
262 maxLength => 1024,
263 },
264 text => {
265 type => 'string',
266 description => 'The String to test',
267 maxLength => 1024,
268 }
269 },
270 },
271 returns => {
272 type => 'number',
273 },
274 code => sub {
275 my ($param) = @_;
276
277 my $text = $param->{text};
278 my $regex = $param->{regex};
279
280 my $regex_check = sub {
281 my $start_time = [Time::HiRes::gettimeofday];
282 my $match = 0;
283 if ($text =~ /$regex/i) {
284 $match = 1;
285 }
286 my $elapsed = Time::HiRes::tv_interval($start_time) * 1000;
287 die "The Regular Expression '$regex' did not match the text '$text' (elapsed time: $elapsed ms)\n"
288 if !$match;
289 return $elapsed;
290 };
291
292 my $elapsed = PVE::Tools::run_fork_with_timeout(2, $regex_check);
293 if ($elapsed eq '') {
294 die "The Regular Expression timed out\n";
295 }
296
297 return $elapsed;
298 }});
299
300 1;