1 |
95b003ff
|
Origo
|
#!/usr/bin/perl
|
2 |
|
|
|
3 |
|
|
# All rights reserved and Copyright (c) 2020 Origo Systems ApS.
|
4 |
|
|
# This file is provided with no warranty, and is subject to the terms and conditions defined in the license file LICENSE.md.
|
5 |
|
|
# The license file is part of this source code package and its content is also available at:
|
6 |
|
|
# https://www.origo.io/info/stabiledocs/licensing/stabile-open-source-license
|
7 |
|
|
|
8 |
|
|
# Clear up tainted environment
|
9 |
|
|
$ENV{PATH} = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
|
10 |
|
|
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
|
11 |
|
|
|
12 |
|
|
#use warnings FATAL => 'all';
|
13 |
|
|
use CGI::Carp qw(fatalsToBrowser);
|
14 |
|
|
use CGI qw(:standard);
|
15 |
|
|
use Getopt::Std;
|
16 |
|
|
use JSON;
|
17 |
|
|
use URI::Escape qw(uri_escape uri_unescape);
|
18 |
|
|
use Tie::DBI;
|
19 |
|
|
use Data::Dumper;
|
20 |
|
|
use Encode;
|
21 |
|
|
use Text::SimpleTable;
|
22 |
|
|
use ConfigReader::Simple;
|
23 |
|
|
use Sys::Syslog qw( :DEFAULT setlogsock);
|
24 |
|
|
use Digest::SHA qw(sha512_base64 sha512_hex);
|
25 |
|
|
use utf8;
|
26 |
|
|
use Hash::Merge qw( merge );
|
27 |
|
|
use Storable qw(freeze thaw);
|
28 |
|
|
use Gearman::Client;
|
29 |
|
|
use Proc::ProcessTable;
|
30 |
|
|
use HTTP::Async;
|
31 |
|
|
use HTTP::Request::Common;
|
32 |
|
|
use LWP::Simple qw(!head);
|
33 |
|
|
use Error::Simple;
|
34 |
|
|
|
35 |
|
|
our %options=();
|
36 |
|
|
# -a action -h help -f full list -p full update -u uuid -i image -m match pattern -k keywords -g args to gearman task
|
37 |
|
|
# -v verbose, include HTTP headers -s impersonate subaccount -t target [uuid or image] -c force console
|
38 |
|
|
Getopt::Std::getopts("a:hfpu:i:g:m:k:vs:t:c", \%options);
|
39 |
|
|
|
40 |
|
|
$Stabile::config = ConfigReader::Simple->new("/etc/stabile/config.cfg",
|
41 |
|
|
[qw(
|
42 |
|
|
AMT_PASSWD
|
43 |
|
|
DBI_PASSWD
|
44 |
|
|
DBI_USER
|
45 |
|
|
DO_DNS
|
46 |
|
|
DNS_DOMAIN
|
47 |
|
|
DO_XMPP
|
48 |
|
|
ENGINEID
|
49 |
|
|
ENGINENAME
|
50 |
|
|
ENGINE_DATA_NIC
|
51 |
|
|
ENGINE_LINKED
|
52 |
|
|
EXTERNAL_IP_RANGE_START
|
53 |
|
|
EXTERNAL_IP_RANGE_END
|
54 |
|
|
EXTERNAL_IP_QUOTA
|
55 |
|
|
EXTERNAL_NIC
|
56 |
|
|
EXTERNAL_SUBNET_SIZE
|
57 |
|
|
MEMORY_QUOTA
|
58 |
|
|
NODE_STORAGE_OVERCOMMISSION
|
59 |
|
|
NODESTORAGE_QUOTA
|
60 |
|
|
PROXY_GW
|
61 |
|
|
PROXY_IP
|
62 |
|
|
PROXY_IP_RANGE_END
|
63 |
|
|
PROXY_IP_RANGE_START
|
64 |
|
|
PROXY_SUBNET_SIZE
|
65 |
|
|
RDIFF-BACKUP_ENABLED
|
66 |
|
|
RDIFF-BACKUP_USERS
|
67 |
|
|
RX_QUOTA
|
68 |
|
|
SHOW_COST
|
69 |
|
|
STORAGE_BACKUPDIR
|
70 |
|
|
STORAGE_POOLS_ADDRESS_PATHS
|
71 |
|
|
STORAGE_POOLS_DEFAULTS
|
72 |
|
|
STORAGE_POOLS_LOCAL_PATHS
|
73 |
|
|
STORAGE_POOLS_NAMES
|
74 |
|
|
STORAGE_POOLS_RDIFF-BACKUP_ENABLED
|
75 |
|
|
STORAGE_QUOTA
|
76 |
|
|
Z_IMAGE_RETENTION
|
77 |
|
|
Z_BACKUP_RETENTION
|
78 |
|
|
TX_QUOTA
|
79 |
|
|
VCPU_QUOTA
|
80 |
|
|
VLAN_RANGE_START
|
81 |
|
|
VLAN_RANGE_END
|
82 |
|
|
VERSION
|
83 |
|
|
)]);
|
84 |
|
|
|
85 |
|
|
$dbiuser = $Stabile::config->get('DBI_USER') || "irigo";
|
86 |
|
|
$dbipasswd = $Stabile::config->get('DBI_PASSWD') || "";
|
87 |
|
|
$dnsdomain = $Stabile::config->get('DNS_DOMAIN') || "stabile.io";
|
88 |
2a63870a
|
Christian Orellana
|
$appstoreurl = $Stabile::config->get('APPSTORE_URL') || "https://www.origo.io/registry";
|
89 |
c899e439
|
Origo
|
$appstores = $Stabile::config->get('APPSTORES') || "stabile.io"; # Used for publishing apps
|
90 |
95b003ff
|
Origo
|
$engineuser = $Stabile::config->get('ENGINEUSER') || "";
|
91 |
|
|
$imageretention = $Stabile::config->get('Z_IMAGE_RETENTION') || "";
|
92 |
|
|
$backupretention = $Stabile::config->get('Z_BACKUP_RETENTION') || "";
|
93 |
|
|
$enginelinked = $Stabile::config->get('ENGINE_LINKED') || "";
|
94 |
|
|
$downloadmasters = $Stabile::config->get('DOWNLOAD_MASTERS') || "";
|
95 |
|
|
$disablesnat = $Stabile::config->get('DISABLE_SNAT') || "";
|
96 |
e9af6c24
|
Origo
|
our $engineid = $Stabile::config->get('ENGINEID') || "";
|
97 |
95b003ff
|
Origo
|
|
98 |
|
|
$Stabile::dbopts = {db=>'mysql:steamregister', key=>'uuid', autocommit=>0, CLOBBER=>2, user=>$dbiuser, password=>$dbipasswd};
|
99 |
|
|
$Stabile::auth_tkt_conf = "/etc/apache2/conf-available/auth_tkt_cgi.conf";
|
100 |
|
|
|
101 |
|
|
my $base = "/var/www/stabile";
|
102 |
|
|
$base = `cat /etc/stabile/basedir` if (-e "/etc/stabile/basedir");
|
103 |
|
|
chomp $base;
|
104 |
|
|
$base =~ /(.+)/; $base = $1; #untaint
|
105 |
|
|
$main::logfile = "/var/log/stabile/steam.log";
|
106 |
|
|
|
107 |
|
|
$current_time = time;
|
108 |
|
|
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($current_time);
|
109 |
|
|
$year += 1900;
|
110 |
|
|
$month = substr("0" . ($mon+1), -2);
|
111 |
|
|
$pretty_time = sprintf "%4d-%02d-%02d@%02d:%02d:%02d",$year,$mon+1,$mday,$hour,$min,$sec;
|
112 |
|
|
|
113 |
d24d9a01
|
hq
|
if ($ENV{'HTTP_HOST'} && !($ENV{'HTTP_HOST'} =~ /^10\./) && $ENV{'HTTP_HOST'} ne 'localhost' && !($ENV{'HTTP_HOST'} =~ /^127/)) {
|
114 |
|
|
$baseurl = "https://$ENV{'HTTP_HOST'}/stabile";
|
115 |
|
|
`echo "$baseurl" > /tmp/baseurl` if ((! -e "/tmp/baseurl") && $baseurl);
|
116 |
2a63870a
|
Christian Orellana
|
} else {
|
117 |
|
|
if (!$baseurl && (-e "/tmp/baseurl" || -e "/etc/stabile/baseurl")) {
|
118 |
|
|
if (-e "/etc/stabile/baseurl") {
|
119 |
|
|
$baseurl = `cat /etc/stabile/baseurl`;
|
120 |
|
|
} else {
|
121 |
|
|
$baseurl = `cat /tmp/baseurl`;
|
122 |
|
|
chomp $baseurl;
|
123 |
|
|
`echo "$baseurl" >/etc/stabile/baseurl` unless (-e "/etc/stabile/baseurl");
|
124 |
|
|
}
|
125 |
|
|
}
|
126 |
|
|
}
|
127 |
|
|
if (!$baseurl) {
|
128 |
|
|
my $hostname = `hostname`; chomp $hostname;
|
129 |
|
|
$baseurl = "https://$hostname/stabile";
|
130 |
|
|
}
|
131 |
95b003ff
|
Origo
|
$baseurl = $1 if ($baseurl =~ /(.+)/); #untaint
|
132 |
|
|
|
133 |
|
|
$Stabile::basedir = "/var/www/stabile";
|
134 |
|
|
$Stabile::basedir = `cat /etc/stabile/basedir` if -e "/etc/stabile/basedir";
|
135 |
|
|
chomp $Stabile::basedir;
|
136 |
|
|
$Stabile::basedir = $1 if ($Stabile::basedir =~ /(.+)/); #untaint
|
137 |
|
|
|
138 |
|
|
$package = substr(lc __PACKAGE__, length "Stabile::");
|
139 |
|
|
$programname = "Stabile";
|
140 |
|
|
|
141 |
|
|
$sshcmd = qq|ssh -l irigo -i /var/www/.ssh/id_rsa_www -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no|;
|
142 |
|
|
|
143 |
|
|
$ENV{'REQUEST_METHOD'} = $ENV{'REQUEST_METHOD'} || 'GET';
|
144 |
|
|
|
145 |
|
|
preInit();
|
146 |
|
|
1;
|
147 |
|
|
|
148 |
|
|
$main::syslogit = sub {
|
149 |
|
|
my ($user, $p, $msg) = @_;
|
150 |
|
|
my $priority = ($p eq 'syslog')?'info':$p;
|
151 |
|
|
|
152 |
|
|
$current_time = time;
|
153 |
|
|
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($current_time);
|
154 |
|
|
$year += 1900;
|
155 |
|
|
$month = substr("0" . ($mon+1), -2);
|
156 |
|
|
my $pretty_time = sprintf "%4d-%02d-%02d@%02d:%02d:%02d",$year,$mon+1,$mday,$hour,$min,$sec;
|
157 |
|
|
|
158 |
|
|
my $loguser = (!$tktuser || $tktuser eq $user)?"$user":"$user ($tktuser)";
|
159 |
|
|
if ($msg && $msg ne '') {
|
160 |
|
|
utf8::decode($msg);
|
161 |
|
|
unless (open(TEMP3, ">>$main::logfile")) {$posterror .= "Status=Error log file '$main::logfile' could not be written";}
|
162 |
|
|
$msg =~ /(.+)/; $msg = $1; #untaint
|
163 |
|
|
print TEMP3 $pretty_time, " : $loguser : $msg\n";
|
164 |
|
|
close(TEMP3);
|
165 |
|
|
}
|
166 |
|
|
return 0 unless ($priority =~ /err|debug/);
|
167 |
|
|
setlogsock('unix');
|
168 |
|
|
# $programname is assumed to be a global. Also log the PID
|
169 |
|
|
# and to CONSole if there's a problem. Use facility 'user'.
|
170 |
|
|
openlog($programname, 'pid,cons', 'user');
|
171 |
|
|
syslog($priority, "($loguser) $msg");
|
172 |
|
|
closelog();
|
173 |
|
|
return 1;
|
174 |
|
|
};
|
175 |
|
|
|
176 |
|
|
|
177 |
|
|
$main::postToOrigo = sub {
|
178 |
|
|
my ($engineid, $postaction, $postcontent, $postkey, $callback) = @_;
|
179 |
|
|
my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
|
180 |
|
|
my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
|
181 |
|
|
my $ret;
|
182 |
|
|
|
183 |
|
|
if ($tktkey && $engineid) {
|
184 |
|
|
my $browser = LWP::UserAgent->new;
|
185 |
|
|
$browser->timeout(15);
|
186 |
|
|
$browser->agent('pressurecontrol/1.0b');
|
187 |
|
|
$browser->protocols_allowed( [ 'http','https'] );
|
188 |
|
|
|
189 |
|
|
my $postreq;
|
190 |
|
|
$postreq->{'engineid'} = $engineid;
|
191 |
|
|
$postreq->{'enginetkthash'} = sha512_hex($tktkey) if ($enginelinked);
|
192 |
|
|
$postreq->{'appuser'} = $user;
|
193 |
|
|
$postreq->{'callback'} .= $callback if ($callback);
|
194 |
|
|
$postkey = 'POSTDATA' unless ($postkey);
|
195 |
|
|
$postreq->{$postkey} = $postcontent;
|
196 |
|
|
my $posturl = "https://www.origo.io/irigo/engine.cgi?action=$postaction";
|
197 |
|
|
my $content = $browser->post($posturl, $postreq)->content();
|
198 |
|
|
my $ok = ($content =~ /OK: (.*)/i);
|
199 |
|
|
$ret .= $content;
|
200 |
|
|
} else {
|
201 |
|
|
$main::syslogit->('pressurecontrol', 'info', "Unable to get engine tktkey...");
|
202 |
|
|
$ret .= "Unable to get engine tktkey...";
|
203 |
|
|
}
|
204 |
|
|
return $ret;
|
205 |
|
|
};
|
206 |
|
|
|
207 |
48fcda6b
|
Origo
|
$main::uploadToOrigo = sub {
|
208 |
|
|
my ($engineid, $filepath, $force) = @_;
|
209 |
|
|
my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
|
210 |
|
|
my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
|
211 |
|
|
my $ret;
|
212 |
|
|
|
213 |
|
|
if (!$filepath || !(-e $filepath)) {
|
214 |
|
|
$ret = "Status=Error Invalid file path\n";
|
215 |
|
|
} elsif ($tktkey && $engineid) {
|
216 |
2a63870a
|
Christian Orellana
|
$HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1;
|
217 |
48fcda6b
|
Origo
|
my $browser = LWP::UserAgent->new;
|
218 |
|
|
$browser->timeout(15 * 60); # 15 min
|
219 |
|
|
$browser->agent('pressurecontrol/1.0b');
|
220 |
|
|
$browser->protocols_allowed( [ 'http','https'] );
|
221 |
|
|
my $fname = $1 if ($filepath =~ /.*\/(.+\.qcow2)$/);
|
222 |
|
|
return "Status=Error Invalid file\n" unless ($fname);
|
223 |
|
|
my $posturl = "https://www.origo.io/irigo/engine.cgi?action=uploadimage";
|
224 |
2a63870a
|
Christian Orellana
|
|
225 |
|
|
# -- using ->post
|
226 |
|
|
# my $postreq = [
|
227 |
|
|
# 'file' => [ $filepath ],
|
228 |
|
|
# 'filename' => $fname,
|
229 |
|
|
# 'engineid' => $engineid,
|
230 |
|
|
# 'enginetkthash' => sha512_hex($tktkey),
|
231 |
|
|
# 'appuser' => $user,
|
232 |
|
|
# 'force' => $force
|
233 |
|
|
# ];
|
234 |
|
|
# my $content = $browser->post($posturl, $postreq, 'Content_Type' => 'form-data')->content;
|
235 |
|
|
# $ret .= $content;
|
236 |
|
|
|
237 |
|
|
# -- using ->request
|
238 |
|
|
my $req = POST $posturl,
|
239 |
|
|
Content_Type => 'form-data',
|
240 |
|
|
Content => [
|
241 |
|
|
'file' => [ $filepath ],
|
242 |
|
|
'filename' => $fname,
|
243 |
|
|
'engineid' => $engineid,
|
244 |
|
|
'enginetkthash' => sha512_hex($tktkey),
|
245 |
|
|
'appuser' => $user,
|
246 |
|
|
'force' => $force
|
247 |
|
|
];
|
248 |
|
|
my $total;
|
249 |
|
|
my $callback = $req->content;
|
250 |
|
|
if (ref($callback) eq "CODE") {
|
251 |
|
|
my $size = $req->header('content-length');
|
252 |
|
|
my $counter = 0;
|
253 |
|
|
my $progress = '';
|
254 |
|
|
$req->content(
|
255 |
|
|
sub {
|
256 |
|
|
my $chunk = $callback->();
|
257 |
|
|
if ($chunk) {
|
258 |
|
|
my $length = length $chunk;
|
259 |
|
|
$total += $length;
|
260 |
|
|
if ($total / $size * 100 > $counter) {
|
261 |
|
|
$counter = 1+ int $total / $size * 100;
|
262 |
|
|
$progress .= "#";
|
263 |
|
|
`echo "$progress$counter" >> /tmp/upload-$fname`;
|
264 |
|
|
}
|
265 |
|
|
# printf "%+5d = %5.1f%%\n", $length, $total / $size * 100;
|
266 |
|
|
# printf "%5.1f%%\n", $total / $size * 100;
|
267 |
|
|
|
268 |
|
|
} else {
|
269 |
|
|
# print "Done\n";
|
270 |
|
|
}
|
271 |
|
|
$chunk;
|
272 |
|
|
}
|
273 |
|
|
);
|
274 |
|
|
my $resp = $browser->request($req)->content();
|
275 |
|
|
$ret .= $resp;
|
276 |
|
|
$ret .= "Status=OK $progress\n";
|
277 |
|
|
} else {
|
278 |
|
|
$ret .= "Status=Error Did not get a callback";
|
279 |
|
|
}
|
280 |
48fcda6b
|
Origo
|
} else {
|
281 |
|
|
$ret .= "Status=Error Unable to get engine tktkey...";
|
282 |
|
|
}
|
283 |
|
|
return $ret;
|
284 |
|
|
};
|
285 |
|
|
|
286 |
95b003ff
|
Origo
|
$main::postAsyncToOrigo = sub {
|
287 |
|
|
my ($engineid, $postaction, $json_text) = @_;
|
288 |
|
|
my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
|
289 |
|
|
my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
|
290 |
|
|
my $ret;
|
291 |
|
|
|
292 |
|
|
if ($tktkey && $engineid) {
|
293 |
|
|
my $browser = LWP::UserAgent->new;
|
294 |
|
|
$browser->timeout(15);
|
295 |
|
|
$browser->agent('pressurecontrol/1.0b');
|
296 |
|
|
$browser->protocols_allowed( [ 'http','https'] );
|
297 |
|
|
|
298 |
|
|
$ret .= "Posting $postaction to origo.io\n";
|
299 |
|
|
|
300 |
|
|
my $postreq;
|
301 |
|
|
$postreq->{'engineid'} = $engineid;
|
302 |
|
|
$postreq->{'enginetkthash'} = sha512_hex($tktkey);
|
303 |
|
|
$postreq->{'POSTDATA'} = $json_text;
|
304 |
|
|
# my $content = $browser->post("https://www.origo.io/irigo/engine.cgi?action=$postaction", $postreq)->content();
|
305 |
|
|
# my $ok = ($content =~ /OK: (.*)/i);
|
306 |
|
|
# $ret .= $content;
|
307 |
|
|
|
308 |
|
|
my $async = HTTP::Async->new;
|
309 |
|
|
my $post = POST "https://www.origo.io/irigo/engine.cgi?action=$postaction",
|
310 |
|
|
[ engineid => $engineid,
|
311 |
|
|
enginetkthash => sha512_hex($tktkey),
|
312 |
|
|
POSTDATA => $json_text
|
313 |
|
|
];
|
314 |
|
|
$async->add( $post );
|
315 |
|
|
# while ( my $response = $async->wait_for_next_response ) {
|
316 |
|
|
# $ret .= $response->decoded_content;
|
317 |
|
|
# }
|
318 |
|
|
} else {
|
319 |
|
|
$main::syslogit->('pressurecontrol', 'info', "Unable to get engine tktkey...");
|
320 |
|
|
$ret .= "Unable to get engine tktkey...";
|
321 |
|
|
}
|
322 |
|
|
return $ret;
|
323 |
|
|
};
|
324 |
|
|
|
325 |
|
|
$main::dnsCreate = sub {
|
326 |
|
|
my ($engineid, $name, $value, $type, $username) = @_;
|
327 |
|
|
my $res;
|
328 |
e9af6c24
|
Origo
|
my $dnssubdomain = substr($engineid, 0, 8);
|
329 |
|
|
$type = uc $type;
|
330 |
|
|
$type || 'CNAME';
|
331 |
95b003ff
|
Origo
|
$name = $1 if ($name =~ /(.+)\.$dnsdomain/);
|
332 |
e9af6c24
|
Origo
|
# $name =$1 if ($name =~ /(.+)\.$dnssubdomain/);
|
333 |
|
|
if ($type eq 'A') { # Look for initial registrations and format correctly
|
334 |
|
|
if (!$name && $value) { # If no name provided assume we are creating initial A-record
|
335 |
|
|
$name = $value;
|
336 |
|
|
} elsif ($name =~ /^(\d+\.\d+\.\d+\.\d+)/) { # Looks like an IP address - must be same as value
|
337 |
|
|
if ($1 eq $value) { # Keep some order in registrations
|
338 |
|
|
$name = "$value.$dnssubdomain"; # The way we format initial registrations
|
339 |
|
|
} else {
|
340 |
|
|
$name = '';
|
341 |
|
|
}
|
342 |
|
|
}
|
343 |
|
|
}
|
344 |
95b003ff
|
Origo
|
# Only allow creation of records corresponding to user's own networks when username is supplied
|
345 |
|
|
# When username is not supplied, we assume checking has been done
|
346 |
|
|
if ($username) {
|
347 |
|
|
my $checkval = $value;
|
348 |
e9af6c24
|
Origo
|
# Remove any trailing period
|
349 |
95b003ff
|
Origo
|
$checkval = $1 if ($checkval =~ /(.+)\.$/);
|
350 |
6fdc8676
|
hq
|
if ($type eq 'TXT') {
|
351 |
|
|
$checkval = '';
|
352 |
|
|
} elsif ($type eq 'A') {
|
353 |
95b003ff
|
Origo
|
$checkval = $value;
|
354 |
|
|
} else {
|
355 |
e9af6c24
|
Origo
|
$checkval = $1 if ($checkval =~ /(\d+\.\d+\.\d+\.\d+)\.$dnssubdomain\.$dnsdomain$/);
|
356 |
95b003ff
|
Origo
|
$checkval = $1 if ($checkval =~ /(\d+\.\d+\.\d+\.\d+)\.$dnsdomain$/);
|
357 |
e9af6c24
|
Origo
|
$checkval = $1 if ($checkval =~ /(\d+\.\d+\.\d+\.\d+)$/);
|
358 |
95b003ff
|
Origo
|
}
|
359 |
|
|
if ($checkval) {
|
360 |
|
|
unless (tie %networkreg,'Tie::DBI', {
|
361 |
|
|
db=>'mysql:steamregister',
|
362 |
|
|
table=>'networks',
|
363 |
|
|
key=>'uuid',
|
364 |
|
|
autocommit=>0,
|
365 |
|
|
CLOBBER=>0,
|
366 |
|
|
user=>$dbiuser,
|
367 |
|
|
password=>$dbipasswd}) {throw Error::Simple("Error Register could not be accessed")};
|
368 |
|
|
my @regkeys = (tied %networkreg)->select_where("externalip = '$checkval'");
|
369 |
|
|
if (scalar @regkeys == 1) {
|
370 |
|
|
if ($register{$regkeys[0]}->{'user'} eq $username) {
|
371 |
|
|
; # OK
|
372 |
|
|
} else {
|
373 |
eb31fb38
|
hq
|
return qq|{"status": "Error", "message": "Invalid value $checkval, not allowed"}|;
|
374 |
95b003ff
|
Origo
|
}
|
375 |
|
|
} elsif (scalar @regkeys >1) {
|
376 |
eb31fb38
|
hq
|
return qq|{"status": "Error", "message": "Invalid value $checkval"}|;
|
377 |
95b003ff
|
Origo
|
}
|
378 |
|
|
untie %networkreg;
|
379 |
e9af6c24
|
Origo
|
if ($type eq 'A') {
|
380 |
6fdc8676
|
hq
|
# $name = "$checkval.$dnssubdomain"; # Only allow this type of A-records...?
|
381 |
e9af6c24
|
Origo
|
} else {
|
382 |
|
|
$value = "$checkval.$dnssubdomain";
|
383 |
|
|
}
|
384 |
95b003ff
|
Origo
|
}
|
385 |
|
|
}
|
386 |
|
|
|
387 |
6fdc8676
|
hq
|
if ($type ne 'MX' && $type ne 'TXT' && `host $name.$dnsdomain authns1.cabocomm.dk` =~ /has address/) {
|
388 |
eb31fb38
|
hq
|
return qq|{"status": "Error", "message": "$name is already registered"}|;
|
389 |
e9af6c24
|
Origo
|
};
|
390 |
|
|
|
391 |
95b003ff
|
Origo
|
if ($enginelinked && $name && $value) {
|
392 |
|
|
require LWP::Simple;
|
393 |
|
|
my $browser = LWP::UserAgent->new;
|
394 |
|
|
$browser->agent('Stabile/1.0b');
|
395 |
|
|
$browser->protocols_allowed( [ 'http','https'] );
|
396 |
|
|
$browser->timeout(10);
|
397 |
|
|
my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
|
398 |
|
|
my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
|
399 |
|
|
my $tkthash = sha512_hex($tktkey);
|
400 |
|
|
my $posturl = "https://www.origo.io/irigo/engine.cgi?action=dnscreate";
|
401 |
|
|
|
402 |
|
|
my $async = HTTP::Async->new;
|
403 |
|
|
my $post = POST $posturl,
|
404 |
6fdc8676
|
hq
|
[ engineid => $engineid,
|
405 |
95b003ff
|
Origo
|
enginetkthash => $tkthash,
|
406 |
6fdc8676
|
hq
|
name => $name,
|
407 |
|
|
domain => $dnsdomain,
|
408 |
|
|
value => $value,
|
409 |
|
|
type => $type,
|
410 |
|
|
username => $username || $user
|
411 |
95b003ff
|
Origo
|
];
|
412 |
|
|
# We fire this asynchronously and hope for the best. Waiting for an answer is just too erratic for now
|
413 |
|
|
$async->add( $post );
|
414 |
|
|
|
415 |
|
|
if ($username) {
|
416 |
|
|
my $response;
|
417 |
|
|
while ( $response = $async->wait_for_next_response ) {
|
418 |
|
|
$ret .= $response->decoded_content;
|
419 |
|
|
}
|
420 |
|
|
foreach my $line (split /\n/, $ret) {
|
421 |
|
|
$res .= $line unless ($line =~ /^\d/);
|
422 |
|
|
}
|
423 |
|
|
}
|
424 |
eb31fb38
|
hq
|
# $res =~ s/://g;
|
425 |
3657de20
|
Origo
|
return "$res\n";
|
426 |
95b003ff
|
Origo
|
|
427 |
|
|
} else {
|
428 |
eb31fb38
|
hq
|
return qq|{"status": "Error", "message": "Problem creating dns record with data $name, $value.| . ($enginelinked?"":" Engine is not linked!") . qq|"}|;
|
429 |
95b003ff
|
Origo
|
}
|
430 |
|
|
};
|
431 |
|
|
|
432 |
|
|
$main::dnsDelete = sub {
|
433 |
ca937547
|
hq
|
my ($engineid, $name, $value, $type, $username) = @_;
|
434 |
e9af6c24
|
Origo
|
my $dnssubdomain = substr($engineid, 0, 8);
|
435 |
afc024ef
|
hq
|
$name = $1 if ($name =~ /(.+)\.$dnsdomain$/);
|
436 |
|
|
# $name =$1 if ($name =~ /(.+)\.$dnssubdomain/);
|
437 |
ca937547
|
hq
|
if ($name =~ /^(\d+\.\d+\.\d+\.\d+)$/) {
|
438 |
|
|
$name = "$1.$dnssubdomain";
|
439 |
|
|
$type = $type || 'A';
|
440 |
95b003ff
|
Origo
|
}
|
441 |
|
|
|
442 |
ca937547
|
hq
|
$main::syslogit->($user, "info", "Deleting DNS entry $type $name $dnsdomain");
|
443 |
95b003ff
|
Origo
|
if ($enginelinked && $name) {
|
444 |
|
|
require LWP::Simple;
|
445 |
|
|
my $browser = LWP::UserAgent->new;
|
446 |
|
|
$browser->agent('Stabile/1.0b');
|
447 |
|
|
$browser->protocols_allowed( [ 'http','https'] );
|
448 |
|
|
my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
|
449 |
|
|
my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
|
450 |
|
|
my $tkthash = sha512_hex($tktkey);
|
451 |
|
|
my $posturl = "https://www.origo.io/irigo/engine.cgi?action=dnsdelete";
|
452 |
|
|
|
453 |
|
|
my $postreq = ();
|
454 |
|
|
$postreq->{'engineid'} = $engineid;
|
455 |
|
|
$postreq->{'enginetkthash'} = $tkthash;
|
456 |
|
|
$postreq->{'name'} = $name;
|
457 |
ca937547
|
hq
|
$postreq->{'value'} = $value;
|
458 |
|
|
$postreq->{'type'} = $type;
|
459 |
6fdc8676
|
hq
|
$postreq->{'username'} = $username || $user;
|
460 |
|
|
$postreq->{'domain'} = "$dnsdomain";
|
461 |
95b003ff
|
Origo
|
$content = $browser->post($posturl, $postreq)->content();
|
462 |
eb31fb38
|
hq
|
# $content =~ s/://g;
|
463 |
95b003ff
|
Origo
|
return $content;
|
464 |
|
|
} else {
|
465 |
|
|
return "ERROR Invalid data $name." . ($enginelinked?"":" Engine is not linked!") . "\n";
|
466 |
|
|
}
|
467 |
|
|
};
|
468 |
|
|
|
469 |
48fcda6b
|
Origo
|
$main::dnsUpdate = sub {
|
470 |
eb31fb38
|
hq
|
my ($engineid, $name, $value, $type, $oldname, $oldvalue, $username) = @_;
|
471 |
48fcda6b
|
Origo
|
$name = $1 if ($name =~ /(.+)\.$dnsdomain/);
|
472 |
eb31fb38
|
hq
|
$type = uc $type;
|
473 |
|
|
$type || 'CNAME';
|
474 |
48fcda6b
|
Origo
|
|
475 |
|
|
# Only allow deletion of records corresponding to user's own networks when username is supplied
|
476 |
|
|
# When username is not supplied, we assume checking has been done
|
477 |
eb31fb38
|
hq
|
# Obsolete
|
478 |
|
|
# my $checkval;
|
479 |
|
|
# if ($username) {
|
480 |
|
|
# if ($name =~ /\d+\.\d+\.\d+\.\d+/) {
|
481 |
|
|
# $checkval = $name;
|
482 |
|
|
# } else {
|
483 |
|
|
# my $checkname = $name;
|
484 |
|
|
# # Remove trailing period
|
485 |
|
|
# $checkname = $1 if ($checkname =~ /(.+)\.$/);
|
486 |
|
|
# $checkname = "$checkname.$dnsdomain" unless ($checkname =~ /(.+)\.$dnsdomain$/);
|
487 |
|
|
# $checkval = $1 if (`host $checkname authns1.cabocomm.dk` =~ /has address (\d+\.\d+\.\d+\.\d+)/);
|
488 |
|
|
# return "ERROR Invalid value $checkname\n" unless ($checkval);
|
489 |
|
|
# }
|
490 |
|
|
#
|
491 |
|
|
# unless (tie %networkreg,'Tie::DBI', {
|
492 |
|
|
# db=>'mysql:steamregister',
|
493 |
|
|
# table=>'networks',
|
494 |
|
|
# key=>'uuid',
|
495 |
|
|
# autocommit=>0,
|
496 |
|
|
# CLOBBER=>0,
|
497 |
|
|
# user=>$dbiuser,
|
498 |
|
|
# password=>$dbipasswd}) {throw Error::Simple("Error Register could not be accessed")};
|
499 |
|
|
# my @regkeys = (tied %networkreg)->select_where("externalip = '$checkval' OR internalip = '$checkval'");
|
500 |
|
|
# if ($isadmin || (scalar @regkeys == 1 && $register{$regkeys[0]}->{'user'} eq $username)) {
|
501 |
|
|
# ; # OK
|
502 |
|
|
# } else {
|
503 |
|
|
# return "ERROR Invalid user for $checkval, not allowed\n";
|
504 |
|
|
# }
|
505 |
|
|
# untie %networkreg;
|
506 |
|
|
# }
|
507 |
48fcda6b
|
Origo
|
|
508 |
|
|
$main::syslogit->($user, "info", "Updating DNS entries for $name $dnsdomain");
|
509 |
|
|
if ($enginelinked && $name) {
|
510 |
|
|
require LWP::Simple;
|
511 |
|
|
my $browser = LWP::UserAgent->new;
|
512 |
|
|
$browser->agent('Stabile/1.0b');
|
513 |
|
|
$browser->protocols_allowed( [ 'http','https'] );
|
514 |
|
|
my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
|
515 |
|
|
my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
|
516 |
|
|
my $tkthash = sha512_hex($tktkey);
|
517 |
|
|
my $posturl = "https://www.origo.io/irigo/engine.cgi?action=dnsupdate";
|
518 |
|
|
|
519 |
|
|
my $postreq = ();
|
520 |
|
|
$postreq->{'engineid'} = $engineid;
|
521 |
|
|
$postreq->{'enginetkthash'} = $tkthash;
|
522 |
|
|
$postreq->{'name'} = $name;
|
523 |
eb31fb38
|
hq
|
$postreq->{'value'} = $value;
|
524 |
|
|
$postreq->{'type'} = $type;
|
525 |
|
|
$postreq->{'oldname'} = $oldname if ($oldname);
|
526 |
|
|
$postreq->{'oldvalue'} = $oldvalue if ($oldvalue);
|
527 |
6fdc8676
|
hq
|
$postreq->{'username'} = $username || $user;
|
528 |
48fcda6b
|
Origo
|
$postreq->{'domain'} = $dnsdomain;
|
529 |
|
|
$content = $browser->post($posturl, $postreq)->content();
|
530 |
|
|
return $content;
|
531 |
|
|
} else {
|
532 |
|
|
return "ERROR Invalid data $name." . ($enginelinked?"":" Engine is not linked!") . "\n";
|
533 |
|
|
}
|
534 |
|
|
};
|
535 |
|
|
|
536 |
e9af6c24
|
Origo
|
$main::dnsList = sub {
|
537 |
eb31fb38
|
hq
|
my ($engineid, $username, $domain) = @_;
|
538 |
e9af6c24
|
Origo
|
if ($enginelinked) {
|
539 |
|
|
require LWP::Simple;
|
540 |
|
|
my $browser = LWP::UserAgent->new;
|
541 |
|
|
$browser->agent('Stabile/1.0b');
|
542 |
|
|
$browser->protocols_allowed( [ 'http','https'] );
|
543 |
|
|
my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
|
544 |
|
|
my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
|
545 |
|
|
my $tkthash = sha512_hex($tktkey);
|
546 |
|
|
my $posturl = "https://www.origo.io/irigo/engine.cgi?action=dnslist";
|
547 |
eb31fb38
|
hq
|
$domain = $domain || $dnsdomain;
|
548 |
e9af6c24
|
Origo
|
|
549 |
|
|
my $postreq = ();
|
550 |
|
|
$postreq->{'engineid'} = $engineid;
|
551 |
|
|
$postreq->{'enginetkthash'} = $tkthash;
|
552 |
eb31fb38
|
hq
|
$postreq->{'domain'} = $domain;
|
553 |
6fdc8676
|
hq
|
$postreq->{'username'} = $username || $user;
|
554 |
e9af6c24
|
Origo
|
$content = $browser->post($posturl, $postreq)->content();
|
555 |
eb31fb38
|
hq
|
# $content =~ s/://g;
|
556 |
e9af6c24
|
Origo
|
return $content;
|
557 |
|
|
} else {
|
558 |
|
|
return "ERROR Engine is not linked!\n";
|
559 |
|
|
}
|
560 |
|
|
};
|
561 |
|
|
|
562 |
|
|
$main::dnsClean = sub {
|
563 |
|
|
my ($engineid, $username) = @_;
|
564 |
|
|
if ($enginelinked) {
|
565 |
|
|
require LWP::Simple;
|
566 |
|
|
my $browser = LWP::UserAgent->new;
|
567 |
|
|
$browser->agent('Stabile/1.0b');
|
568 |
|
|
$browser->protocols_allowed( [ 'http','https'] );
|
569 |
|
|
my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
|
570 |
|
|
my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
|
571 |
|
|
my $tkthash = sha512_hex($tktkey);
|
572 |
|
|
my $posturl = "https://www.origo.io/irigo/engine.cgi?action=dnsclean";
|
573 |
|
|
my $postreq = ();
|
574 |
|
|
$postreq->{'engineid'} = $engineid;
|
575 |
|
|
$postreq->{'enginetkthash'} = $tkthash;
|
576 |
|
|
$postreq->{'domain'} = $dnsdomain;
|
577 |
|
|
$content = $browser->post($posturl, $postreq)->content();
|
578 |
|
|
$content =~ s/://g;
|
579 |
|
|
return $content;
|
580 |
|
|
} else {
|
581 |
|
|
return "ERROR Engine is not linked!\n";
|
582 |
|
|
}
|
583 |
|
|
};
|
584 |
|
|
|
585 |
95b003ff
|
Origo
|
$main::xmppSend = sub {
|
586 |
|
|
my ($to, $msg, $engineid, $sysuuid) = @_;
|
587 |
|
|
$engineid = `cat /etc/stabile/config.cfg | sed -n -e 's/^ENGINEID=//p'` unless ($engineid);
|
588 |
|
|
my $doxmpp = `cat /etc/stabile/config.cfg | sed -n -e 's/^DO_XMPP=//p'`;
|
589 |
|
|
if (!$doxmpp) {
|
590 |
|
|
return "INFO: DO_XMPP not enabled in config\n";
|
591 |
|
|
|
592 |
|
|
} elsif ($to && $msg) {
|
593 |
|
|
my $xdom;
|
594 |
|
|
$xdom = $1 if ($to =~ /\@(.+)$/);
|
595 |
|
|
if ($xdom && `host -t SRV _xmpp-server._tcp.$xdom` !~ /NXDOMAIN/) {
|
596 |
|
|
require LWP::Simple;
|
597 |
|
|
my $browser = LWP::UserAgent->new;
|
598 |
|
|
$browser->agent('Stabile/1.0b');
|
599 |
|
|
$browser->protocols_allowed( [ 'http','https'] );
|
600 |
|
|
$browser->timeout(10);
|
601 |
|
|
my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
|
602 |
|
|
my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
|
603 |
|
|
my $tkthash = sha512_hex($tktkey);
|
604 |
|
|
my $posturl = "https://www.origo.io/irigo/engine.cgi?action=xmppsend";
|
605 |
|
|
|
606 |
|
|
my $async = HTTP::Async->new;
|
607 |
|
|
my $post = POST $posturl,
|
608 |
|
|
[ engineid => $engineid,
|
609 |
|
|
enginetkthash => $tkthash,
|
610 |
|
|
sysuuid => $sysuuid,
|
611 |
|
|
to => $to,
|
612 |
|
|
msg => $msg
|
613 |
|
|
];
|
614 |
|
|
$async->add( $post );
|
615 |
|
|
|
616 |
|
|
#my $postreq = ();
|
617 |
|
|
#$postreq->{'engineid'} = $engineid;
|
618 |
|
|
#$postreq->{'enginetkthash'} = $tkthash;
|
619 |
|
|
#$postreq->{'to'} = $to;
|
620 |
|
|
#$postreq->{'msg'} = $msg;
|
621 |
|
|
#$content = $browser->post($posturl, $postreq)->content();
|
622 |
|
|
|
623 |
|
|
return "Status=OK Sent xmpp message to $to\n";
|
624 |
|
|
} else {
|
625 |
|
|
return "Status=ERROR XMPP srv records not found for domain \"$xdom\"\n";
|
626 |
|
|
}
|
627 |
|
|
|
628 |
|
|
} else {
|
629 |
|
|
return "Status=ERROR Invalid xmpp data $to, $msg\n";
|
630 |
|
|
}
|
631 |
|
|
};
|
632 |
|
|
|
633 |
2a63870a
|
Christian Orellana
|
# Enumerate and return network interfaces
|
634 |
|
|
$main::getNics = sub {
|
635 |
|
|
my $internalnic = $Stabile::config->get('ENGINE_DATA_NIC');
|
636 |
|
|
my $externalnic = $Stabile::config->get('EXTERNAL_NIC');
|
637 |
|
|
if (!$externalnic) {
|
638 |
|
|
my $droute = `ip route show default`;
|
639 |
|
|
$externalnic = $1 if ($droute =~ /default via .+ dev (.+) proto/);
|
640 |
|
|
}
|
641 |
|
|
my @nics = ();
|
642 |
|
|
if (!$externalnic || !$internalnic) {
|
643 |
|
|
my $niclist = `ifconfig | grep flags= | sed -n -e 's/: .*//p'`;
|
644 |
|
|
if (-e "/mnt/stabile/tftp/bionic") { # If a piston root exists, assume we will be providing boot services over secondary NIC even if it has no link
|
645 |
|
|
$niclist = `ifconfig -a | grep flags= | sed -n -e 's/: .*//p'`;
|
646 |
|
|
}
|
647 |
|
|
# my $niclist = `netstat -in`;
|
648 |
|
|
push @nics, $externalnic if ($externalnic);
|
649 |
|
|
foreach my $line (split("\n", $niclist)) {
|
650 |
|
|
if ($line =~ /^(\w+)$/) {
|
651 |
|
|
my $nic = $1;
|
652 |
|
|
push(@nics, $nic) if ($nic ne 'lo' && $nic ne $externalnic && !($nic=~/^virbr/) && !($nic=~/^docker/) && !($nic=~/^br/) && !($nic=~/^vnet/) && !($nic=~/^Name/) && !($nic=~/^Kernel/) && !($nic=~/^Iface/) && !($nic=~/(\.|\:)/));
|
653 |
|
|
}
|
654 |
|
|
}
|
655 |
|
|
}
|
656 |
|
|
$externalnic = $externalnic || $nics[0];
|
657 |
|
|
$internalnic = $internalnic || $nics[1] || $externalnic;
|
658 |
|
|
return ($internalnic, $externalnic);
|
659 |
|
|
};
|
660 |
|
|
|
661 |
95b003ff
|
Origo
|
$main::updateUI = sub {
|
662 |
|
|
my @parslist = @_;
|
663 |
|
|
my $newtasks;
|
664 |
|
|
my $tab;
|
665 |
|
|
my $duser;
|
666 |
|
|
foreach my $pars (@parslist) {
|
667 |
|
|
my $type = $pars->{type};
|
668 |
|
|
my $duuid = $pars->{uuid};
|
669 |
|
|
my $domuuid = $pars->{domuuid};
|
670 |
|
|
my $dstatus = $pars->{status};
|
671 |
|
|
my $message = $pars->{message};
|
672 |
48fcda6b
|
Origo
|
$message =~ s/"/\\"/g;
|
673 |
|
|
$message =~ s/'/\\'/g;
|
674 |
95b003ff
|
Origo
|
my $newpath = $pars->{newpath};
|
675 |
|
|
my $displayip = $pars->{displayip};
|
676 |
|
|
my $displayport = $pars->{displayport};
|
677 |
|
|
my $name = $pars->{name};
|
678 |
|
|
my $master = $pars->{master};
|
679 |
|
|
my $mac = $pars->{mac};
|
680 |
|
|
my $macname = $pars->{macname};
|
681 |
|
|
my $progress = $pars->{progress};
|
682 |
|
|
my $title = $pars->{title};
|
683 |
|
|
my $managementlink = $pars->{managementlink};
|
684 |
|
|
my $backup = $pars->{backup};
|
685 |
2a63870a
|
Christian Orellana
|
my $download = $pars->{download};
|
686 |
|
|
my $size = $pars->{size};
|
687 |
95b003ff
|
Origo
|
my $sender = $pars->{sender};
|
688 |
|
|
my $path = $pars->{path};
|
689 |
|
|
my $snap1 = $pars->{snap1};
|
690 |
|
|
my $username = $pars->{username};
|
691 |
|
|
|
692 |
|
|
$tab = $pars->{tab};
|
693 |
|
|
$duser = $pars->{user};
|
694 |
|
|
$duser = "irigo" if ($duser eq "--");
|
695 |
|
|
$tab = $tab || substr(lc __PACKAGE__, 9);
|
696 |
|
|
$type = $type || ($message?'message':'update');
|
697 |
|
|
$sender = $sender || "stabile:$package";
|
698 |
|
|
|
699 |
|
|
if ($package eq 'users' && $pars->{'uuid'}) {
|
700 |
|
|
my %u = %{$register{$pars->{'uuid'}}};
|
701 |
|
|
delete $u{'password'};
|
702 |
|
|
$u{'user'} = $duser;
|
703 |
|
|
$u{'type'} = 'update';
|
704 |
|
|
$u{'status'} = ($u{'privileges'} =~ /d/)?'disabled':'enabled';
|
705 |
|
|
$u{'tab'} = $package;
|
706 |
|
|
$u{'timestamp'} = $current_time;
|
707 |
|
|
$newtasks .= to_json(\%u) . ", ";
|
708 |
|
|
} else {
|
709 |
|
|
$newtasks .= "{\"type\":\"$type\",\"tab\":\"$tab\",\"timestamp\":$current_time" .
|
710 |
|
|
($duuid?",\"uuid\":\"$duuid\"":"") .
|
711 |
|
|
($domuuid?",\"domuuid\":\"$domuuid\"":"") .
|
712 |
|
|
($duser?",\"user\":\"$duser\"":"") .
|
713 |
|
|
($dstatus?",\"status\":\"$dstatus\"":"") .
|
714 |
|
|
($message?",\"message\":\"$message\"":"") .
|
715 |
|
|
($newpath?",\"path\":\"$newpath\"":"") .
|
716 |
|
|
($displayip?",\"displayip\":\"$displayip\"":"") .
|
717 |
|
|
($displayport?",\"displayport\":\"$displayport\"":"") .
|
718 |
|
|
($name?",\"name\":\"$name\"":"") .
|
719 |
|
|
($backup?",\"backup\":\"$backup\"":"") .
|
720 |
2a63870a
|
Christian Orellana
|
($download?",\"download\":\"$download\"":"") .
|
721 |
|
|
($size?",\"size\":\"$size\"":"") .
|
722 |
95b003ff
|
Origo
|
($mac?",\"mac\":\"$mac\"":"") .
|
723 |
|
|
($macname?",\"macname\":\"$macname\"":"") .
|
724 |
|
|
($progress?",\"progress\":$progress":"") . # This must be a number between 0 and 100
|
725 |
|
|
($title?",\"title\":\"$title\"":"") .
|
726 |
|
|
($managementlink?",\"managementlink\":\"$managementlink\"":"") .
|
727 |
|
|
($master?",\"master\":\"$master\"":"") .
|
728 |
|
|
($snap1?",\"snap1\":\"$snap1\"":"") .
|
729 |
|
|
($username?",\"username\":\"$username\"":"") .
|
730 |
48fcda6b
|
Origo
|
($path?",\"path\":\"$path\"":"") .
|
731 |
|
|
",\"sender\":\"$sender\"}, ";
|
732 |
95b003ff
|
Origo
|
}
|
733 |
|
|
}
|
734 |
|
|
$newtasks = $1 if ($newtasks =~ /(.+)/); #untaint
|
735 |
|
|
my $res;
|
736 |
|
|
eval {
|
737 |
|
|
opendir my($dh), '/tmp' or die "Couldn't open '/tmp': $!";
|
738 |
|
|
my @files;
|
739 |
|
|
if ($tab eq 'nodes' || $duser eq 'irigo') {
|
740 |
|
|
# write tasks to all admin user's session task pipes
|
741 |
|
|
@files = grep { /.*~A-.*\.tasks$/ } readdir $dh;
|
742 |
|
|
} else {
|
743 |
|
|
# write tasks to all the user's session task pipes
|
744 |
|
|
@files = grep { /^$duser~.*\.tasks$/ } readdir $dh;
|
745 |
|
|
}
|
746 |
|
|
closedir $dh;
|
747 |
|
|
my $t = new Proc::ProcessTable;
|
748 |
|
|
my @ptable = @{$t->table};
|
749 |
|
|
my @pfiles;
|
750 |
|
|
my $cmnds;
|
751 |
|
|
foreach my $f (@files) {
|
752 |
|
|
# my $n = `pgrep -fc "$f"`;
|
753 |
|
|
# chomp $n;
|
754 |
|
|
foreach my $p ( @ptable ){
|
755 |
|
|
my $pcmd = $p->cmndline;
|
756 |
|
|
$cmnds .= $pcmd . "\n" if ($pcmd =~ /tmp/);
|
757 |
|
|
if ($pcmd =~ /\/tmp\/$f/) { # Only include pipes with active listeners
|
758 |
|
|
push @pfiles, "/tmp/$f";
|
759 |
|
|
last;
|
760 |
|
|
}
|
761 |
|
|
}
|
762 |
|
|
};
|
763 |
|
|
my $tasksfiles = join(' ', @pfiles);
|
764 |
|
|
$tasksfiles = $1 if ($tasksfiles =~ /(.+)/); #untaint
|
765 |
|
|
# Write to users named pipes if user is logged in and session file found
|
766 |
|
|
if ($tasksfiles) {
|
767 |
|
|
$res = `/bin/echo \'$newtasks\' | /usr/bin/tee $tasksfiles \&`;
|
768 |
|
|
} else {
|
769 |
|
|
# If session file not found, append to orphan tasks file wait a sec and reload
|
770 |
|
|
$res = `/bin/echo \'$newtasks\' >> /tmp/$duser.tasks`;
|
771 |
|
|
$res .= `chown www-data:www-data /tmp/$duser.tasks`;
|
772 |
|
|
# sleep 1;
|
773 |
|
|
eval {`/usr/bin/pkill -HUP -f ui_update`; 1;} or do {;};
|
774 |
ca937547
|
hq
|
# `echo "duh: $duser" >> /tmp/duh`;
|
775 |
95b003ff
|
Origo
|
}
|
776 |
|
|
# eval {`/usr/bin/pkill -HUP -f $duser~ui_update`; 1;} or do {;};
|
777 |
|
|
} or do {$e=1; $res .= "ERROR Problem writing to tasks pipe $@\n";};
|
778 |
|
|
return 1;
|
779 |
|
|
};
|
780 |
|
|
|
781 |
|
|
sub action {
|
782 |
|
|
my ($target, $action, $obj) = @_;
|
783 |
|
|
my $res;
|
784 |
|
|
my $func = ucfirst $action;
|
785 |
|
|
# If a function named $action (with first letter uppercased) exists, call it and return the result
|
786 |
|
|
if (defined &{$func}) {
|
787 |
|
|
$res .= &{$func}($target, $action, $obj);
|
788 |
|
|
}
|
789 |
|
|
return $res;
|
790 |
|
|
}
|
791 |
|
|
|
792 |
|
|
sub privileged_action {
|
793 |
|
|
my ($target, $action, $obj) = @_;
|
794 |
|
|
return "Status=ERROR Your account does not have the necessary privileges\n" if ($isreadonly);
|
795 |
|
|
return action($target, $action) if ($help);
|
796 |
|
|
my $res;
|
797 |
|
|
$obj = {} unless ($obj);
|
798 |
|
|
$obj->{'console'} = 1 if ($console || $options{c});
|
799 |
2a63870a
|
Christian Orellana
|
$obj->{'baseurl'} = $baseurl if ($baseurl);
|
800 |
95b003ff
|
Origo
|
my $client = Gearman::Client->new;
|
801 |
|
|
$client->job_servers('127.0.0.1:4730');
|
802 |
|
|
# Gearman server will try to call a method named "do_gear_$action"
|
803 |
|
|
$res = $client->do_task(steamexec => freeze({package=>$package, tktuser=>$tktuser, user=>$user, target=>$target, action=>$action, args=>$obj}));
|
804 |
|
|
$res = ${ $res };
|
805 |
|
|
return $res;
|
806 |
|
|
}
|
807 |
|
|
|
808 |
|
|
sub privileged_action_async {
|
809 |
|
|
my ($target, $action, $obj) = @_;
|
810 |
|
|
return "Status=ERROR Your account does not have the necessary privileges\n" if ($isreadonly);
|
811 |
|
|
return action($target, $action) if ($help);
|
812 |
|
|
my $client = Gearman::Client->new;
|
813 |
|
|
$client->job_servers('127.0.0.1:4730');
|
814 |
|
|
my $tasks = $client->new_task_set;
|
815 |
|
|
$obj = {} unless ($obj);
|
816 |
|
|
$obj->{'console'} = 1 if ($console || $options{c});
|
817 |
|
|
# Gearman server will try to call a method named "do_gear_$action"
|
818 |
|
|
if (scalar(keys %{$obj}) > 2 ) {
|
819 |
|
|
my $handle = $tasks->add_task(steamexec => freeze({package=>$package, tktuser=>$tktuser, user=>$user, target=>$target, action=>$action, args=>$obj}));
|
820 |
|
|
} else {
|
821 |
|
|
my $handle = $tasks->add_task(steamexec => freeze({package=>$package, tktuser=>$tktuser, user=>$user, target=>$target, action=>$action}));
|
822 |
|
|
}
|
823 |
|
|
my $regtarget = $register{$target};
|
824 |
|
|
my $imgregtarget = $imagereg{$target};
|
825 |
d24d9a01
|
hq
|
$uistatus = $regtarget->{status} || "$action".'ing';
|
826 |
95b003ff
|
Origo
|
$uistatus = 'cloning' if ($action eq 'clone');
|
827 |
|
|
$uistatus = 'snapshotting' if ($action eq 'snapshot');
|
828 |
|
|
$uistatus = 'unsnapping' if ($action eq 'unsnap');
|
829 |
|
|
$uistatus = 'mastering' if ($action eq 'master');
|
830 |
|
|
$uistatus = 'unmastering' if ($action eq 'unmaster');
|
831 |
|
|
$uistatus = 'backingup' if ($action eq 'backup');
|
832 |
|
|
$uistatus = 'restoring' if ($action eq 'restore');
|
833 |
|
|
$uistatus = 'saving' if ($action eq 'save');
|
834 |
|
|
$uistatus = 'venting' if ($action eq 'releasepressure');
|
835 |
|
|
my $name = $regtarget->{name} || $imgregtarget->{name};
|
836 |
|
|
if ($action eq 'save') {
|
837 |
|
|
if ($package eq 'images') {
|
838 |
|
|
if ($obj->{status} eq 'new') {
|
839 |
|
|
$obj->{status} = 'unused';
|
840 |
|
|
}
|
841 |
|
|
elsif ($obj->{regstoragepool} ne $obj->{storagepool}) {
|
842 |
d24d9a01
|
hq
|
$obj->{'status'} = $uistatus = 'moving';
|
843 |
95b003ff
|
Origo
|
}
|
844 |
|
|
}
|
845 |
|
|
$postreply = to_json($obj, {pretty=>1});
|
846 |
|
|
$postreply = encode('utf8', $postreply);
|
847 |
|
|
$postreply =~ s/""/"--"/g;
|
848 |
|
|
$postreply =~ s/null/"--"/g;
|
849 |
|
|
$postreply =~ s/"notes" {0,1}: {0,1}"--"/"notes":""/g;
|
850 |
|
|
$postreply =~ s/"installable" {0,1}: {0,1}"(true|false)"/"installable":$1/g;
|
851 |
|
|
return $postreply;
|
852 |
|
|
} else {
|
853 |
|
|
return "Status=$uistatus OK $action $name (deferred)\n";
|
854 |
|
|
}
|
855 |
|
|
}
|
856 |
|
|
|
857 |
|
|
sub do_gear_action {
|
858 |
|
|
my ($target, $action ,$obj) = @_;
|
859 |
|
|
$target = encode("iso-8859-1", $target); # MySQL uses Latin1 as default charset
|
860 |
|
|
$action = $1 if ($action =~ /gear_(.+)/);
|
861 |
|
|
my $res;
|
862 |
|
|
return "This only works with elevated privileges\n" if ($>);
|
863 |
9d03439e
|
hq
|
if ($register{$target}
|
864 |
|
|
|| $action =~ /all$|save|^monitors|^packages|^changemonitoremail|^buildsystem|^removesystem|^updateaccountinfo|^updateengineinfo|^removeusersystems|^removeuserimages/
|
865 |
|
|
|| $action =~ /^updateamtinfo|^updatedownloads|^releasepressure|linkmaster$|activate$|engine$|^syncusers|^deletesystem|^getserverbackups|^listserverbackups|^fullstats/
|
866 |
14fd7cc5
|
hq
|
|| $action =~ /^zbackup|^updateallbtimes|^initializestorage|^liststoragedevices|^getbackupdevice|^getimagesdevice|^listbackupdevices|^listimagesdevices/
|
867 |
|
|
|| $action =~ /^setstoragedevice|^updateui|configurecgroups|backup|sync_backup/
|
868 |
95b003ff
|
Origo
|
|| ($action eq 'remove' && $package eq 'images' && $target =~ /\.master\.qcow2$/) # We allow removing master images by name only
|
869 |
|
|
) {
|
870 |
|
|
my $func = ucfirst $action;
|
871 |
|
|
# If a function named $action (with first letter uppercased) exists, call it and return the result
|
872 |
|
|
if (defined &{$func}) {
|
873 |
|
|
if ($obj) {
|
874 |
|
|
$console = $obj->{'console'} if ($obj->{'console'});
|
875 |
|
|
$target = $obj->{uuid} if (!$target && $obj->{uuid}); # backwards compat with apps calling removesystem
|
876 |
|
|
$res .= &{$func}($target, $action, $obj);
|
877 |
|
|
} else {
|
878 |
|
|
$res .= &{$func}($target, $action);
|
879 |
|
|
}
|
880 |
|
|
} else {
|
881 |
|
|
$res .= "Status=ERROR Unable to $action $target - function not found in $package\n";
|
882 |
|
|
}
|
883 |
|
|
} else {
|
884 |
|
|
$res .= "Status=ERROR Unable to $action $target - target not found in $package\n";
|
885 |
|
|
}
|
886 |
|
|
return $res;
|
887 |
|
|
}
|
888 |
|
|
|
889 |
|
|
sub preInit {
|
890 |
|
|
# Set global vars: $user, $tktuser, $curuuid and if applicable: $curdomuuid, $cursysuuid, $curimg
|
891 |
|
|
# Identify and validate user, read user prefs from DB
|
892 |
48fcda6b
|
Origo
|
unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username'}, $Stabile::dbopts)) ) {throw Error::Simple("Status=Error User register could not be accessed")};
|
893 |
95b003ff
|
Origo
|
|
894 |
|
|
$user = $user || $Stabile::user || $ENV{'REMOTE_USER'};
|
895 |
|
|
$user = 'irigo' if ($package eq 'steamexec');
|
896 |
|
|
$remoteip = $ENV{'REMOTE_ADDR'};
|
897 |
|
|
# If request is coming from a running server from an internal ip, identify user requesting access
|
898 |
|
|
if (!$user && $remoteip && $remoteip =~ /^10\.\d+\.\d+\.\d+/) {
|
899 |
48fcda6b
|
Origo
|
unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks', CLOBBER=>3}, $Stabile::dbopts)) ) {throw Error::Simple("Status=Error Network register could not be accessed")};
|
900 |
|
|
unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains', CLOBBER=>3}, $Stabile::dbopts)) ) {throw Error::Simple("Status=Error Domain register could not be accessed")};
|
901 |
95b003ff
|
Origo
|
my @regkeys = (tied %networkreg)->select_where("internalip = '$remoteip'");
|
902 |
|
|
foreach my $k (@regkeys) {
|
903 |
|
|
my $network = $networkreg{$k};
|
904 |
|
|
my @domregkeys = (tied %domreg)->select_where("networkuuid1 = '$network->{uuid}'");
|
905 |
|
|
my $dom = $domreg{$network->{'domains'}} || $domreg{$domregkeys[0]}; # Sometimes domains is lost in network - compensate
|
906 |
|
|
# Request is coming from a running server from an internal ip - accept
|
907 |
|
|
if ($network->{'internalip'} eq $remoteip) {
|
908 |
|
|
$user = $network->{'user'};
|
909 |
|
|
# my $dom = $domreg{$network->{'domains'}};
|
910 |
|
|
if ($package eq 'networks') {
|
911 |
|
|
$curuuid = $network->{'uuid'};
|
912 |
|
|
$curdomuuid = $network->{'domains'};
|
913 |
|
|
$cursysuuid = $dom->{'system'};
|
914 |
|
|
} elsif ($package eq 'images') {
|
915 |
|
|
$curimg = $dom->{'image'} unless ($curimg);
|
916 |
|
|
} elsif ($package eq 'systems') {
|
917 |
|
|
$curuuid = $dom->{'system'} || $dom->{'uuid'} unless ($curuuid);
|
918 |
|
|
$cursysuuid = $dom->{'system'};
|
919 |
|
|
$curdomuuid = $dom->{'uuid'};
|
920 |
|
|
} elsif ($package eq 'servers') {
|
921 |
|
|
$curuuid = $dom->{'uuid'} unless ($curuuid);
|
922 |
|
|
$cursysuuid = $dom->{'system'};
|
923 |
|
|
}
|
924 |
|
|
if (!$userreg{$user}->{'allowinternalapi'}) {
|
925 |
|
|
$user = ''; # Internal API access is not enabled, disallow access
|
926 |
|
|
}
|
927 |
|
|
last;
|
928 |
|
|
}
|
929 |
|
|
}
|
930 |
|
|
untie %networkreg;
|
931 |
|
|
untie %domreg;
|
932 |
705b5366
|
hq
|
} else { # Check authorized referers to mitigate CSRF attacks. If no referer in ENV we let it pass to allow API access.
|
933 |
|
|
if (-e "/etc/stabile/basereferers"
|
934 |
|
|
&& $ENV{HTTP_REFERER}
|
935 |
|
|
) {
|
936 |
|
|
my $basereferers = `cat /etc/stabile/basereferers`;
|
937 |
|
|
chomp $basereferers;
|
938 |
|
|
my @baserefs = split(/\s+/, $basereferers);
|
939 |
|
|
my $match = 0;
|
940 |
|
|
foreach my $ref (@baserefs) {
|
941 |
|
|
if ($ENV{HTTP_REFERER} =~ /$ref/) {
|
942 |
|
|
$match = 1;
|
943 |
|
|
last;
|
944 |
|
|
}
|
945 |
|
|
}
|
946 |
|
|
$user = '' unless ($match);
|
947 |
|
|
}
|
948 |
95b003ff
|
Origo
|
}
|
949 |
|
|
$user = $1 if $user =~ /(.+)/; #untaint
|
950 |
|
|
$tktuser = $user;
|
951 |
|
|
$Stabile::tktuser = $tktuser;
|
952 |
|
|
|
953 |
|
|
# Initalize CGI
|
954 |
|
|
$Stabile::q = new CGI;
|
955 |
|
|
|
956 |
|
|
# Load params
|
957 |
|
|
%params = $Stabile::q->Vars;
|
958 |
|
|
$uripath = URI::Escape::uri_unescape($ENV{'REQUEST_URI'});
|
959 |
|
|
if ($options{s}) {
|
960 |
|
|
$account = $options{s};
|
961 |
|
|
} else {
|
962 |
|
|
$account = $Stabile::q->cookie('steamaccount');
|
963 |
|
|
}
|
964 |
|
|
$user = 'guest' if (!$user && $params{'action'} eq 'help');
|
965 |
|
|
die "No active user. Please authenticate or provide user through REMOTE_USER environment variable." unless ($user);
|
966 |
|
|
|
967 |
|
|
my $u = $userreg{$user};
|
968 |
|
|
my @accounts = split(/,\s*/, $u->{'accounts'}) if ($u->{'accounts'});
|
969 |
|
|
my @accountsprivs = split(/,\s*/, $u->{'accountsprivileges'}) if ($u->{'accountsprivileges'});
|
970 |
|
|
for my $i (0 .. $#accounts)
|
971 |
|
|
{ $ahash{$accounts[$i]} = $accountsprivs[$i] || 'r'; }
|
972 |
|
|
|
973 |
|
|
$privileges = '';
|
974 |
|
|
# User is requesting access to another account - check privs
|
975 |
|
|
if ($account && $account ne $user) {
|
976 |
|
|
if ($ahash{$account}) {
|
977 |
|
|
$user = $account;
|
978 |
|
|
$main::account = $account;
|
979 |
|
|
# Only allow users whose base account is admin to get admin privs
|
980 |
|
|
$ahash{$account} =~ s/a// unless ($userreg{$tktuser}->{'privileges'} =~ /a/);
|
981 |
|
|
$privileges = $ahash{$account};
|
982 |
|
|
$u = $userreg{$account};
|
983 |
|
|
}
|
984 |
|
|
}
|
985 |
|
|
|
986 |
|
|
$Stabile::user = $user;
|
987 |
|
|
|
988 |
|
|
$defaultmemoryquota = $Stabile::config->get('MEMORY_QUOTA') + 0;
|
989 |
|
|
$defaultstoragequota = $Stabile::config->get('STORAGE_QUOTA') + 0;
|
990 |
|
|
$defaultnodestoragequota = $Stabile::config->get('NODESTORAGE_QUOTA') + 0;
|
991 |
|
|
$defaultvcpuquota = $Stabile::config->get('VCPU_QUOTA') + 0;
|
992 |
|
|
$defaultexternalipquota = $Stabile::config->get('EXTERNAL_IP_QUOTA') + 0;
|
993 |
|
|
$defaultrxquota = $Stabile::config->get('RX_QUOTA') + 0;
|
994 |
|
|
$defaulttxquota = $Stabile::config->get('TX_QUOTA') + 0;
|
995 |
|
|
|
996 |
|
|
# Read quotas and privileges from db
|
997 |
|
|
$Stabile::userstoragequota = 0+ $u->{'storagequota'} if ($u->{'storagequota'});
|
998 |
|
|
$Stabile::usernodestoragequota = 0+ $u->{'nodestoragequota'} if ($u->{'storagequota'});
|
999 |
|
|
$usermemoryquota = 0+ $u->{'memoryquota'} if ($u->{'memoryquota'});
|
1000 |
|
|
$uservcpuquota = 0+ $u->{'vcpuquota'} if ($u->{'vcpuquota'});
|
1001 |
54401133
|
hq
|
$Stabile::userexternalipquota = 0+ $u->{'externalipquota'} if ($u->{'externalipquota'});
|
1002 |
|
|
$Stabile::userrxquota = 0+ $u->{'rxquota'} if ( $u->{'rxquota'});
|
1003 |
|
|
$Stabile::usertxquota = 0+ $u->{'txquota'} if ($u->{'txquota'});
|
1004 |
95b003ff
|
Origo
|
|
1005 |
|
|
$billto = $u->{'billto'};
|
1006 |
|
|
$Stabile::userprivileges = $u->{'privileges'};
|
1007 |
|
|
$privileges = $Stabile::userprivileges if (!$privileges && $Stabile::userprivileges);
|
1008 |
|
|
$isadmin = index($privileges,"a")!=-1;
|
1009 |
|
|
$ismanager = index($privileges,"m")!=-1;
|
1010 |
|
|
$isreadonly = index($privileges,"r")!=-1;
|
1011 |
|
|
$preserveimagesonremove = index($privileges,"p")!=-1;
|
1012 |
|
|
$fulllist = $options{f} && $isadmin;
|
1013 |
|
|
$fullupdate = $options{p} && $isadmin;
|
1014 |
|
|
|
1015 |
71b897d3
|
hq
|
my $bto = $userreg{$billto};
|
1016 |
|
|
my @bdnsdomains = split(/, ?/, $bto->{'dnsdomains'});
|
1017 |
|
|
my @udnsdomains = split(/, ?/, $u->{'dnsdomains'});
|
1018 |
23748604
|
hq
|
$dnsdomain = '' if ($dnsdomain eq '--'); # TODO - ugly
|
1019 |
|
|
$udnsdomains[0] = '' if ($udnsdomains[0] eq '--');
|
1020 |
|
|
$bdnsdomains[0] = '' if ($bdnsdomains[0] eq '--');
|
1021 |
45cc3024
|
hq
|
$dnsdomain = $udnsdomains[0] || $bdnsdomains[0] || $dnsdomain; # override config value
|
1022 |
|
|
|
1023 |
|
|
my $bstoreurl = $bto->{'appstoreurl'};
|
1024 |
23748604
|
hq
|
$bstoreurl = '' if ($bstoreurl eq '--');
|
1025 |
45cc3024
|
hq
|
my $ustoreurl = $u->{'appstoreurl'};
|
1026 |
23748604
|
hq
|
$ustoreurl = '' if ($ustoreurl eq '--');
|
1027 |
45cc3024
|
hq
|
$appstoreurl = $bstoreurl || $ustoreurl || $appstoreurl; # override config value
|
1028 |
71b897d3
|
hq
|
|
1029 |
95b003ff
|
Origo
|
$Stabile::sshcmd = $sshcmd;
|
1030 |
|
|
$Stabile::disablesnat = $disablesnat;
|
1031 |
|
|
$Stabile::privileges = $privileges;
|
1032 |
|
|
$Stabile::isadmin = $isadmin;
|
1033 |
|
|
|
1034 |
|
|
$storagepools = $u->{'storagepools'}; # Prioritized list of users storage pools as numbers, e.g. "0,2,1"
|
1035 |
|
|
my $dbuser = $u->{'username'};
|
1036 |
|
|
untie %userreg;
|
1037 |
|
|
|
1038 |
|
|
# If params are passed in URI for a POST og PUT request, we try to parse them out
|
1039 |
|
|
if (($ENV{'REQUEST_METHOD'} ne 'GET') && !$isreadonly) {
|
1040 |
|
|
$action = $1 if (!$action && $uripath =~ /action=(\w+)/);
|
1041 |
|
|
if ($uripath =~ /$package(\.cgi)?\/(.+)$/ && !$isreadonly) {
|
1042 |
|
|
my $uuid = $2;
|
1043 |
|
|
if (!(%params) && !$curuuid && $uuid =~ /^\?/) {
|
1044 |
|
|
%params = split /[=&]/, substr($uuid,1);
|
1045 |
|
|
$curuuid = $params{uuid};
|
1046 |
|
|
} else {
|
1047 |
|
|
$curuuid = $uuid;
|
1048 |
|
|
}
|
1049 |
|
|
$curuuid = $1 if ($curuuid =~ /\/(.+)/);
|
1050 |
|
|
}
|
1051 |
|
|
}
|
1052 |
|
|
|
1053 |
|
|
# Parse out params from g option if called from cmdline
|
1054 |
|
|
my $args = $options{g};
|
1055 |
|
|
if ($args && !%params) {
|
1056 |
|
|
my $obj = from_json( uri_unescape ($args));
|
1057 |
|
|
if (ref($obj) eq 'HASH') {
|
1058 |
|
|
%params = %{$obj};
|
1059 |
|
|
} else {
|
1060 |
|
|
%params = {};
|
1061 |
|
|
$params{'POSTDATA'} = $args;
|
1062 |
|
|
}
|
1063 |
|
|
$console = $obj->{'console'} if ($obj->{'console'});
|
1064 |
|
|
$curuuid = $obj->{uuid} if (!$curuuid && $obj->{uuid}); # backwards compat with apps calling removesystem
|
1065 |
|
|
}
|
1066 |
|
|
|
1067 |
|
|
# Action may be via on command line switch -a
|
1068 |
|
|
if (!$action) {
|
1069 |
|
|
$action = $options{a};
|
1070 |
|
|
if ($action) { # Set a few options if we are called from command line
|
1071 |
|
|
$console = 1 unless ($options{v} && !$options{c});
|
1072 |
|
|
$Data::Dumper::Varname = $package;
|
1073 |
|
|
$Data::Dumper::Pair = ' : ';
|
1074 |
|
|
$Data::Dumper::Terse = 1;
|
1075 |
|
|
$Data::Dumper::Useqq = 1;
|
1076 |
|
|
}
|
1077 |
|
|
}
|
1078 |
|
|
# Parse out $action - i.e. find out what action is requested
|
1079 |
|
|
$action = $action || $params{'action'}; # $action may have been set above to 'remove' by DELETE request
|
1080 |
|
|
|
1081 |
|
|
# Handling of action given as part of addressable API
|
1082 |
|
|
# Special cases for systems, monitors, etc.
|
1083 |
|
|
if (!$action && $uripath =~ /$package\/(.+)(\/|\?)/ && !$params{'path'}) {
|
1084 |
|
|
$action = $1;
|
1085 |
|
|
$action = $1 if ($action =~ /([^\/]+)\/(.*)/);
|
1086 |
|
|
}
|
1087 |
|
|
$curuuid = $curuuid || $params{'uuid'} || $params{'id'} || $params{'system'} || $params{'serveruuid'};
|
1088 |
|
|
# Handling of target given as part of addressable API
|
1089 |
|
|
# if ($uripath =~ /$package(\.cgi)?\/($action\/)?(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})(:\w+)?/) {
|
1090 |
|
|
if ($uripath =~ /$package\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})(:\w+)?/) {
|
1091 |
|
|
$curuuid = "$1$2";
|
1092 |
|
|
} elsif ($package eq 'nodes' && $uripath =~ /$package\/(\w{12})(:\w+)?/) {
|
1093 |
|
|
$curuuid = "$1$2";
|
1094 |
|
|
}
|
1095 |
|
|
|
1096 |
|
|
$action = lc $action;
|
1097 |
|
|
if (!$params && $options{k}) {
|
1098 |
|
|
$params{'keywords'} = URI::Escape::uri_unescape($options{k});
|
1099 |
|
|
$console = 1 unless ($options{v} && !$options{c});
|
1100 |
|
|
}
|
1101 |
d3d1a2d4
|
Origo
|
$action = (($action)?$action.'_':'') . 'remove' if ($ENV{'REQUEST_METHOD'} eq 'DELETE' && $action ne 'remove');
|
1102 |
95b003ff
|
Origo
|
# -f should only set $fulllisting and not trigger any keyword actions
|
1103 |
|
|
delete $params{'keywords'} if ($params{'keywords'} eq '-f');
|
1104 |
|
|
|
1105 |
|
|
# Regular read - we send out JSON version of directory list
|
1106 |
|
|
if (!$action && (!$ENV{'REQUEST_METHOD'} || $ENV{'REQUEST_METHOD'} eq 'GET')) {
|
1107 |
|
|
if (!($package)) {
|
1108 |
|
|
; # If we get called as a library this is were we end - do nothing...
|
1109 |
|
|
} elsif ($params{'keywords'}) {
|
1110 |
|
|
; # If param keywords is provided treat as a post
|
1111 |
|
|
} else {
|
1112 |
|
|
$action = 'list';
|
1113 |
|
|
}
|
1114 |
|
|
}
|
1115 |
|
|
|
1116 |
|
|
### Main security check
|
1117 |
|
|
unless ($package eq 'pressurecontrol' || $dbuser || ($user eq 'common' && $action =~ /^updatebtime|^list/)) {throw Error::Simple("Status=Error $action: Unknown user $user [$remoteip]")};
|
1118 |
|
|
if (index($privileges,"d")!=-1 && $action ne 'help') {throw Error::Simple("Status=Error Disabled user")};
|
1119 |
|
|
|
1120 |
|
|
$curuuid = $curuuid || URI::Escape::uri_unescape($params{'uuid'}); # $curuuid may have been set above for DELETE requests
|
1121 |
|
|
$curuuid = "" if ($curuuid eq "--");
|
1122 |
|
|
$curuuid = $options{u} unless $curuuid;
|
1123 |
|
|
if ($package eq 'images') {
|
1124 |
|
|
$curimg = URI::Escape::uri_unescape($params{'image'} || $params{'path'}) unless ($action eq 'listfiles');
|
1125 |
|
|
$curimg = "" if ($curimg eq "--");
|
1126 |
|
|
$curimg = $1 if ($curimg =~ /(.*)\*$/); # Handle Dojo peculiarity
|
1127 |
|
|
$curimg = URI::Escape::uri_unescape($options{i}) unless $curimg;
|
1128 |
|
|
unless (tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images', CLOBBER=>1}, $Stabile::dbopts)) ) {throw Error::Simple("Stroke=Error Image UUID register could not be accessed")};
|
1129 |
|
|
if ($curimg && !$curuuid && $curimg =~ /(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})/) {
|
1130 |
|
|
$curuuid = $curimg;
|
1131 |
|
|
$curimg = $imagereg{$curuuid}->{'path'} if ($imagereg{$curuuid});
|
1132 |
|
|
# } elsif ($target && !$curimg && !$curuuid) {
|
1133 |
|
|
# if ($target =~ /(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})/) {
|
1134 |
|
|
# $curuuid = $1;
|
1135 |
|
|
# $curimg = $imagereg{$curuuid}->{'path'};
|
1136 |
|
|
# } else {
|
1137 |
|
|
# $curimg = $target;
|
1138 |
|
|
# }
|
1139 |
|
|
} elsif (!$curimg && $curuuid) {
|
1140 |
|
|
$curimg = $imagereg{$curuuid}->{'path'} if ($imagereg{$curuuid});
|
1141 |
|
|
}
|
1142 |
|
|
untie %imagereg;
|
1143 |
|
|
}
|
1144 |
|
|
}
|
1145 |
|
|
|
1146 |
|
|
sub process {
|
1147 |
|
|
my $target = $params{'target'} || $options{t} || $curuuid;
|
1148 |
|
|
# We may receive utf8 strings either from browser or command line - convert them to native Perl to avoid double encodings
|
1149 |
|
|
utf8::decode($target) if ( $target =~ /[^\x00-\x7f]/ );# true if string contains any non-ascii character
|
1150 |
|
|
my $uipath;
|
1151 |
d24d9a01
|
hq
|
# my $uistatus;
|
1152 |
95b003ff
|
Origo
|
# Special handling
|
1153 |
|
|
if ($package eq 'images') {
|
1154 |
|
|
$target = $curimg || $params{'path'} || $params{'image'} || $target unless ($target =~ /^\/.+/);
|
1155 |
|
|
$params{'restorepath'} = $params{'path'} if ($action eq 'listfiles');
|
1156 |
2a63870a
|
Christian Orellana
|
$params{'baseurl'} = "https://$ENV{'HTTP_HOST'}/stabile" if ($action eq 'download' && $ENV{'HTTP_HOST'} && !($baseurl =~ /\./)); # send baseurl if configured value not valid
|
1157 |
95b003ff
|
Origo
|
} elsif ($package eq 'systems') {
|
1158 |
|
|
$target = $params{'id'} || $target if ($action =~ /^monitors_/);
|
1159 |
|
|
} elsif ($package eq 'nodes') {
|
1160 |
|
|
$target = $target || $params{'mac'};
|
1161 |
|
|
} elsif ($package eq 'users') {
|
1162 |
|
|
$target = $target || $params{'username'};
|
1163 |
|
|
}
|
1164 |
|
|
# Named action - we got a request for an action
|
1165 |
|
|
my $obj;
|
1166 |
|
|
if ($action && (defined &{"do_$action"}) && ($ENV{'REQUEST_METHOD'} ne 'POST' || $action eq 'upload' || $action eq 'restorefiles')) {
|
1167 |
|
|
# If a function named do_$action (only lowercase allowed) exists, call it and print the result
|
1168 |
|
|
if ($action =~ /^monitors/) {
|
1169 |
|
|
if ($params{'PUTDATA'}) {
|
1170 |
|
|
$obj = $params{'PUTDATA'};
|
1171 |
|
|
$action = 'monitors_save' unless ($action =~ /monitors_.+/);
|
1172 |
|
|
} else {
|
1173 |
|
|
$obj = { action => $action, id => $target };
|
1174 |
|
|
}
|
1175 |
|
|
} else {
|
1176 |
|
|
unless (%params) {
|
1177 |
|
|
if ($package eq 'images' && $target =~ /^\//) {
|
1178 |
|
|
%params = ("path", $target);
|
1179 |
|
|
delete $params{"uuid"};
|
1180 |
|
|
} else{
|
1181 |
|
|
%params = ("uuid", $target);
|
1182 |
|
|
}
|
1183 |
|
|
}
|
1184 |
|
|
if ($curuuid || $target) {
|
1185 |
|
|
$params{uuid} = $curuuid || $target unless ($params{uuid} || $params{path} || ($params{image} && $package eq 'images'));
|
1186 |
|
|
}
|
1187 |
|
|
$obj = getObj(\%params);
|
1188 |
|
|
}
|
1189 |
|
|
$obj->{'console'} = $console if ($console);
|
1190 |
2a63870a
|
Christian Orellana
|
$obj->{'baseurl'} = $params{baseurl} if ($params{baseurl});
|
1191 |
95b003ff
|
Origo
|
# Perform the action
|
1192 |
|
|
$postreply = &{"do_$action"}($target, $action, $obj);
|
1193 |
|
|
if (!$postreply) { # We expect some kind of reply
|
1194 |
6fdc8676
|
hq
|
$postreply .= header('text/plain', '500 Internal Server Error because no reply') unless ($console);
|
1195 |
95b003ff
|
Origo
|
$main::syslogit->($user, 'info', "Could not $action $target ($package)") unless ($action eq 'uuidlookup');
|
1196 |
|
|
} elsif (! ($postreply =~ /^(Content-type|Status|Location):/i) ) {
|
1197 |
|
|
if ($postreply =~ /Content-type:/) {
|
1198 |
|
|
;
|
1199 |
|
|
} elsif (!$postreply || $postreply =~ /Status=/ || $postreply =~ /^</ || $postreply =~ /^\w/) {
|
1200 |
|
|
$postreply = header('text/plain; charset=UTF8') . $postreply unless ($console);
|
1201 |
|
|
} else {
|
1202 |
|
|
$postreply = header('application/json; charset=UTF8') . $postreply unless ($console);
|
1203 |
|
|
}
|
1204 |
|
|
}
|
1205 |
|
|
print "$postreply";
|
1206 |
|
|
|
1207 |
|
|
} elsif (($params{'PUTDATA'} || $params{"keywords"} || $params{"POSTDATA"}) && !$isreadonly) {
|
1208 |
|
|
# We got a save post with JSON. Look for interesting stuff and perform action or save
|
1209 |
2a63870a
|
Christian Orellana
|
my @json_array;
|
1210 |
95b003ff
|
Origo
|
if ($params{'PUTDATA'}) {
|
1211 |
|
|
my $json_text = $params{'PUTDATA'};
|
1212 |
|
|
utf8::decode($json_text);
|
1213 |
|
|
$json_text =~ s/\x/ /g;
|
1214 |
|
|
$json_text =~ s/\[\]/\"\"/g;
|
1215 |
|
|
@json_array = from_json($json_text);
|
1216 |
|
|
} elsif ($params{"keywords"} || $params{"POSTDATA"}) {
|
1217 |
|
|
my $json_text = $params{"keywords"} || $params{'POSTDATA'};
|
1218 |
|
|
$json_text = uri_unescape($json_text);
|
1219 |
|
|
utf8::decode($json_text);
|
1220 |
|
|
$json_text =~ s/\x/ /g;
|
1221 |
|
|
$json_text =~ s/\[\]/\"\"/g;
|
1222 |
|
|
my $json_obj = from_json($json_text);
|
1223 |
|
|
if (ref $json_obj eq 'ARRAY') {
|
1224 |
|
|
@json_array = @$json_obj;
|
1225 |
|
|
} elsif (ref $json_obj eq 'HASH') {
|
1226 |
|
|
my %json_hash = %$json_obj;
|
1227 |
|
|
my $json_array_ref = [\%json_hash];
|
1228 |
|
|
if ($json_hash{"items"}) {
|
1229 |
|
|
$json_array_ref = $json_hash{"items"};
|
1230 |
|
|
}
|
1231 |
|
|
@json_array = @$json_array_ref;
|
1232 |
|
|
}
|
1233 |
|
|
}
|
1234 |
2a63870a
|
Christian Orellana
|
|
1235 |
95b003ff
|
Origo
|
foreach (@json_array) {
|
1236 |
|
|
my %h = %$_;
|
1237 |
|
|
$console = 1 if $h{"console"};
|
1238 |
|
|
my $objaction = $h{'action'} || $action;
|
1239 |
|
|
$objaction = 'save' if (!$objaction || $objaction eq "--");
|
1240 |
|
|
$h{'action'} = $objaction = $action.'_'.$objaction if ($action eq "monitors" || $action eq "packages"); # Allow sending e.g. disable action to monitors by calling monitors_disable
|
1241 |
2a63870a
|
Christian Orellana
|
$h{'action'} = $objaction if ($objaction && !$h{'action'});
|
1242 |
95b003ff
|
Origo
|
my $obj = getObj(\%h);
|
1243 |
|
|
next unless $obj;
|
1244 |
|
|
$obj->{'console'} = $console if ($console);
|
1245 |
|
|
# Now build the requested action
|
1246 |
|
|
my $objfunc = "do_$objaction";
|
1247 |
|
|
# If a function named objfunc exists, call it
|
1248 |
|
|
if (defined &$objfunc) {
|
1249 |
|
|
$target = $h{'uuid'} || $h{'id'};
|
1250 |
|
|
$uiuuid = $target;
|
1251 |
|
|
my $targetimg = $imagereg{$target};
|
1252 |
|
|
# Special handling
|
1253 |
|
|
if ($package eq 'images') {
|
1254 |
|
|
$target = $targetimg->{'path'} || $h{'image'} || $h{'path'} || $target;
|
1255 |
|
|
}
|
1256 |
|
|
# Perform the action
|
1257 |
|
|
$postreply = &{$objfunc}($target, $objaction, $obj);
|
1258 |
|
|
# $uistatus = $1 if ($postreply =~ /\w+=(.\w+) /);
|
1259 |
|
|
# Special handling
|
1260 |
|
|
if ($package eq 'images') {
|
1261 |
|
|
if ($h{'status'} eq 'new') {
|
1262 |
|
|
# $uistatus = 'new';
|
1263 |
|
|
# $uiuuid = ''; # Refresh entire view
|
1264 |
|
|
}
|
1265 |
|
|
}
|
1266 |
|
|
my $node = $nodereg{$mac};
|
1267 |
|
|
my $updateEntry = {
|
1268 |
|
|
tab=>$tab,
|
1269 |
|
|
user=>$user,
|
1270 |
|
|
uuid=>$uiuuid,
|
1271 |
|
|
status=>$uistatus,
|
1272 |
|
|
mac=>$mac,
|
1273 |
|
|
macname=>$node->{'name'},
|
1274 |
|
|
displayip=>$uidisplayip,
|
1275 |
|
|
displayport=>$uidisplayport,
|
1276 |
|
|
type=>$uiupdatetype,
|
1277 |
|
|
message=>$postmsg
|
1278 |
|
|
};
|
1279 |
|
|
# Special handling
|
1280 |
|
|
if ($package eq 'images') {
|
1281 |
|
|
$obj->{'uuid'} = '' if ($uistatus eq 'new');
|
1282 |
|
|
$uipath = $obj->{'path'};
|
1283 |
|
|
$updateEntry->{'path'} = $uipath;
|
1284 |
|
|
$uiname = $obj->{'name'};
|
1285 |
|
|
}
|
1286 |
|
|
if ($uiname) {
|
1287 |
|
|
$updateEntry->{'name'} = $uiname;
|
1288 |
|
|
}
|
1289 |
|
|
if ($uiuuid || $postmsg || $uistatus) {
|
1290 |
|
|
push (@updateList, $updateEntry);
|
1291 |
|
|
}
|
1292 |
|
|
} else {
|
1293 |
|
|
$postreply .= "Status=ERROR Unknown $package action: $objaction\n";
|
1294 |
|
|
}
|
1295 |
|
|
}
|
1296 |
|
|
|
1297 |
|
|
if (! ($postreply =~ /^(Content-type|Status|Location):/i) ) {
|
1298 |
|
|
if (!$postreply || $postreply =~ /Status=/) {
|
1299 |
|
|
$postreply = header('text/plain; charset=UTF8') . $postreply unless ($console);
|
1300 |
|
|
} else {
|
1301 |
|
|
$postreply = header('application/json; charset=UTF8') . $postreply unless ($console);
|
1302 |
|
|
}
|
1303 |
|
|
}
|
1304 |
|
|
print $postreply;
|
1305 |
|
|
} else {
|
1306 |
|
|
$postreply .= "Status=Error Unknown $ENV{'REQUEST_METHOD'} $package action: $action\n";
|
1307 |
|
|
print header('text/html', '500 Internal Server Error') unless ($console);
|
1308 |
|
|
print $postreply;
|
1309 |
|
|
}
|
1310 |
|
|
# Functions called via aliases to privileged_action or privileged_action_async cannot update $postmsg or $uistatus
|
1311 |
|
|
# so updateUI must be called internally in these functions.
|
1312 |
|
|
if (@updateList) {
|
1313 |
|
|
$main::updateUI->(@updateList);
|
1314 |
|
|
}
|
1315 |
|
|
}
|
1316 |
|
|
|
1317 |
|
|
|
1318 |
|
|
# Print list of available actions
|
1319 |
|
|
sub Help {
|
1320 |
|
|
$help = 1;
|
1321 |
|
|
no strict 'refs';
|
1322 |
|
|
my %fdescriptions;
|
1323 |
|
|
my %fmethods;
|
1324 |
|
|
my %fparams;
|
1325 |
|
|
my @fnames;
|
1326 |
|
|
|
1327 |
|
|
my $res = header() unless ($console);
|
1328 |
|
|
# my $tempuuid = "484d7852-90d2-43f1-8bd6-e29e234848b0";
|
1329 |
|
|
my $tempuuid = "";
|
1330 |
|
|
unless ($console) {
|
1331 |
|
|
$res .= <<END
|
1332 |
|
|
<!DOCTYPE html>
|
1333 |
|
|
<html>
|
1334 |
|
|
<head>
|
1335 |
|
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
|
1336 |
|
|
<!-- script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script -->
|
1337 |
|
|
<!-- script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script -->
|
1338 |
|
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
|
1339 |
|
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
|
1340 |
|
|
<style>
|
1341 |
|
|
.form-control {display: inline-block; width: auto; margin: 2px; }
|
1342 |
|
|
input.form-control {width: 180px;}
|
1343 |
|
|
pre {
|
1344 |
|
|
overflow-x: auto;
|
1345 |
|
|
white-space: pre-wrap;
|
1346 |
|
|
white-space: -moz-pre-wrap;
|
1347 |
|
|
white-space: -pre-wrap;
|
1348 |
|
|
white-space: -o-pre-wrap;
|
1349 |
|
|
word-wrap: break-word;
|
1350 |
|
|
}
|
1351 |
|
|
</style>
|
1352 |
|
|
</head>
|
1353 |
|
|
<body style="margin:1.25rem;">
|
1354 |
|
|
<div>
|
1355 |
|
|
<table style="width:100%;"><tr><td>
|
1356 |
|
|
<select class="form-control" id="scopeaction" name="scopeaction" placeholder="action" onchange="data.scopeaction=this.value; dofields();" autocomplete="off"></select>
|
1357 |
|
|
<span id="scopeinputs">
|
1358 |
|
|
<input class="form-control" id="scopeuuid" name="scopeuuid" placeholder="uuid" onchange="data.scopedata.uuid=this.value; update();" value="$tempuuid" autocomplete="off" size="34">
|
1359 |
|
|
</span>
|
1360 |
|
|
<button class="btn btn-primary" href="#" onclick="doit();">Try it</button>
|
1361 |
|
|
<pre>
|
1362 |
|
|
\$.ajax({
|
1363 |
|
|
url: "<span class='scopeurl'>/stabile/$package?uuid=$tempuuid&action=activate</span>",
|
1364 |
|
|
type: "<span class='scopemethod'>GET</span>", <span id="dataspan" style="display:none;"><br /> data: "<span class="scopedata"></span>",</span>
|
1365 |
|
|
success: function(result) {\$("#scoperesult").text(result);}
|
1366 |
|
|
});
|
1367 |
|
|
</pre>
|
1368 |
|
|
</td><td width="50%"><textarea id="scoperesult" style="width:100%; height: 200px;"></textarea></td>
|
1369 |
|
|
</tr>
|
1370 |
|
|
</table>
|
1371 |
|
|
</div>
|
1372 |
|
|
<script>
|
1373 |
|
|
data = {"scopemethod": "GET", "scopeaction": "activate", "scopeuuid": "$tempuuid", "scopeurl": "/stabile/$package?uuid=$tempuuid&action=activate"};
|
1374 |
|
|
function doit() {
|
1375 |
|
|
var obj = {
|
1376 |
|
|
url: data.scopeurl,
|
1377 |
|
|
type: data.scopemethod,
|
1378 |
|
|
success: handleResult,
|
1379 |
|
|
error: handleResult
|
1380 |
|
|
}
|
1381 |
|
|
if (data.scopemethod != 'GET') obj.data = JSON.stringify(data.scopedata);
|
1382 |
|
|
\$.ajax(obj);
|
1383 |
27512919
|
Origo
|
\$("#scoperesult").text("");
|
1384 |
95b003ff
|
Origo
|
return true;
|
1385 |
|
|
function handleResult(data, textStatus, jqXHR) {
|
1386 |
|
|
if (jqXHR == 'Unauthorized') {
|
1387 |
|
|
\$("#scoperesult").text(jqXHR + ": You must log in before you can call API methods.");
|
1388 |
|
|
} else if (jqXHR.responseText) {
|
1389 |
|
|
\$("#scoperesult").text(jqXHR.responseText);
|
1390 |
|
|
} else {
|
1391 |
|
|
\$("#scoperesult").text("No result received");
|
1392 |
|
|
}
|
1393 |
|
|
}
|
1394 |
|
|
}
|
1395 |
|
|
function dofields() {
|
1396 |
|
|
if (scopeparams[data.scopeaction].length==0) {
|
1397 |
|
|
\$("#scopeinputs").hide();
|
1398 |
|
|
} else {
|
1399 |
|
|
var fields = "";
|
1400 |
|
|
\$.each(scopeparams[data.scopeaction], function (i, item) {
|
1401 |
|
|
var itemname = "scope" + item;
|
1402 |
|
|
if (\$("#"+itemname).val()) data[itemname] = \$("#"+itemname).val();
|
1403 |
|
|
fields += '<input class="form-control" id="' + itemname + '" placeholder="' + item + '" value="' + ((data[itemname])?data[itemname]:'') + '" size="34" onchange="update();"> ';
|
1404 |
|
|
});
|
1405 |
|
|
\$("#scopeinputs").empty();
|
1406 |
|
|
\$("#scopeinputs").append(fields);
|
1407 |
|
|
\$("#scopeinputs").show();
|
1408 |
|
|
}
|
1409 |
|
|
update();
|
1410 |
|
|
}
|
1411 |
|
|
function update() {
|
1412 |
|
|
data.scopemethod = scopemethods[data.scopeaction];
|
1413 |
|
|
if (data.scopemethod == "POST") {
|
1414 |
|
|
\$("#dataspan").show();
|
1415 |
|
|
data.scopeurl = "/stabile/$package";
|
1416 |
|
|
data.scopedata = {"items": [{"action":data.scopeaction}]};
|
1417 |
|
|
\$.each(scopeparams[data.scopeaction], function (i, item) {
|
1418 |
|
|
var val = \$("#scope"+item).val();
|
1419 |
|
|
if (val) data.scopedata.items[0][item] = val;
|
1420 |
|
|
});
|
1421 |
|
|
} else if (data.scopemethod == "PUT") {
|
1422 |
|
|
\$("#dataspan").show();
|
1423 |
|
|
data.scopeurl = "/stabile/$package";
|
1424 |
|
|
data.scopedata = [{"action":data.scopeaction}];
|
1425 |
|
|
\$.each(scopeparams[data.scopeaction], function (i, item) {
|
1426 |
|
|
var val = \$("#scope"+item).val();
|
1427 |
|
|
if (val) data.scopedata[0][item] = val;
|
1428 |
|
|
});
|
1429 |
|
|
} else {
|
1430 |
|
|
\$("#dataspan").hide();
|
1431 |
|
|
data.scopeurl = "/stabile/$package?action="+data.scopeaction;
|
1432 |
|
|
\$.each(scopeparams[data.scopeaction], function (i, item) {
|
1433 |
|
|
var val = \$("#scope"+item).val();
|
1434 |
|
|
if (val) data.scopeurl += "&" + item + "=" + val;
|
1435 |
|
|
});
|
1436 |
|
|
data.scopedata = '';
|
1437 |
|
|
}
|
1438 |
|
|
\$(".scopemethod").text(data.scopemethod);
|
1439 |
|
|
\$(".scopeurl").text(data.scopeurl);
|
1440 |
|
|
\$(".scopedata").text(JSON.stringify(data.scopedata, null, ' ').replace(/\\n/g,'').replace(/ /g,''));
|
1441 |
|
|
}
|
1442 |
|
|
\$( document ).ready(function() {
|
1443 |
|
|
data.scopeaction=\$("#scopeaction").val(); dofields()
|
1444 |
|
|
});
|
1445 |
|
|
END
|
1446 |
|
|
;
|
1447 |
|
|
$res .= qq|var scopeparams = {};\n|;
|
1448 |
|
|
$res .= qq|var scopemethods = {};\n|;
|
1449 |
|
|
$res .= qq|var package="$package"\n|;
|
1450 |
|
|
}
|
1451 |
|
|
my @entries;
|
1452 |
|
|
if ($package eq 'networks') {
|
1453 |
|
|
@entries = sort keys %Stabile::Networks::;
|
1454 |
|
|
} elsif ($package eq 'images') {
|
1455 |
|
|
@entries = sort keys %Stabile::Images::;
|
1456 |
|
|
} elsif ($package eq 'servers') {
|
1457 |
|
|
@entries = sort keys %Stabile::Servers::;
|
1458 |
|
|
} elsif ($package eq 'nodes') {
|
1459 |
|
|
@entries = sort keys %Stabile::Nodes::;
|
1460 |
|
|
} elsif ($package eq 'users') {
|
1461 |
|
|
@entries = sort keys %Stabile::Users::;
|
1462 |
|
|
} elsif ($package eq 'systems') {
|
1463 |
|
|
@entries = sort keys %Stabile::Systems::;
|
1464 |
|
|
}
|
1465 |
|
|
|
1466 |
|
|
foreach my $entry (@entries) {
|
1467 |
|
|
if (defined &{"$entry"} && $entry !~ /help/i && $entry =~ /^do_(.+)/) {
|
1468 |
|
|
my $fname = $1;
|
1469 |
|
|
# Ask function for help - $help is on
|
1470 |
|
|
my $helptext = &{"$entry"}(0, $fname);
|
1471 |
|
|
my @helplist = split(":", $helptext, 3);
|
1472 |
|
|
chomp $helptext;
|
1473 |
|
|
unless ($fname =~ /^gear_/) {
|
1474 |
|
|
$fmethods{$fname} = $helplist[0];
|
1475 |
|
|
$fparams{$fname} = $helplist[1];
|
1476 |
|
|
$fdescriptions{$fname} = $helplist[2];
|
1477 |
|
|
$fdescriptions{$fname} =~ s/\n// unless ($console);
|
1478 |
|
|
$fdescriptions{$fname} =~ s/\n/\n<br>/g unless ($console);
|
1479 |
|
|
my @plist = split(/, ?/, $fparams{$fname});
|
1480 |
|
|
unless ($console) {
|
1481 |
|
|
$res .= qq|scopeparams["$fname"] = |.to_json(\@plist).";\n";
|
1482 |
|
|
$res .= qq|\$("#scopeaction").append(new Option("$fname", "$fname"));\n|;
|
1483 |
|
|
$res .= qq|scopemethods["$fname"] = "$helplist[0]";\n|;
|
1484 |
|
|
}
|
1485 |
|
|
}
|
1486 |
|
|
}
|
1487 |
|
|
}
|
1488 |
|
|
@fnames = sort (keys %fdescriptions);
|
1489 |
|
|
|
1490 |
|
|
unless ($console) {
|
1491 |
|
|
$res .= "\n</script>\n";
|
1492 |
|
|
$res .= <<END
|
1493 |
|
|
<div class="table-responsive" style="margin-top:1.5rem; noheight: 65vh; overflow-y: scroll;">
|
1494 |
|
|
<table class="table table-striped table-sm">
|
1495 |
|
|
<thead>
|
1496 |
|
|
<tr>
|
1497 |
|
|
<th>Name</th>
|
1498 |
|
|
<th>Method</th>
|
1499 |
|
|
<th>Parameters</th>
|
1500 |
|
|
<th style="width:60%;">Description</th>
|
1501 |
|
|
</tr>
|
1502 |
|
|
</thead>
|
1503 |
|
|
<tbody>
|
1504 |
|
|
END
|
1505 |
|
|
;
|
1506 |
|
|
foreach my $fname (@fnames) {
|
1507 |
|
|
my $fp = ($fparams{$fname}) ? "$fparams{$fname}" : '';
|
1508 |
|
|
$res .= <<END
|
1509 |
|
|
<tr>
|
1510 |
|
|
<td><a href="#" onclick="data.scopeaction=this.text; \$('#scopeaction option[value=$fname]').prop('selected', true); dofields();">$fname</a></td>
|
1511 |
|
|
<td>$fmethods{$fname}</td>
|
1512 |
|
|
<td>$fp</td>
|
1513 |
|
|
<td>$fdescriptions{$fname}</td>
|
1514 |
|
|
</tr>
|
1515 |
|
|
END
|
1516 |
|
|
;
|
1517 |
|
|
}
|
1518 |
|
|
$res .= <<END
|
1519 |
|
|
</tbody>
|
1520 |
|
|
</table>
|
1521 |
|
|
</div>
|
1522 |
|
|
END
|
1523 |
|
|
;
|
1524 |
|
|
$res .= qq|</body>\n</html>|;
|
1525 |
|
|
} else {
|
1526 |
|
|
foreach my $fname (@fnames) {
|
1527 |
|
|
my $fp = ($fparams{$fname}) ? "[$fparams{$fname}]" : '';
|
1528 |
|
|
$res .= <<END
|
1529 |
|
|
* $fname ($fmethods{$fname}) $fp $fdescriptions{$fname}
|
1530 |
|
|
END
|
1531 |
|
|
;
|
1532 |
|
|
}
|
1533 |
|
|
}
|
1534 |
|
|
|
1535 |
|
|
return $res;
|
1536 |
|
|
}
|
1537 |
|
|
|
1538 |
8d7785ff
|
Origo
|
sub getBackupSize {
|
1539 |
|
|
my ($subdir, $img, $imguser) = @_; # $subdir, if specified, includes leading slash
|
1540 |
|
|
$imguser = $imguser || $user;
|
1541 |
|
|
my $backupsize = 0;
|
1542 |
|
|
my @bdirs = ("$backupdir/$imguser$subdir/$img");
|
1543 |
|
|
if ($backupdir =~ /^\/stabile-backup\//) { # ZFS backup is enabled - we need to scan more dirs
|
1544 |
|
|
@bdirs = (
|
1545 |
|
|
"/stabile-backup/*/$imguser$subdir/" . shell_esc_chars($img),
|
1546 |
|
|
"/stabile-backup/*/.zfs/snapshot/*/$imguser$subdir/". shell_esc_chars($img)
|
1547 |
|
|
);
|
1548 |
|
|
}
|
1549 |
|
|
foreach my $bdir (@bdirs) {
|
1550 |
|
|
my $bdu = `/usr/bin/du -bs $bdir 2>/dev/null`;
|
1551 |
|
|
my @blines = split("\n", $bdu);
|
1552 |
|
|
# only count size from last snapshot
|
1553 |
|
|
my $bline = pop @blines;
|
1554 |
|
|
# foreach my $bline (@blines) {
|
1555 |
|
|
$bline =~ /(\d+)\s+/;
|
1556 |
|
|
$backupsize += $1;
|
1557 |
|
|
# }
|
1558 |
|
|
}
|
1559 |
|
|
return $backupsize;
|
1560 |
|
|
}
|
1561 |
|
|
|
1562 |
95b003ff
|
Origo
|
sub shell_esc_chars {
|
1563 |
|
|
my $str = shift;
|
1564 |
|
|
$str =~ s/([;<>\*\|`&\$!#\(\)\[\]\{\}:'" ])/\\$1/g;
|
1565 |
|
|
return $str;
|
1566 |
|
|
}
|