Project

General

Profile

Download (78.5 KB) Statistics
| Branch: | Revision:
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 48fcda6b Origo
# https://www.stabile.io/info/stabiledocs/licensing/stabile-open-source-license
7 95b003ff Origo
8
package Stabile::Users;
9
10
use Error qw(:try);
11
use Time::Local;
12
# use Time::HiRes qw( time );
13
use Config::Simple;
14
use Text::CSV_XS qw( csv );
15
use Proc::Daemon;
16
use MIME::Lite;
17
use File::Basename;
18
use Data::Password qw(:all);
19 4aef7ef6 hq
use Geo::IP;
20 95b003ff Origo
use lib dirname (__FILE__);
21
use Stabile;
22
23
$engineid = $Stabile::config->get('ENGINEID') || "";
24
$enginename = $Stabile::config->get('ENGINENAME') || "";
25
#$enginelinked = $Stabile::config->get('ENGINE_LINKED') || "";
26
$showcost = $Stabile::config->get('SHOW_COST') || "";
27
$cur = $Stabile::config->get('CURRENCY') || "USD";
28
$engineuser = $Stabile::config->get('ENGINEUSER') || "";
29
$externaliprangestart = $Stabile::config->get('EXTERNAL_IP_RANGE_START') || "";
30
$externaliprangeend = $Stabile::config->get('EXTERNAL_IP_RANGE_END') || "";
31
$proxyiprangestart = $Stabile::config->get('PROXY_IP_RANGE_START') || "";
32
$proxyiprangeend = $Stabile::config->get('PROXY_IP_RANGE_END') || "";
33
$proxygw = $Stabile::config->get('PROXY_GW') || "";
34
35
$uiuuid;
36
$uistatus;
37
$help = 0; # If this is set, functions output help
38
39
#our %options=();
40
# -a action -h help -u uuid -m match pattern -f full list, i.e. all users
41
# -v verbose, include HTTP headers -s impersonate subaccount -t target [uuid or image]
42
# -g args to gearman task
43
#Getopt::Std::getopts("a:hfu:g:m:vs:t:", \%options);
44
45
try {
46
    Init(); # Perform various initalization tasks
47
    process() if ($package);
48
49
} catch Error with {
50
    my $ex = shift;
51
    print header('text/html', '500 Internal Server Error') unless ($console);
52
    if ($ex->{-text}) {
53
        print "Got error: ", $ex->{-text}, " on line ", $ex->{-line}, "\n";
54
    } else {
55
        print "Status=ERROR\n";
56
    }
57
} finally {
58
};
59
60
1;
61
62
sub getObj {
63
    my %h = %{@_[0]};
64
    $console = 1 if $h{"console"};
65
    $api = 1 if $h{"api"};
66
    my $username = $h{"username"} || $h{"uuid"};
67
    my $obj;
68
    $action = $action || $h{'action'};
69
    if ($action=~ /engine$|updateclientui$|updateui$/) {
70
        $obj = \%h;
71
        $obj->{pwd} = $obj->{password} if ($obj->{password});
72
    } else {
73
        $obj = $register{$username};
74
        my %hobj = %{$register{$username}};
75
        $obj = \%hobj; # We do this to get around a weird problem with freeze...
76
        my @props = qw ( restorefile engineid enginename engineurl username user password pwd fullname email
77
            opemail alertemail phone opphone opfullname allowfrom allowinternalapi privileges accounts accountsprivileges
78 54401133 hq
            storagepools memoryquota storagequota nodestoragequota vcpuquota externalipquota rxquota txquota billto dnsdomains appstoreurl totpsecret );
79 95b003ff Origo
        foreach my $prop (@props) {
80
            if (defined $h{$prop}) {
81
                $obj->{$prop} = $h{$prop};
82
            }
83
        }
84
    }
85
    return $obj;
86
}
87
88
sub Init {
89
    # Tie database tables to hashes
90
    unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username'}, $Stabile::dbopts)) ) {return "Unable to access users register"};
91
92
    # simplify globals initialized in Stabile.pm
93
    $tktuser = $tktuser || $Stabile::tktuser;
94
    $user = $user || $Stabile::user;
95
96
    $fullname = $register{$user}->{'fullname'};
97
    $email = $register{$user}->{'email'};
98
    $opemail = $register{$user}->{'opemail'};
99
    $alertemail = $register{$user}->{'alertemail'};
100
    $phone = $register{$user}->{'phone'};
101
    $opphone = $register{$user}->{'opphone'};
102
    $opfullname = $register{$user}->{'opfullname'};
103
    $allowfrom = $register{$user}->{'allowfrom'};
104
    $allowinternalapi = $register{$user}->{'allowinternalapi'};
105
    $lastlogin = $register{$user}->{'lastlogin'};
106
    $lastloginfrom = $register{$user}->{'lastloginfrom'};
107
108
#    if ($register{$user}->{'lastlogin'} ne $tkt) {
109
#        $register{$user}->{'lastlogin'} = time;
110
#        $register{$user}->{'lastloginfrom'} = $ENV{'REMOTE_ADDR'};
111
#        $register{$user}->{'lasttkt'} = $tkt;
112
#    }
113
114
    $Stabile::userstoragequota = 0+ $register{$user}->{'storagequota'};
115
    $Stabile::usernodestoragequota = 0+ $register{$user}->{'nodestoragequota'};
116
    $usermemoryquota = 0+ $register{$user}->{'memoryquota'};
117
    $uservcpuquota = 0+ $register{$user}->{'vcpuquota'};
118
    $userexternalipquota = 0+ $register{$user}->{'externalipquota'};
119
    $userrxquota = 0+ $register{$user}->{'rxquota'};
120
    $usertxquota = 0+ $register{$user}->{'txquota'};
121
122
    $storagequota = $Stabile::userstoragequota || $defaultstoragequota;
123
    $nodestoragequota = $Stabile::usernodestoragequota || $defaultnodestoragequota;
124
    $memoryquota = $usermemoryquota || $defaultmemoryquota;
125
    $vcpuquota = $uservcpuquota || $defaultvcpuquota;
126
    $externalipquota = $userexternalipquota || $defaultexternalipquota;
127
    $rxquota = $userrxquota || $defaultrxquota;
128
    $txquota = $usertxquota || $defaulttxquota;
129
130
    # Create aliases of functions
131
    *header = \&CGI::header;
132
133
    *Unlinkengine = \&Linkengine;
134
    *Updateengine = \&Linkengine;
135
    *Saveengine = \&Linkengine;
136
    *Syncusers = \&Linkengine;
137
138
    *do_help = \&action;
139
    *do_show = \&do_uuidshow;
140
    *do_delete = \&do_remove;
141
    *do_tablelist = \&do_list;
142
    *do_billingstatus = \&do_billing;
143
    *do_usage = \&do_billing;
144
    *do_usagestatus = \&do_billing;
145
    *do_billingavgstatus = \&do_billing;
146
    *do_usageavgstatus = \&do_billing;
147
    *do_upgradeengine = \&privileged_action;
148
    *do_gear_upgradeengine = \&do_gear_action;
149
    *do_backupengine = \&privileged_action;
150
    *do_gear_backupengine = \&do_gear_action;
151
    *do_restoreengine = \&privileged_action;
152
    *do_gear_restoreengine = \&do_gear_action;
153
    *do_releasepressure = \&privileged_action_async;
154
    *do_gear_releasepressure = \&do_gear_action;
155
156
    *do_linkengine = \&privileged_action;
157
    *do_gear_linkengine = \&do_gear_action;
158
    *do_saveengine = \&privileged_action_async;
159
    *do_gear_saveengine = \&do_gear_action;
160
    *do_unlinkengine = \&privileged_action;
161
    *do_gear_unlinkengine = \&do_gear_action;
162
    *do_updateengine = \&privileged_action;
163
    *do_syncusers = \&privileged_action;
164
    *do_gear_updateengine = \&do_gear_action;
165
    *do_gear_syncusers = \&do_gear_action;
166
    *do_deleteentirely = \&privileged_action;
167
    *do_gear_deleteentirely = \&do_gear_action;
168
    *do_vent = \&privileged_action;
169
    *do_gear_vent = \&do_gear_action;
170
    *do_updateui = \&privileged_action;
171
    *do_gear_updateui = \&do_gear_action;
172
}
173
174
sub do_listaccounts {
175
    my ($uuid, $action, $obj) = @_;
176
    if ($help) {
177
        return <<END
178
GET:common:
179
List other user accounts current user has access to use and switch to. This is an internal method which includes html
180
specifically for use with Dojo.
181
END
182
    }
183
    my $common = $params{'common'};
184
    my %bhash;
185
    my @accounts = split(/,\s*/, $register{$tktuser}->{'accounts'});
186
    my @accountsprivs = split(/,\s*/, $register{$tktuser}->{'accountsprivileges'});
187
    for my $i (0 .. $#accounts) {
188
        $bhash{$accounts[$i]} = {
189
            id=>$accounts[$i],
190
            privileges=>$accountsprivs[$i] || 'r'
191
        } if ($register{$accounts[$i]}); # Only include accounts that exist on this engine
192
    };
193
    $bhash{$tktuser} = {id=>$tktuser, privileges=>$privileges};
194
    delete $bhash{$user};
195
    $bhash{'common'} = {id=>'common', privileges=>'--'} if ($common);
196
    my @bvalues = values %bhash;
197
    unshift(@bvalues, {id=>$user, privileges=>$privileges});
198
    my $logout = {privileges=>'', id=>'<span class="glyphicon glyphicon-log-out" aria-hidden="true" style="font-size:15px;color:#3c3c3c; vertical-align:top; margin-top:8px;"></span> Log out '};
199
    push(@bvalues, $logout) unless ($common);
200
    $postreply = "{\"identifier\": \"id\",\"label\": \"id\", \"items\":" . JSON::to_json(\@bvalues, {pretty=>1}) . "}";
201
    return $postreply;
202
}
203
204
sub do_listids {
205
    my ($uuid, $action, $obj) = @_;
206
    if ($help) {
207
        return <<END
208
GET::
209
List other user accounts current user has read access to. Call with flat=1 if you want a flat array.
210
END
211
    }
212
    require "$Stabile::basedir/cgi/images.cgi";
213
    my $backupdevice = Stabile::Images::Getbackupdevice('', 'getbackupdevice');
214
    my $imagesdevice = Stabile::Images::Getimagesdevice('', 'getimagesdevice');
215
    my $mounts = `cat /proc/mounts | grep zfs`;
216
    my %engine_h;
217
    my $zbackupavailable = ( (($mounts =~ /$backupdevice\/backup (\S+) zfs/) && ($mounts =~ /$imagesdevice\/images (\S+) zfs/) )?1:'');
218
    my $jsontext = qq|{"identifier": "id","label": "id", "items":[| .
219
              qq|{"id": "$user", "privileges": "$privileges", "userprivileges": "$dbprivileges", "tktuser": "$tktuser", |.
220
              qq|"storagequota": $storagequota, "nodestoragequota": $nodestoragequota, "memoryquota": $memoryquota, "vcpuquota": $vcpuquota, |.
221
              qq|"fullname": "$fullname", "email": "$email", "opemail": "$opemail", "alertemail": "$alertemail", |.
222
              qq|"phone": "$phone", "opphone": "$opphone", "opfullname": "$opfullname", "appstoreurl": "$appstoreurl", |.
223 71b897d3 hq
              qq|"allowfrom": "$allowfrom", "lastlogin": "$lastlogin", "lastloginfrom": "$lastloginfrom", "allowinternalapi": "$allowinternalapi", "billto": "$billto", |.
224 45cc3024 hq
              qq|"dnsdomain": "$dnsdomain", "appstoreurl": "$appstoreurl", |;
225 95b003ff Origo
226
    if ($isadmin && $engineid) {
227
        $engine_h{"engineid"} = $engineid;
228
        $engine_h{"engineuser"} = $engineuser;
229
        $engine_h{"externaliprangestart"} = $externaliprangestart;
230
        $engine_h{"externaliprangeend"} = $externaliprangeend;
231
        $engine_h{"proxyiprangestart"} = $proxyiprangestart;
232
        $engine_h{"proxyiprangeend"} = $proxyiprangeend;
233
        $engine_h{"proxygw"} = $proxygw;
234
235
        $engine_h{"disablesnat"} = $disablesnat;
236
        $engine_h{"imagesdevice"} = $imagesdevice;
237
        $engine_h{"backupdevice"} = $backupdevice;
238
239
        my $nodecfg = new Config::Simple("/etc/stabile/nodeconfig.cfg");
240
        my $readlimit = $nodecfg->param('VM_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
241
        my $writelimit = $nodecfg->param('VM_WRITE_LIMIT');
242
        my $iopsreadlimit = $nodecfg->param('VM_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
243
        my $iopswritelimit = $nodecfg->param('VM_IOPS_WRITE_LIMIT');
244
        $engine_h{"vmreadlimit"} = $readlimit;
245
        $engine_h{"vmwritelimit"} = $writelimit;
246
        $engine_h{"vmiopsreadlimit"} = $iopsreadlimit;
247
        $engine_h{"vmiopswritelimit"} = $iopswritelimit;
248
249
        $engine_h{"zfsavailable"} = $zbackupavailable;
250
        $engine_h{"downloadmasters"} = $downloadmasters;
251
    }
252 6fdc8676 hq
    if (-e "/var/www/stabile/static/img/logo-icon-" . $ENV{HTTP_HOST} . ".png") {
253
        $jsontext .= qq|"favicon": "/stabile/static/img/logo-icon-$ENV{HTTP_HOST}.png", |;
254
    }
255 c899e439 Origo
    $engine_h{"enginename"} = $enginename;
256
    $engine_h{"enginelinked"} = $enginelinked;
257 95b003ff Origo
    $jsontext .= "\"showcost\": \"$showcost\", ";
258
    $jsontext .= "\"externalipquota\": $externalipquota, \"rxquota\": $rxquota, \"txquota\": $txquota, ";
259
    $jsontext .= qq|"defaultstoragequota": $defaultstoragequota, "defaultnodestoragequota": $defaultnodestoragequota, "defaultmemoryquota": $defaultmemoryquota, "defaultvcpuquota": $defaultvcpuquota, |;
260
    $jsontext .= "\"defaultexternalipquota\": $defaultexternalipquota, \"defaultrxquota\": $defaultrxquota, \"defaulttxquota\": $defaulttxquota, ";
261
    $jsontext .= qq|"engine": | . to_json(\%engine_h);
262
    $jsontext .= "},  ";
263
264
    $jsontext .= "{\"id\": \"common\", \"privileges\": \"--\"," .
265
      "\"fullname\": \"--\", \"email\": \"--\"," .
266
      "\"storagequota\": 0, \"memoryquota\": 0, \"vcpuquota\": 0, \"externalipquota\": 0," .
267
      "\"rxquota\": 0, \"txquota\": 0}";
268
269
    $jsontext .= ", {\"id\": \"$billto\"}" if ($billto && $billto ne '--');
270
271
    foreach my $aid (keys %ahash) {
272
        my $privs = $ahash{$aid};
273
        $jsontext .= qq|, {"id": "$aid", "privileges": "$privs"}| unless ($aid eq $user || $aid eq $billto);
274
    }
275
276
    $jsontext .= "]}";
277
    # Create ui_update link in case we are logging in with a remotely generated ticket, i.e. not passing through login.cgi
278
    `/bin/ln -s ../ui_update.cgi ../cgi/ui_update/$user~ui_update.cgi` unless (-e "../cgi/ui_update/$user~ui_update.cgi");
279
    $postreply = to_json(from_json($jsontext), {pretty=>1});
280
    return $postreply;
281
}
282
283
sub do_listengines{
284
    my ($uuid, $action, $obj) = @_;
285
    if ($help) {
286
        return <<END
287
GET::
288
List other engines user has access to
289
END
290
    }
291
    if ($enginelinked) {
292
        require LWP::Simple;
293
        my $browser = LWP::UserAgent->new;
294
        $browser->agent('stabile/1.0b');
295
        $browser->protocols_allowed( [ 'http','https'] );
296
297
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
298
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
299
300
        $postreq->{'engineid'} = $engineid;
301 2a63870a Christian Orellana
        $postreq->{'user'} = $user;
302 95b003ff Origo
        $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
303 6372a66e hq
        $postreq->{'api'} = $params{api};
304
        $postreq->{'usertkt'} = $params{auth_tkt};
305 95b003ff Origo
306 48fcda6b Origo
        my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=listengines", $postreq)->content();
307 95b003ff Origo
        if ($content =~ /ERROR:(.+)"/) {
308
            $postreply = qq|{"identifier": "url", "label": "name", "items": [{"url": "# $1", "name": "$enginename"}]}|;
309
        } else {
310
            $postreply = qq|{"identifier": "url", "label": "name", "items": $content}|;
311
        }
312
    } else {
313
        $postreply = qq|{"identifier": "url", "label": "name", "items": [{"url": "#", "name": "$enginename"}]}|;
314
    }
315
    return $postreply;
316
}
317
318 6372a66e hq
sub do_listengineconfigs{
319
    my ($uuid, $action, $obj) = @_;
320
    if ($help) {
321
        return <<END
322
GET::
323
List configs of engines user has access to
324
END
325
    }
326
    if ($enginelinked) {
327
        require LWP::Simple;
328
        my $browser = LWP::UserAgent->new;
329
        $browser->agent('stabile/1.0b');
330
        $browser->protocols_allowed( [ 'http','https'] );
331
332
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
333
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
334
335
        $postreq->{'engineid'} = $engineid;
336
        $postreq->{'user'} = $user;
337
        $postreq->{'username'} = $params{username};
338
        $postreq->{'usertkt'} = $params{auth_tkt};
339
        $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
340
341
        my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=listengineconfigs", $postreq)->content();
342
        $postreply = $content;
343
    } else {
344
        $postreply = qq|{"status": "Error", "message": "Engine not linked"}|;
345
    }
346
    return $postreply;
347
}
348
349 95b003ff Origo
sub do_billing {
350
    my ($uuid, $action, $obj) = @_;
351
    if ($help) {
352
        return <<END
353
GET:uuid,username,month,startmonth,endmonth,format:
354 d24d9a01 hq
List usage data, optionally for specific server/system [uuid] or user [username]. May be called as usage, usagestatus or usageavgstatus.
355 95b003ff Origo
When called as "usage", format may be csv, in which case startmonth and endmonth may be specified.
356
END
357
    }
358
    my $buser = $params{'user'} || $params{'username'} || $user;
359
    my $bmonth = $params{'month'} || $month;
360
    $bmonth = substr("0$bmonth", -2);
361
    my $byear = $params{'year'} || $year;
362
    my $vcpu=0, $memory=0, $virtualsize=0, $nodevirtualsize=0, $backupsize=0, $externalip=0;
363
    my $rx = 0;
364
    my $tx = 0;
365
    my $vcpuavg = 0;
366
    my $externalipavg = 0;
367
    $uuid = '' if ($register{$uuid}); # check if $uuid was set to $user because no actual uuid passed
368
369
    if ($user eq $buser || index($privileges,"a")!=-1) {
370
         my %stats = collectBillingData( $uuid, $buser, $bmonth, $byear, $showcost );
371
         my $memoryquotagb = int(0.5 + 100*$memoryquota/1024)/100;
372
         my $storagequotagb = int(0.5 + 100*$storagequota/1024)/100;
373
         my $nodestoragequotagb = int(0.5 + 100*$nodestoragequota/1024)/100;
374
         my $irigo_cost = ($showcost?"showcost":"hidecost");
375
376
         if ($action eq "billing" || $action eq "usage") {
377
             if ($params{'format'} eq 'csv') {
378
                 $postreply = header("text/plain");
379
                 my $startmonth = $params{'startmonth'} || 1;
380
                 my $endmonth = $params{'endmonth'} || $bmonth;
381
                 my @vals;
382
                 for (my $i=$startmonth; $i<=$endmonth; $i++) {
383
                     my $m = substr("0$i", -2);
384
                     my %mstats = collectBillingData( $uuid, $buser, $m, $byear, $showcost );
385
                     push @vals, \%mstats;
386
                 }
387
                 csv(in => \@vals, out => \my $csvdata);
388
                 $postreply .= $csvdata;
389
             } else {
390
                 my $json_text = JSON::to_json(\%stats, {pretty => 1});
391
                 $postreply = "$json_text";
392
             }
393
394
         } elsif ($action eq "billingstatus" || $action eq "usagestatus") {
395 2a63870a Christian Orellana
             my $virtualsizegb = $stats{'virtualsize'};
396
             my $backupsizegb = $stats{'backupsize'};
397
             my $externalip = $stats{'externalip'};
398
             my $memorygb = $stats{'memory'};
399
             my $nodevirtualsizegb = $stats{'nodevirtualsize'};
400 95b003ff Origo
             $rx = $stats{'rx'};
401
             $tx = $stats{'tx'};
402
             $vcpu = $stats{'vcpu'};
403
404
             my $res;
405
             if ($params{'format'} eq 'html') {
406
                 $postreply .= header("text/html");
407
                 $res .= qq[<tr><th>Ressource</th><th>Quantity</th><th class="$irigo_cost">Cost/month</th><th>Quota</th></tr>];
408
                 $res .= qq[<tr><td>vCPU's:</td><td align="right">$vcpu</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$vcpu*$vcpuprice) . qq[</td><td align="right">$vcpuquota</td></tr>];
409
                 $res .= qq[<tr><td>Memory:</td><td align="right">$memorygb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$memorygb*$memoryprice) . qq[</td><td align="right">$memoryquotagb GB</td></tr>];
410
                 $res .= qq[<tr><td>Shared storage:</td><td align="right">$virtualsizegb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$virtualsizegb*$storageprice) . qq[</td><td align="right">$storagequotagb GB</td></tr>];
411
                 $res .= qq[<tr><td>Node storage:</td><td align="right">$nodevirtualsizegb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$nodevirtualsizegb*$nodestorageprice) . qq[</td><td align="right">$nodestoragequotagb GB</td></tr>];
412 8d7785ff Origo
                 $res .= qq[<tr><td>Backup storage (est.):</td><td align="right">$backupsizegb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$backupsizegb*$storageprice) . qq[</td><td align="right">&infin;</td></tr>];
413 95b003ff Origo
                 $res .= qq[<tr><td>External IP addresses:</td><td align="right">$externalip</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$externalip*$externalipprice) . qq[</td><td align="right">$externalipquota</td></tr>];
414
                 if (!$uuid) {
415 8d7785ff Origo
                     $res .= qq[<tr><td>Network traffic out:</td><td align="right">] . $rx . qq[ GB</td><td align="right" class="$irigo_cost">$cur 0</td><td align="right">] . int(0.5 + $rxquota/1024/1024) . qq[ GB</td></tr>];
416
                     $res .= qq[<tr><td>Network traffic in:</td><td align="right">] . $tx . qq[ GB</td><td align="right" class="$irigo_cost">$cur 0</td><td align="right">] . int(0.5 + $txquota/1024/1024) . qq[ GB</td></tr>];
417 95b003ff Origo
                 }
418
419
                 $res =~ s/-1/&infin;/g;
420
                 $res =~ s/>0 .B<\/td><\/tr>/>&infin;<\/td><\/tr>/g;
421
                 $postreply .= qq[<table cellspacing="0" noframe="void" norules="rows" class="systemTables">$res</table>];
422
             } else {
423
                 my $bill = {
424
                     vcpus => {quantity => $vcpu, quota => $vcpuquota},
425
                     memory => {quantity => $memorygb, unit => 'GB', quota => $memoryquotagb},
426
                     shared_storage => {quantity => $virtualsizegb, unit => 'GB', quota => $storagequotagb},
427
                     node_storage => {quantity => $nodevirtualsizegb, unit => 'GB', quota => $nodestoragequotagb},
428
                     backup_storage => {quantity => $backupsizegb, unit => 'GB'},
429
                     external_ips => {quantity => $externalip, quota => $externalipquota},
430 8d7785ff Origo
                     network_traffic_out => {quantity => $rx, unit => 'GB', quota => int(0.5 + $rxquota/1024/1024)},
431
                     network_traffic_in => {quantity => $tx, unit => 'GB', quota => int(0.5 + $txquota/1024/1024)}
432 95b003ff Origo
                 };
433
                 if ($showcost) {
434
                     $bill->{vcpus}->{cost} = int(0.5+$vcpu*$vcpuprice);
435
                     $bill->{memory}->{cost} = int(0.5+$memorygb*$memoryprice);
436
                     $bill->{shared_storage}->{cost} = int(0.5+$virtualsizegb*$storageprice);
437
                     $bill->{node_storage}->{cost} = int(0.5+$nodevirtualsizegb*$nodestorageprice);
438
                     $bill->{backup_storage}->{cost} = int(0.5+$backupsizegb*$storageprice);
439
                     $bill->{external_ips}->{cost} = int(0.5+$externalip*$externalipprice);
440
                     $bill->{currency} = $cur;
441
                     $bill->{username} = $buser;
442
                 }
443
                 $postreply .= to_json($bill, {pretty=>1});
444
             }
445
         } elsif ($action eq "billingavgstatus" || $action eq "usageavgstatus") {
446 2a63870a Christian Orellana
             my $virtualsizeavggb = $stats{'virtualsizeavg'};
447
             my $backupsizeavggb = $stats{'backupsizeavg'};
448
             my $memoryavggb = $stats{'memoryavg'};
449
             my $nodevirtualsizeavggb = $stats{'nodevirtualsizeavg'};
450 95b003ff Origo
             $vcpuavg = $stats{'vcpuavg'};
451
             $externalipavg = $stats{'externalipavg'};
452
             $rx = $stats{'rx'};
453
             $tx = $stats{'tx'};
454
             if ($params{'format'} eq 'html') {
455
                 $postreply .= header("text/html");
456
                 my $res;
457
                 $res .= qq[<tr><th>Ressource</th><th>Quantity</th><th class="$irigo_cost">Cost/month</th><th>Quota</th></tr>];
458
                 $res .= qq[<tr><td>vCPU's:</td><td align="right">] . int(0.5+100*$vcpuavg)/100 . qq[</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$vcpuavg*$vcpuprice) . qq[</td><td align="right">$vcpuquota</td></tr>];
459
                 $res .= qq[<tr><td>Memory:</td><td align="right">$memoryavggb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$memoryavggb*$memoryprice) . qq[</td><td align="right">$memoryquotagb GB</td></tr>];
460
                 $res .= qq[<tr><td>Shared storage:</td><td align="right">$virtualsizeavggb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$virtualsizeavggb*$storageprice) . qq[</td><td align="right">$storagequotagb GB</td></tr>];
461
                 $res .= qq[<tr><td>Node storage:</td><td align="right">$nodevirtualsizeavggb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$nodevirtualsizeavggb*$nodestorageprice) . qq[</td><td align="right">$nodestoragequotagb GB</td></tr>];
462 8d7785ff Origo
                 $res .= qq[<tr><td>Backup storage (est.):</td><td align="right">$backupsizeavggb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$backupsizeavggb*$storageprice) . qq[</td><td align="right">&infin;</td></tr>];
463 95b003ff Origo
                 $res .= qq[<tr><td>External IP addresses:</td><td align="right">] . int(0.5+100*$externalipavg)/100 . qq[</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$externalipavg*$externalipprice) . qq[</td><td align="right">$externalipquota</td></tr>];
464
                 $res .= qq[<tr><td>Network traffic in:</td><td align="right">] . int(0.5 + $rx) . qq[ GB</td><td align="right" class="$irigo_cost">$cur 0</td><td align="right">] . int(0.5 + $rxquota/1024/1024) . qq[ GB</td></tr>];
465
                 $res .= qq[<tr><td>Network traffic out:</td><td align="right">] . int(0.5 + $tx) . qq[ GB</td><td align="right" class="$irigo_cost">$cur 0</td><td align="right">] . int(0.5 + $txquota/1024/1024) . qq[ GB</td></tr>];
466
467
                 $res =~ s/-1/&infin;/g;
468
                 $res =~ s/>0 .B<\/td><\/tr>/>&infin;<\/td><\/tr>/g;
469
                 $postreply .= qq[<table cellspacing="0" noframe="void" norules="rows" class="systemTables">$res</table>];
470
             } else {
471
                 my $bill = {
472
                     vcpus => {quantity => $vcpuavg, quota => $vcpuquota},
473
                     memory => {quantity => $memoryavggb, unit => 'GB', quota => $memoryquotagb},
474
                     shared_storage => {quantity => $virtualsizeavggb, unit => 'GB', quota => $storagequotagb},
475
                     node_storage => {quantity => $nodevirtualsizeavggb, unit => 'GB', quota => $nodestoragequotagb},
476
                     backup_storage => {quantity => $backupsizeavggb, unit => 'GB'},
477
                     external_ips => {quantity => $externalipavg, quota => $externalipquota},
478
                     network_traffic_out => {quantity => int(0.5 + $rx), unit => 'GB', quota => int(0.5 + $rxquota/1024/1024)},
479
                     network_traffic_in => {quantity => int(0.5 + $tx), unit => 'GB', quota => int(0.5 + $txquota/1024/1024)}
480
                 };
481
                 if ($showcost) {
482
                     $bill->{vcpus}->{cost} = int(0.5+$vcpuavg*$vcpuprice);
483
                     $bill->{memory}->{cost} = int(0.5+$memoryavggb*$memoryprice);
484
                     $bill->{shared_storage}->{cost} = int(0.5+$virtualsizeavggb*$storageprice);
485
                     $bill->{node_storage}->{cost} = int(0.5+$nodevirtualsizeavggb*$nodestorageprice);
486
                     $bill->{backup_storage}->{cost} = int(0.5+$backupsizeavggb*$storageprice);
487
                     $bill->{external_ips}->{cost} = int(0.5+$externalipavg*$externalipprice);
488
                     $bill->{currency} = $cur;
489
                     $bill->{username} = $buser;
490
                 }
491
                 $postreply .= to_json($bill, {pretty=>1});
492
             }
493
        }
494
    } else {
495
        $postreply .= "Status=ERROR no privileges!!\n";
496
    }
497
    return $postreply;
498
}
499
500
sub do_listenginebackups {
501
    my ($uuid, $action, $obj) = @_;
502
    if ($help) {
503
        return <<END
504
GET::
505 48fcda6b Origo
List the backups of this engine's configuration in the registry.
506 95b003ff Origo
END
507
    }
508
    if ($enginelinked) {
509
        require LWP::Simple;
510
        my $browser = LWP::UserAgent->new;
511
        $browser->agent('stabile/1.0b');
512
        $browser->protocols_allowed( [ 'http','https'] );
513
514
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
515
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
516
517
        $postreq->{'engineid'} = $engineid;
518
        $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
519
520 48fcda6b Origo
        my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=listbackups", $postreq)->content();
521 95b003ff Origo
        if ($content =~ /\[\]/) {
522
            $postreply = qq|{"identifier": "path", "label": "name", "items": [{"path": "#", "name": "No backups"}]}|;
523
        } else {
524
            $postreply = qq|{"identifier": "path", "label": "name", "items": $content}|;
525
        }
526
    } else {
527
        $postreply = qq|{"identifier": "path", "label": "name", "items": [{"path": "#", "name": "Engine not linked"}]}|;
528
    }
529
    return $postreply;
530
}
531
532
sub Backupengine {
533
    my ($uuid, $action, $obj) = @_;
534
    if ($help) {
535
        return <<END
536
GET::
537 48fcda6b Origo
Backup this engine's configuration to the registry.
538 95b003ff Origo
END
539
    }
540
    my $backupname = "$enginename.$engineid.$pretty_time";
541
    $backupname =~ tr/:/-/; # tar has a problem with colons in filenames
542
    if (-e "/tmp/$backupname.tgz") {
543
        $postreply .= "Status=ERROR Engine is already being backed up";
544
    } else {
545
        $res .= `mysqldump --ignore-table=steamregister.nodeidentities steamregister > /etc/stabile/steamregister.sql`;
546
        $res .= `cp /etc/apache2/conf-available/auth_tkt_cgi.conf /etc/stabile`;
547
        $res .= `cp /etc/apache2/ssl/*.crt /etc/stabile`;
548
        $res .= `cp /etc/apache2/ssl/*.pem /etc/stabile`;
549
        $res .= `cp /etc/apache2/ssl/*.key /etc/stabile`;
550
        $res .= `cp /etc/hosts.allow /etc/stabile`;
551
        $res .= `cp /etc/mon/mon.cf /etc/stabile`;
552
553
        # copy default node configuration to /etc/stabile
554
        unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access identity register"};
555
556
        my $defaultpath = $idreg{'default'}->{'path'} . "/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
557
        $res .= `cp $defaultpath /etc/stabile`;
558
559
        # Make tarball
560
        my $cmd = qq[(cd /etc/stabile; /bin/tar -czf "/tmp/$backupname.tgz" * 2>/dev/null)];
561
        $res .= `$cmd`;
562
563
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
564
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
565
        my $enginetkthash = Digest::SHA::sha512_hex($tktkey);
566
567 48fcda6b Origo
        my $res = `/usr/bin/curl -k -F engineid=$engineid -F enginetkthash=$enginetkthash -F filedata=@"/tmp/$backupname.tgz" https://www.stabile.io/irigo/engine.cgi?action=backup`;
568 95b003ff Origo
        if ($res =~ /OK: $backupname.tgz received/) {
569 48fcda6b Origo
            $postreply .= "Status=OK Engine configuration saved to the registry";
570
            $main::syslogit->($user, "info", "Engine configuration saved to the registry");
571 95b003ff Origo
            unlink("/tmp/$backupname.tgz");
572
        } else {
573 48fcda6b Origo
            $postreply .= "Status=ERROR Problem backing configuration up to the registry\n$res\n";
574 95b003ff Origo
        }
575
    }
576
    return $postreply;
577
}
578
579
sub Upgradeengine {
580
    my ($uuid, $action, $obj) = @_;
581
    if ($help) {
582
        return <<END
583
GET::
584 48fcda6b Origo
Try to upgrade this engine to latest release from the registry
585 95b003ff Origo
END
586
    }
587 4aef7ef6 hq
    $postreply = "Status=OK Requesting upgrade of Stabile\n";
588
    print header("text/plain"), $postreply;
589 95b003ff Origo
    `echo "UPGRADE=1" >> /etc/stabile/config.cfg` unless ( `grep ^UPGRADE=1 /etc/stabile/config.cfg`);
590 4aef7ef6 hq
    my $cmd = "echo 'sleep 5 ; /usr/bin/pkill pressurecontrol' | at now";
591
    system($cmd);
592
    exit 0;
593 95b003ff Origo
}
594
595
sub do_billengine {
596
    my ($uuid, $action, $obj) = @_;
597
    if ($help) {
598
        return <<END
599
GET::
600 48fcda6b Origo
Submit billing data for this engine to the registry.
601 95b003ff Origo
END
602
    }
603
    require LWP::Simple;
604
    my $browser = LWP::UserAgent->new;
605
    $browser->agent('stabile/1.0b');
606
    $browser->protocols_allowed( [ 'http','https'] );
607
608
    my $bmonth = $params{'month'} || $month;
609
    $bmonth = substr("0$bmonth", -2);
610
    my $byear = $params{'year'} || $year;
611
    $showcost = 1;
612
613
    my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
614
    my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
615
    my $tkthash = Digest::SHA::sha512_hex($tktkey);
616
617
    my $postreq = ();
618
    my %bill;
619
    my @regvalues = values %register; # Sort by id
620
    foreach my $valref (@regvalues) {
621 d24d9a01 hq
        my $cuser = $valref->{'username'};
622
        my %stats = collectBillingData( '', $cuser, $bmonth, $byear, $showcost );
623
        $bill{"$cuser-$byear-$bmonth"} = \%stats;
624 95b003ff Origo
    }
625
    $postreq->{'engineid'} = $engineid;
626
    $postreq->{'enginetkthash'} = $tkthash;
627
    $postreq->{'keywords'} = JSON::to_json(\%bill, {pretty=>1});
628 48fcda6b Origo
    my $url = "https://www.stabile.io/irigo/engine.cgi";
629 95b003ff Origo
    $content = $browser->post($url, $postreq)->content();
630
    $postreply = "Status=OK Billed this engine ($engineid)\n";
631
    $postreply .= "$postreq->{'keywords'}\n$content";
632
    return $postreply;
633
}
634
635
sub Linkengine {
636
    my ($uuid, $action, $obj) = @_;
637
    if ($help) {
638
        return <<END
639
PUT:username,password,engineid,enginename,engineurl:
640 48fcda6b Origo
Links engine to the registry
641 95b003ff Origo
END
642
    }
643
    return "Status=Error Not allowed\n" unless ($isadmin || ($user eq $engineuser));
644
    my $linkaction = 'update';
645
    $linkaction = 'link' if ($action eq 'linkengine');
646
    $linkaction = 'unlink' if ($action eq 'unlinkengine');
647
    $linkaction = 'update' if ($action eq 'updateengine');
648
    $linkaction = 'update' if ($action eq 'syncusers');
649
650
    require LWP::Simple;
651
    my $browser = LWP::UserAgent->new;
652
    $browser->agent('stabile/1.0b');
653
    $browser->protocols_allowed( [ 'http','https'] );
654
655
    my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
656
    my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
657
658
    my $postreq = ();
659
    $postreq->{'user'} = $user || $obj->{'username'};
660
    $postreq->{'engineid'} = $obj->{'engineid'} || $engineid;
661
    $postreq->{'pwd'} = $obj->{'pwd'} if ($obj->{'pwd'});
662
    $postreq->{'enginename'} = $obj->{'enginename'} if ($obj->{'enginename'});
663
    $postreq->{'engineurl'} = $obj->{'engineurl'} if ($obj->{'engineurl'});
664
    if ($tktkey) {
665
        if ($action eq 'linkengine') {
666 48fcda6b Origo
            $main::syslogit->($user, "info", "Linking engine with the registry");
667 95b003ff Origo
            $postreq->{'enginetktkey'} = $tktkey;
668
        } else {
669
            $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
670
        }
671
    }
672 48fcda6b Origo
    if ($action eq "saveengine") { # Save request from the registry - don't post back
673
        # Pressurecontrol reads new configuration data from the registry, simply reload it
674 95b003ff Origo
        my $pressureon = !(`systemctl is-active pressurecontrol` =~ /inactive/);
675
        $postreply = ($pressureon)? "Status=OK Engine updating...\n":"Status=OK Engine not updating because pressurecontrol not active\n";
676
        $postreply .= `systemctl restart pressurecontrol` if ($pressureon);
677
    } else {
678
        my $res;
679
        my $cfg = new Config::Simple("/etc/stabile/config.cfg");
680
        if ($action eq 'linkengine' || $action eq 'syncusers') {
681 48fcda6b Origo
            # Send engine users to the registry
682 95b003ff Origo
            my @vals = values %register;
683
            my $json = JSON::to_json(\@vals);
684
            $json =~ s/null/""/g;
685
            $json = URI::Escape::uri_escape($json);
686
            $postreq->{'POSTDATA'} = $json;
687
        }
688
        if ($action eq 'linkengine' || $action eq 'updateengine') {
689
            # Update name in config file
690
            if ($postreq->{'enginename'} && $cfg->param("ENGINENAME") ne $postreq->{'enginename'}) {
691
                $cfg->param("ENGINENAME", $postreq->{'enginename'});
692
                $cfg->save();
693
            }
694 48fcda6b Origo
            # Send entire engine config file to the registry
695 95b003ff Origo
            my %cfghash = $cfg->vars();
696
            foreach my $param (keys %cfghash) {
697
                $param =~ /default\.(.+)/; # Get rid of default. prefix
698
                if ($1) {
699
                    my $k = $1;
700
                    my @cvals = $cfg->param($param);
701
                    my $cval = join(", ", @cvals);
702
                    $postreq->{$k} = URI::Escape::uri_escape($cval);
703
                }
704
            }
705 48fcda6b Origo
            # Send entire engine piston config file to the registry
706 95b003ff Origo
            my $nodeconfigfile = "/mnt/stabile/tftp/bionic/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
707
            if (-e $nodeconfigfile) {
708
                my $pistoncfg = new Config::Simple($nodeconfigfile);
709
                %cfghash = $pistoncfg->vars();
710
                foreach my $param (keys %cfghash) {
711
                    $param =~ /default\.(.+)/; # Get rid of default. prefix
712
                    if ($1) {
713
                        my $k = $1;
714
                        my @cvals = $pistoncfg->param($param);
715
                        my $cval = join(", ", @cvals);
716
                        $postreq->{$k} = URI::Escape::uri_escape($cval);
717
                    }
718
                }
719
            }
720
        }
721
        if ($linkaction eq 'link' || $enginelinked) {
722 48fcda6b Origo
            my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=$linkaction", $postreq)->content();
723 95b003ff Origo
            if ($content =~ /(Engine linked|Engine not linked|Engine unlinked|Engine updated|Unknown engine|Invalid credentials .+\.)/i) {
724
                $res = "Status=OK $1";
725
                my $linked = 1;
726
                $linked = 0 unless ($content =~ /Engine linked/i || $content =~ /Engine updated/i);
727
                $cfg->param("ENGINE_LINKED", $linked);
728
                $cfg->save();
729 48fcda6b Origo
            } elsif ($action eq 'syncusers' || $action eq 'linkengine') { # If we send user list to the registry we get merged list back
730 95b003ff Origo
                if ($content =~ /^\[/) { # Sanity check to see if we got json back
731
                    $res .= "Status=OK Engine linked\n" if ($action eq 'linkengine');
732 48fcda6b Origo
                    # Update engine users with users from the registry
733
                    $res .= updateEngineUsers($content);
734
                    $res .= "Status=OK Users synced with registry\n";
735 95b003ff Origo
                    $main::updateUI->({ tab => 'users', type=>'update', user=>$user});
736
                }
737 48fcda6b Origo
                $res .= "$content" unless ($res =~ /Status=OK/); # Only add if there are problems
738 95b003ff Origo
            }
739
            $postreply = $res;
740
            $content =~ s/\n/ - /;
741
            $res =~ s/\n/ - /;
742 64c667ea hq
        #    $main::syslogit->($user, "info", "$content");
743
            $main::syslogit->($user, "info", "Synced users");
744 95b003ff Origo
        } else {
745
            $postreply .= "Status=OK Engine not linked, saving name\n";
746
        }
747
    }
748
    return $postreply;
749
}
750
751
sub Releasepressure {
752
    my ($uuid, $action, $obj) = @_;
753
    if ($help) {
754
        return <<END
755
GET::
756
Restarts pressurecontrol.
757
END
758
    }
759
    my $res;
760
    unless (`systemctl is-active pressurecontrol` =~ /inactive/) {
761
        my $daemon = Proc::Daemon->new(
762
            work_dir => '/usr/local/bin',
763
            exec_command => "systemctl restart pressurecontrol"
764
        ) or do {$postreply .= "Status=ERROR $@\n";};
765
        my $pid = $daemon->Init();
766
#        $res = `systemctl restart pressurecontrol`;
767
        return "Status=OK Venting...\n";
768
    } else {
769
        return "Status=OK Not venting\n";
770
    }
771
}
772
773
sub do_enable {
774
    my ($uuid, $action, $obj) = @_;
775
    if ($help) {
776
        return <<END
777
GET:username:
778
Enable a user.
779
END
780
    }
781
    my $username = $obj->{'username'};
782 71b897d3 hq
    return unless ($username);
783 95b003ff Origo
    if ($isadmin || ($user eq $engineuser)) {
784 71b897d3 hq
        # Create user on this engine if not yet created
785
        do_save($username, 'save', $obj);
786 95b003ff Origo
        my $uprivileges = $register{$username}->{'privileges'};
787
        $uprivileges =~ s/d//;
788
        $uprivileges .= 'n' unless ($uprivileges =~ /n/);# These are constant sources of problems - enable by default when enabling users to alleviate situation
789
        $register{$username}->{'privileges'} = $uprivileges;
790
        $register{$username}->{'allowinternalapi'} = 1;
791
        $postreply .= "Status=OK User $username enabled\n";
792
    } else {
793
        $postreply .= "Status=ERROR Not allowed\n";
794
    }
795
    $uiuuid = $username;
796
    return $postreply;
797
}
798
799
sub do_disable {
800
    my ($uuid, $action, $obj) = @_;
801
    if ($help) {
802
        return <<END
803
GET:username:
804
Disable a user.
805
END
806
    }
807
    my $username = $obj->{'username'};
808
    if ($isadmin || ($user eq $engineuser)) {
809
        my $uprivileges = $register{$username}->{'privileges'};
810
        $uprivileges .= 'd' unless ($uprivileges =~ /d/);
811
        $register{$username}->{'privileges'} = $uprivileges;
812
        $postreply .= "Stream=OK User $username disabled, halting servers...\n";
813
        require "$Stabile::basedir/cgi/servers.cgi";
814
        $Stabile::Servers::console = 1;
815
        $postreply .= Stabile::Servers::destroyUserServers($username,1);
816
        `/bin/rm /tmp/$username~*.tasks`;
817
    } else {
818
        $postreply .= "Status=ERROR Not allowed\n";
819
    }
820
    $uiuuid = $username;
821
    return $postreply;
822
}
823
824
sub Updateui {
825
    my ($uuid, $action, $obj) = @_;
826
    if ($help) {
827
        return <<END
828
GET:username,message,tab:
829
Update the UI for given user if logged into UI.
830
END
831
    }
832
    my $username = $obj->{'username'} || $user;
833
    my $message = $obj->{'message'};
834
    my $tab = $obj->{'tab'} || 'home';
835
    if ($isadmin || ($username eq $user) || ($user eq $engineuser)) {
836
        $postreply = $main::updateUI->({ tab => $tab, user => $username, message =>$message, type=>'update'});
837
    } else {
838
        $postreply = "Status=ERROR Not allowed\n";
839
    }
840
}
841
842
sub do_updateclientui {
843
    my ($uuid, $action, $obj) = @_;
844
    if ($help) {
845
        return <<END
846 6fdc8676 hq
GET:username,message,tab,type:
847 95b003ff Origo
Update the UI for given user if logged into UI.
848
END
849
    }
850
    my $username = $obj->{'username'} || $user;
851
    my $message = $obj->{'message'};
852
    my $tab = $obj->{'tab'} || 'home';
853 6fdc8676 hq
    my $type= $obj->{'type'} || 'update';
854 95b003ff Origo
    if ($isadmin || ($username eq $user) || ($user eq $engineuser)) {
855 6fdc8676 hq
        $postreply = $main::updateUI->({ tab => $tab, user => $username, message =>$message, type=>$type});
856 95b003ff Origo
    } else {
857
        $postreply = "Status=ERROR Not allowed\n";
858
    }
859
}
860
861
sub Vent {
862
    my ($uuid, $action, $obj) = @_;
863
    if ($help) {
864
        return <<END
865
GET::
866
Restart pressurecontrol.
867
END
868
    }
869
    `systemctl restart pressurecontrol`;
870
    $postreply = "Status=OK Restarting pressurecontrol\n";
871
    return $postreply;
872
}
873
874
sub Deleteentirely {
875
    my ($uuid, $action, $obj) = @_;
876
    if ($help) {
877
        return <<END
878
GET:username:
879
Deletes a user and all the user's servers, images, networks etc. Warning: This destroys data
880
END
881
    }
882
    my $username = $obj->{'username'};
883 6372a66e hq
    my $reply = "Status=OK Removed $username";
884 95b003ff Origo
    if (($isadmin || ($user eq $engineuser)) && $register{$username} && !($register{$username}->{'privileges'} =~ /a/) && !($username eq $engineuser)) {
885
        #Never delete admins
886
        my @dusers = ($username);
887
        # Add list of subusers - does not look like a good idea
888
        # foreach my $u (values %register) {
889
        #     push @dusers, $u->{'username'} if ($u->{'billto'} && $u->{'billto'} eq $username);
890
        # };
891
892
        foreach my $uname (@dusers) {
893 6372a66e hq
            if ($register{$uname}->{privileges} =~ /a/) { #Never delete admins
894
                $postreply .= "Stream=OK Not deleting user $uname - demote before deleting!\n";
895
                next;
896
            }
897 95b003ff Origo
            $main::updateUI->({ tab => 'users', type=>'update', user=>$user, username=>$username, status=>'deleting'});
898
899
            $postreply .= "Stream=OK Deleting user $uname and all associated data!!!\n";
900 6372a66e hq
            $main::syslogit->($user, "info", "Deleting user $uname and all associated data");
901 95b003ff Origo
902
            require "$Stabile::basedir/cgi/servers.cgi";
903
            $Stabile::Servers::console = 1;
904 6372a66e hq
            $Stabile::Servers::isadmin = $isadmin;
905 95b003ff Origo
            require "$Stabile::basedir/cgi/systems.cgi";
906
            $Stabile::Systems::console = 1;
907 6372a66e hq
            $Stabile::Systems::isadmin = $isadmin;
908 95b003ff Origo
            Stabile::Systems::removeusersystems($uname);
909
            Stabile::Servers::removeUserServers($uname);
910
911
            require "$Stabile::basedir/cgi/images.cgi";
912
            $Stabile::Images::console = 1;
913
            $postreply .= Stabile::Images::removeUserImages($uname);
914
915
            require "$Stabile::basedir/cgi/networks.cgi";
916
            $Stabile::Networks::console = 1;
917 6372a66e hq
            $Stabile::Networks::isadmin = $isadmin;
918 95b003ff Origo
            Stabile::Networks::Removeusernetworks($uname);
919
            remove($uname);
920
            $reply = "$reply\n$postreply";
921
922
            # Also remove billing data, so next user with same username does not get old billing data
923
            `echo "delete from billing_domains where usernodetime like '$uname-%';" | mysql steamregister`;
924
            `echo "delete from billing_images where userstoragepooltime like '$uname-%';" | mysql steamregister`;
925
            `echo "delete from billing_networks where useridtime like '$uname-%';" | mysql steamregister`;
926
        }
927 48fcda6b Origo
        $main::updateUI->({tab => 'users', type=>'update', user=>$user});
928 95b003ff Origo
929
    } else {
930
        $postreply .= "Stream=ERROR Cannot delete user $username - you cannot delete administrators!\n";
931
        $reply = $postreply;
932
    }
933
    return $reply;
934
}
935
936
sub do_save {
937 71b897d3 hq
    my ($username, $action, $obj) = @_;
938 95b003ff Origo
    if ($help) {
939
        return <<END
940 a439a9c4 hq
POST:username, password, privileges, fullname, email, opemail, alertemail, phone, opphone, opfullname, allowfrom, allowinternalapi, accounts, accountsprivileges, storagepools, memoryquota, storagequota, nodestoragequota, vcpuquota, externalipquota, rxquota, txquota:
941 71b897d3 hq
Saves a user. If [username] does not exist, it is created if privileges allow this. [password] can be plaintext or a SHA256 hash.
942 95b003ff Origo
END
943
    }
944 71b897d3 hq
    $username = $username || $obj->{"username"};
945 95b003ff Origo
    unless ($username && (($user eq $username) || $isadmin || ($user eq $engineuser))) {
946
        $postreply = "Status=ERROR Please provide a valid username\n";
947
        return $postreply;
948
    }
949
    my $password = '';
950
    my $reguser = $register{$username};
951
    if ($obj->{"password"} && $obj->{"password"} ne '--') {
952
        if (length $obj->{'password'} == 86) {
953
            $password = $obj->{"password"}; # This is already encoded
954
        } else {
955
            $password = $obj->{"password"};
956
            $MAXLEN = 20;
957
            my $msg = IsBadPassword($password);
958
            if ($msg) {
959
                $postreply = "Status=Error $msg - please choose a stronger password\n";
960
                $postmsg = "$msg - please choose a stronger password";
961
                return $postreply;
962
            } else {
963
                $password = Digest::SHA::sha512_base64($password);
964
            }
965
        }
966
    } else {
967
        $password = $reguser->{'password'};
968
    }
969
    my $fullname = $obj->{"fullname"} || $reguser->{'fullname'};
970
    my $email = $obj->{"email"} || $reguser->{'email'};
971
    my $opemail = $obj->{"opemail"} || $reguser->{'opemail'};
972
    my $alertemail = $obj->{"alertemail"} || $reguser->{'alertemail'};
973
    my $phone = $obj->{"phone"} || $reguser->{'phone'};
974
    my $opphone = $obj->{"opphone"} || $reguser->{'opphone'};
975
    my $opfullname = $obj->{"opfullname"} || $reguser->{'opfullname'};
976 4aef7ef6 hq
    my $allowfrom = $obj->{"allowfrom"};
977 705b5366 hq
    my $totpsecret = $reguser->{'totpsecret'};
978
    $totpsecret = $obj->{"totpsecret"} if (defined $obj->{"totpsecret"});
979 95b003ff Origo
    my $allowinternalapi = $obj->{"allowinternalapi"} || $reguser->{'allowinternalapi'};
980
981 4aef7ef6 hq
    if (defined $obj->{"allowfrom"}) {
982 95b003ff Origo
        my @allows = split(/(,\s*|\s+)/, $allowfrom);
983
        $allowfrom = '';
984 4aef7ef6 hq
        my %allowshash;
985 95b003ff Origo
        foreach my $ip (@allows) {
986 4aef7ef6 hq
            $allowshash{"$1$2"} = 1 if ($ip =~ /(\d+\.\d+\.\d+\.\d+)(\/\d+)?/);
987
            if ($ip =~ /\w\w/) { # Check if we are dealing with a country code
988
                $ip = uc $ip;
989
                my $geoip = Geo::IP->new(GEOIP_MEMORY_CACHE);
990
                my $tz = $geoip->time_zone($ip, '');
991
                $allowshash{$ip} = 1 if ($tz); # We have a valid country code
992
            }
993 95b003ff Origo
        }
994 4aef7ef6 hq
        $allowfrom = join(", ", sort(keys %allowshash));
995 95b003ff Origo
    }
996
997
    my $uprivileges = $reguser->{'privileges'};
998
    my $uaccounts = $reguser->{'accounts'};
999
    my $uaccountsprivileges = $reguser->{'accountsprivileges'};
1000
    my $storagepools = $reguser->{'storagepools'};
1001
    my $memoryquota = $reguser->{'memoryquota'};
1002
    my $storagequota = $reguser->{'storagequota'};
1003
    my $nodestoragequota = $reguser->{'nodestoragequota'};
1004
    my $vcpuquota = $reguser->{'vcpuquota'};
1005
    my $externalipquota = $reguser->{'externalipquota'};
1006
    my $rxquota = $reguser->{'rxquota'};
1007
    my $txquota = $reguser->{'txquota'};
1008
    my $tasks = $reguser->{'tasks'};
1009
    my $ubillto = $reguser->{'billto'};
1010 45cc3024 hq
    my $udnsdomains = $reguser->{'dnsdomains'};
1011
    my $uappstoreurl = $reguser->{'appstoreurl'}; $uappstoreurl = '' if ($uappstoreurl eq '--');
1012 95b003ff Origo
    my $created = $reguser->{'created'} || $current_time; # set created timestamp for new users
1013
1014
    # Only allow admins to change user privileges and quotas
1015
    if ($isadmin || $user eq $engineuser) {
1016
        $uprivileges = $obj->{"privileges"} || $reguser->{'privileges'};
1017
        $uprivileges = '' if ($uprivileges eq '--');
1018
        $uprivileges = 'n' if (!$reguser->{'username'} && !$uprivileges); # Allow new users to use node storage unless explicitly disallowed
1019
        $uprivileges =~ tr/adnrpu//cd; # filter out non-valid privileges
1020
        $uprivileges =~ s/(.)(?=.*?\1)//g; # filter out duplicates using positive lookahead
1021
        $storagepools = ($obj->{"storagepools"} || $obj->{"storagepools"} eq '0')?$obj->{"storagepools"} : $reguser->{'storagepools'};
1022
        $memoryquota = (defined $obj->{"memoryquota"}) ? $obj->{"memoryquota"} : $reguser->{'memoryquota'};
1023
        $storagequota = (defined $obj->{"storagequota"}) ? $obj->{"storagequota"} : $reguser->{'storagequota'};
1024
        $nodestoragequota = (defined $obj->{"nodestoragequota"}) ? $obj->{"nodestoragequota"} : $reguser->{'nodestoragequota'};
1025
        $vcpuquota = (defined $obj->{"vcpuquota"}) ? $obj->{"vcpuquota"} : $reguser->{'vcpuquota'};
1026
        $externalipquota = (defined $obj->{"externalipquota"}) ? $obj->{"externalipquota"} : $reguser->{'externalipquota'};
1027
        $rxquota = (defined $obj->{"rxquota"}) ? $obj->{"rxquota"} : $reguser->{'rxquota'};
1028
        $txquota = (defined $obj->{"txquota"}) ? $obj->{"txquota"} : $reguser->{'txquota'};
1029
        $tasks = $obj->{"tasks"} || $reguser->{'tasks'};
1030
        $ubillto = $obj->{"billto"} || $reguser->{'billto'};
1031 45cc3024 hq
        $udnsdomains = $obj->{"dnsdomains"} || $udnsdomains; $udnsdomains = '' if ($udnsdomains eq '--');
1032
        $uappstoreurl = $obj->{"appstoreurl"} || $uappstoreurl;
1033 95b003ff Origo
        $uaccounts = $obj->{"accounts"} || $reguser->{'accounts'};
1034
        $uaccountsprivileges = $obj->{"accountsprivileges"} || $reguser->{'accountsprivileges'};
1035 a439a9c4 hq
        my @ua = split(/, ?/, $uaccounts);
1036
        my @up = split(/, ?/, $uaccountsprivileges);
1037 95b003ff Origo
        my @ua2 = ();
1038
        my @up2 = ();
1039
        my $i = 0;
1040
        foreach my $u (@ua) {
1041
            if ($register{$u} && ($u ne $username)) {
1042
                push @ua2, $u;
1043
                my $uprivs = $up[$i] || 'u';
1044
                $uprivs =~ tr/adnrpu//cd; # filter out non-valid privileges
1045
                $uprivs =~ s/(.)(?=.*?\1)//g; # filter out duplicates using positive lookahead
1046
                push @up2, $uprivs;
1047
            }
1048
            $i++;
1049
        }
1050
        $uaccounts = join(", ", @ua2);
1051
        $uaccountsprivileges = join(", ", @up2);
1052
    }
1053
1054
    # Sanity checks
1055
    if (
1056
        ($fullname && length $fullname > 255)
1057
            || ($password && length $password > 255)
1058
    ) {
1059
        $postreply .= "Status=ERROR Bad data: $username\n";
1060
        return  $postreply;
1061
    }
1062
    # Only allow new users to be created by admins, i.e. no auto-registration
1063
    if ($reguser->{'username'} || $isadmin) {
1064
        $register{$username} = {
1065
            password           => $password,
1066
            fullname           => $fullname,
1067
            email              => $email,
1068
            opemail            => $opemail,
1069
            alertemail         => $alertemail,
1070
            phone              => $phone,
1071
            opphone            => $opphone,
1072
            opfullname         => $opfullname,
1073
            allowfrom          => $allowfrom,
1074 54401133 hq
            totpsecret         => $totpsecret,
1075 95b003ff Origo
            privileges         => $uprivileges,
1076
            accounts           => $uaccounts,
1077
            accountsprivileges => $uaccountsprivileges,
1078
            storagepools       => $storagepools,
1079
            memoryquota        => $memoryquota+0,
1080
            storagequota       => $storagequota+0,
1081
            nodestoragequota   => $nodestoragequota+0,
1082
            vcpuquota          => $vcpuquota+0,
1083
            externalipquota    => $externalipquota+0,
1084
            rxquota            => $rxquota+0,
1085
            txquota            => $txquota+0,
1086
            tasks              => $tasks,
1087
            allowinternalapi   => $allowinternalapi || 1, # specify '--' to explicitly disallow
1088
            billto             => $ubillto,
1089 45cc3024 hq
            dnsdomains         => $udnsdomains,
1090
            appstoreurl        => $uappstoreurl,
1091
            created            => $created,
1092 95b003ff Origo
            modified           => $current_time,
1093
            action             => ""
1094
        };
1095
        my %uref = %{$register{$username}};
1096
        $uref{result} = "OK";
1097
        $uref{password} = "";
1098
        $uref{status} = ($uprivileges =~ /d/)?'disabled':'enabled';
1099
        $postreply = JSON::to_json(\%uref, { pretty => 1 });
1100
#        $postreply =~ s/""/"--"/g;
1101
        $postreply =~ s/null/""/g;
1102
#        $postreply =~ s/\x/ /g;
1103
    }
1104
    return $postreply;
1105
}
1106
1107
sub do_list {
1108
    my ($uuid, $action, $obj) = @_;
1109
    if ($help) {
1110
        return <<END
1111
GET::
1112
List users registered on this engine.
1113
END
1114
    }
1115
    my $userfilter;
1116
    my $usermatch;
1117
    my $propmatch;
1118
    if ($uripath =~ /users(\.cgi)?\/(\?|)(me|this)/) {
1119
        $usermatch = $user;
1120
        $propmatch = $4 if ($uripath =~ /users(\.cgi)?\/(\?|)(me|this)\/(.+)/);
1121
    } elsif ($uripath =~ /users(\.cgi)?\/(\?|)(username)/) {
1122
        $userfilter = $3 if ($uripath =~ /users(\.cgi)?\/\??username(:|=)(.+)/);
1123
        $userfilter = $1 if ($userfilter =~ /(.*)\*/);
1124
    } elsif ($uripath =~ /users(\.cgi)?\/(\S+)/) {
1125
        $usermatch = $2;
1126
        $propmatch = $4 if ($uripath =~ /users(\.cgi)?\/(\S+)\/(.+)/);
1127
    }
1128
1129
    my @regvalues = (sort {$a->{'id'} <=> $b->{'id'}} values %register); # Sort by id
1130
    my @curregvalues;
1131
1132
    foreach my $valref (@regvalues) {
1133
        my $reguser = $valref->{'username'};
1134
        if ($user eq $reguser || $isadmin) {
1135
            next if ($reguser eq 'irigo' || $reguser eq 'guest');
1136
            my %val = %{$valref}; # Deference and assign to new ass array, effectively cloning object
1137 54401133 hq
            $val{'password'} = '';
1138
            $val{'status'} = ($val{'privileges'} =~ /d/)?'disabled':'enabled';
1139
            if ((!$userfilter && !$usermatch) || ($userfilter && $reguser =~ /$userfilter/) || $reguser eq $usermatch) {
1140
                push @curregvalues,\%val;
1141
            }
1142 95b003ff Origo
        }
1143
    }
1144
    if ($action eq 'tablelist') {
1145
        my $t2 = Text::SimpleTable->new(14,32,24,10);
1146
1147
        $t2->row('username', 'fullname', 'lastlogin', 'privileges');
1148
        $t2->hr;
1149
        my $pattern = $options{m};
1150
        foreach $rowref (@curregvalues){
1151
            if ($pattern) {
1152
                my $rowtext = $rowref->{'username'} . " " . $rowref->{'fullname'} . " " . $rowref->{'lastlogin'}
1153
                               . " " .  $rowref->{'privileges'};
1154
                $rowtext .= " " . $rowref->{'mac'} if ($isadmin);
1155
                next unless ($rowtext =~ /$pattern/i);
1156
            }
1157
            $t2->row($rowref->{'username'}, $rowref->{'fullname'}||'--', localtime($rowref->{'lastlogin'})||'--',
1158
            $rowref->{'privileges'}||'--');
1159
        }
1160
        #$t2->row('common', '--', '--', '--');
1161
        #$t2->row('all', '--', '--', '--') if (index($privileges,"a")!=-1);
1162
        $postreply .= $t2->draw;
1163
    } elsif ($console) {
1164
        $postreply = Dumper(\@curregvalues);
1165
    } else {
1166
        my $json_text;
1167
        if ($propmatch) {
1168
            $json_text = JSON::to_json($curregvalues[0]->{$propmatch}, {allow_nonref=>1});
1169
        } else {
1170
            $json_text = JSON::to_json(\@curregvalues, {pretty=>1});
1171
        }
1172
        $json_text =~ s/"--"/""/g;
1173
        $json_text =~ s/null/""/g;
1174
#        $json_text =~ s/\x/ /g;
1175
        $postreply = qq|{"identifier": "username", "label": "username", "items": | unless ($usermatch || $action ne 'listusers');
1176
        $postreply .= $json_text;
1177
        $postreply .= "}\n" unless ($usermatch || $action ne 'listusers');
1178
    }
1179
    return $postreply;
1180
}
1181
1182
sub do_uuidlookup {
1183
    if ($help) {
1184
        return <<END
1185
GET:uuid:
1186
Simple action for looking up a username (uuid) or part of a username and returning the complete username.
1187
END
1188
    }
1189
    my $u = $options{u};
1190
    $u = $params{'uuid'} unless ($u || $u eq '0');
1191
    if ($u || $u eq '0') {
1192
        foreach my $uuid (keys %register) {
1193
            if ($uuid =~ /^$u/) {
1194
                return "$uuid\n" if ($uuid eq $user || index($privileges,"a")!=-1);
1195
            }
1196
        }
1197
    }
1198
}
1199
1200
sub do_uuidshow {
1201
    if ($help) {
1202
        return <<END
1203
GET:uuid:
1204
Simple action for showing a single user. Pass username as uuid.
1205
END
1206
    }
1207
    my $u = $options{u};
1208
    $u = $params{'uuid'} unless ($u || $u eq '0');
1209
    if ($u eq $user || index($privileges,"a")!=-1) {
1210
        foreach my $uuid (keys %register) {
1211
            if ($uuid =~ /^$u/) {
1212
                my %hash = %{$register{$uuid}};
1213
                delete $hash{'action'};
1214
                my $dump = to_json(\%hash, {pretty=>1});
1215
                $dump =~ s/undef/"--"/g;
1216
                return $dump;
1217
            }
1218
        }
1219
    }
1220
}
1221
1222
sub Restoreengine {
1223
    my ($uuid, $action, $obj) = @_;
1224
    if ($help) {
1225
        return <<END
1226
GET:restorefile:
1227
Restores this engine's configuration from "restorefile", which must be one of the paths listed in listenginebackups
1228
END
1229
    }
1230
    if (!$isadmin) {
1231
        $postreply = "Status=ERROR You must be an administrator in order to restore this engine";
1232
    } else {
1233
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
1234
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
1235
        my $enginetkthash = Digest::SHA::sha512_hex($tktkey);
1236
1237
        my $restoredir = "/etc";
1238
        my $dbname = "steamregister";
1239
        my $restorefile = $obj->{'restorefile'};
1240
1241
        if ($restorefile && !($restorefile =~ /\//)) {
1242
            my $urifile = URI::Escape::uri_escape($restorefile);
1243 48fcda6b Origo
            my $uri = "https://www.stabile.io/irigo/engine.cgi";
1244 95b003ff Origo
            my $cmd = qq|/usr/bin/curl -f --cookie -O -L -F action=getbackup -F restorefile=$urifile -F engineid=$engineid -F enginetkthash=$enginetkthash "$uri" > "/tmp/$restorefile"|;
1245
            my $res = `$cmd`;
1246
            if (-s "/tmp/$restorefile") {
1247
                $res .= `(mkdir $restoredir/stabile; cd $restoredir/stabile; /bin/tar -zxf "/tmp/$restorefile")`;
1248
                $res .= `/usr/bin/mysql -e "create database $dbname;"`;
1249
                $res .= `/usr/bin/mysql $dbname < $restoredir/stabile/steamregister.sql`;
1250
                $res .= `cp -b $restoredir/stabile/hosts.allow /etc/hosts.allow`;
1251
                $res .= `cp -b $restoredir/stabile/auth_tkt_cgi.conf /etc/apache2/conf.d/`;
1252
                $res .= `cp -b $restoredir/stabile/*.crt /etc/apache2/ssl/`;
1253
                $res .= `cp -b $restoredir/stabile/*.key /etc/apache2/ssl/`;
1254
                $res .= `cp -b $restoredir/stabile/mon.cf /etc/mon/`;
1255
                $res .= `service apache2 reload`;
1256
1257
                # Restore default node configuration
1258
                unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access identity register"};
1259
                my $defaultpath = $idreg{'default'}->{'path'} . "/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
1260
                untie %idreg;
1261
                $res .=  `cp $restoredir/stabile/nodeconfig.cfg $defaultpath`;
1262 48fcda6b Origo
                $main::syslogit->($user, "info", "Engine configuration $restorefile restored from the registry");
1263
                $postreply .= "Status=OK Engine configuration $restorefile restored from the registry - reloading UI\n";
1264 95b003ff Origo
            } else {
1265
                $postreply .= "Status=ERROR Restore failed, $restorefile not found...\n";
1266
            }
1267
        } else {
1268
            $postreply .= "Status=ERROR You must select a restore file\n";
1269
        }
1270
    }
1271
    return $postreply;
1272
}
1273
1274
# Print list of available actions on objects
1275
sub do_plainhelp {
1276
    my $res;
1277
    $res .= header('text/plain') unless $console;
1278
    $res .= <<END
1279
new [username="name", password="password"]
1280
* enable: Enables a disabled user
1281
* disable: Disables a user, disallowing login
1282
* remove: Deletes a user, leaving servers, images, networks etc. untouched
1283
* deleteentirely: Deletes a user and all the user's servers, images, networks etc. Warning: This destroys data
1284
1285
END
1286
;
1287
}
1288
1289 8d7785ff Origo
sub do_cleanbillingdata {
1290
    my ($uuid, $action, $obj) = @_;
1291
    if ($help) {
1292
        return <<END
1293
GET:year,dryrun,cleanup:
1294
Deletes billing from [year]. Default is current year-2. Set dryrun to do a test run. Set cleanup to remove invalid entries.
1295
END
1296
    }
1297
    return "Status=Error Not allowed\n" unless ($isadmin);
1298
1299
    my $y = $params{'year'} || ($year-2);
1300
    my $dryrun = $params{'dryrun'};
1301
    my $cleanup = $params{'cleanup'};
1302
    my $pattern = qq|like '%-$y-__'|;
1303
    if ($cleanup) {
1304
        $pattern = qq|not like '%-____-__'|;
1305
        $y = '';
1306
    }
1307
1308
    unless ( tie(%bnetworksreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_networks', key=>'useridtime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1309
    my @bkeys = (tied %bnetworksreg)->select_where("useridtime $pattern");
1310
    $postreply .= "Status=OK -- this is only a test run ---\n" if ($dryrun);
1311
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y network rows\n";
1312
    foreach my $bkey (@bkeys) {
1313
        $postreply .= "Status=OK removing $bnetworksreg{$bkey}->{useridtime}\n";
1314
        delete($bnetworksreg{$bkey}) unless ($dryrun);
1315
    }
1316
    untie(%bnetworksreg);
1317
1318
    unless ( tie(%bimagesreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_images', key=>'userstoragepooltime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1319
    my @bkeys = (tied %bimagesreg)->select_where("userstoragepooltime $pattern");
1320
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y image rows\n";
1321
    foreach my $bkey (@bkeys) {
1322
        $postreply .= "Status=OK removing $bimagesreg{$bkey}->{userstoragepooltime}\n";
1323
        delete($bimagesreg{$bkey}) unless ($dryrun);
1324
    }
1325
    untie(%bimagesreg);
1326
1327
    unless ( tie(%bserversreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_domains', key=>'usernodetime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1328
    my @bkeys = (tied %bserversreg)->select_where("usernodetime $pattern");
1329
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y server rows\n";
1330
    foreach my $bkey (@bkeys) {
1331
        $postreply .= "Status=OK removing $bserversreg{$bkey}->{usernodetime}\n";
1332
        delete($bserversreg{$bkey}) unless ($dryrun);
1333
    }
1334
    untie(%bserversreg);
1335
1336
    return $postreply;
1337
1338
}
1339
1340 95b003ff Origo
sub collectBillingData {
1341
    my ( $curuuid, $buser, $bmonth, $byear, $showcost ) = @_;
1342
1343 8d7785ff Origo
    my $vcpu=0;
1344 95b003ff Origo
    my $rx = 0;
1345
    my $tx = 0;
1346
    my $vcpuavg = 0;
1347 8d7785ff Origo
    my $memory = 0;
1348 95b003ff Origo
    my $memoryavg = 0;
1349 8d7785ff Origo
    my $backupsize = 0;
1350 95b003ff Origo
    my $backupsizeavg = 0;
1351 8d7785ff Origo
    my $nodevirtualsize = 0;
1352 95b003ff Origo
    my $nodevirtualsizeavg = 0;
1353 8d7785ff Origo
    my $virtualsize = 0;
1354 95b003ff Origo
    my $virtualsizeavg = 0;
1355 8d7785ff Origo
    my $externalip = 0;
1356 95b003ff Origo
    my $externalipavg = 0;
1357
1358
    my $prevmonth = $bmonth-1;
1359
    my $prevyear = $byear;
1360
    if ($prevmonth == 0) {$prevmonth=12; $prevyear--;};
1361
    $prevmonth = substr("0" . $prevmonth, -2);
1362
    my $prev_rx = 0;
1363
    my $prev_tx = 0;
1364
    # List pricing for a single system/server
1365
    if ($curuuid) {
1366
        unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domains register"};
1367
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images',key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1368
        unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
1369
1370
        my @domains;
1371
        my $isserver = 1 if ($domreg{$curuuid});
1372
        if ($isserver) {
1373
            @domains = $domreg{$curuuid};
1374
        } else {
1375
            @domains = values %domreg;
1376
        }
1377
        foreach my $valref (@domains) {
1378
            if ($valref->{'system'} eq $curuuid || $isserver) {
1379
                $memory += $valref->{'memory'};
1380
                $vcpu += $valref->{'vcpu'};
1381
                my $image = $valref->{'image'};
1382
                my $storagepool;
1383
                if ($imagereg{$image}) {
1384
                    $storagepool = $imagereg{$image}->{'storagepool'};
1385
                    if ($storagepool == -1) {
1386
                        $nodevirtualsize += $imagereg{$image}->{'virtualsize'};
1387
                    } else {
1388
                        $virtualsize += $imagereg{$image}->{'virtualsize'};
1389
                    }
1390
                    $backupsize += $imagereg{$image}->{'backupsize'};
1391
                }
1392
                $image = $valref->{'image2'};
1393
                if ($imagereg{$image}) {
1394
                    $storagepool = $imagereg{$image}->{'storagepool'};
1395
                    if ($storagepool == -1) {
1396
                        $nodevirtualsize += $imagereg{$image}->{'virtualsize'};
1397
                    } else {
1398
                        $virtualsize += $imagereg{$image}->{'virtualsize'};
1399
                    }
1400
                    $backupsize += $imagereg{$image}->{'backupsize'};
1401
                }
1402
                my $networkuuid = $valref->{'networkuuid1'};
1403
                my $networktype = $networkreg{$networkuuid}->{'type'};
1404
                $externalip++ if ($networktype eq 'externalip'|| $networktype eq 'ipmapping');
1405
                $networkuuid = $valref->{'networkuuid2'};
1406
                if ($networkreg{$networkuuid}) {
1407
                    $networktype = $networkreg{$networkuuid}->{'type'};
1408 2a63870a Christian Orellana
                    $externalip++ if ($networktype eq 'externalip'|| $networktype eq 'ipmapping');
1409 95b003ff Origo
                }
1410
            }
1411
        }
1412
        untie %domreg;
1413
        untie %imagereg;
1414
        untie %networkreg;
1415
1416
    # List pricing for all servers
1417
    } else {
1418 d24d9a01 hq
        # Network billing
1419 95b003ff Origo
        unless ( tie(%bnetworksreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_networks', key=>'useridtime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
1420
        unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
1421
1422 d24d9a01 hq
        # Build list of the user's network id's
1423 95b003ff Origo
        my %usernetworks;
1424 8d7785ff Origo
        my @nkeys = (tied %networkreg)->select_where("user = '$buser'");
1425 95b003ff Origo
        foreach $network (@nkeys) {
1426
            my $id = $networkreg{$network}->{'id'};
1427
            $usernetworks{$id} = $id unless ($usernetworks{$id} || $id==0 || $id==1);
1428
        }
1429
        untie %networkreg;
1430
1431
        foreach $id (keys %usernetworks) {
1432
            my $networkobj = $bnetworksreg{"$buser-$id-$byear-$bmonth"};
1433
            my $prevnetworkobj = $bnetworksreg{"$buser-$id-$prevyear-$prevmonth"};
1434
            $externalip += $networkobj->{'externalip'};
1435
            $externalipavg += $networkobj->{'externalipavg'};
1436
            $rx += $networkobj->{'rx'};
1437
            $tx += $networkobj->{'tx'};
1438
            $prev_rx += $prevnetworkobj->{'rx'};
1439
            $prev_tx += $prevnetworkobj->{'tx'};
1440
        }
1441
        untie %bnetworksreg;
1442
1443
    # Image billing
1444
1445
        unless ( tie(%bimagesreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_images', key=>'userstoragepooltime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
1446
1447
        # Build list of the users storage pools
1448
        my $storagepools = $Stabile::config->get('STORAGE_POOLS_DEFAULTS') || "0";
1449
        my $upools = $register{$buser}->{'storagepools'}; # Prioritized list of users storage pools as numbers, e.g. "0,2,1"
1450 8d7785ff Origo
        $storagepools = $upools if ($upools && $upools ne '--');
1451 95b003ff Origo
        my @spl = split(/,\s*/, $storagepools);
1452
        my $bimageobj = $bimagesreg{"$buser--1-$byear-$bmonth"};
1453
        $backupsize = $bimageobj->{'backupsize'}+0;
1454
        $nodevirtualsize = $bimageobj->{'virtualsize'}+0;
1455
        $backupsizeavg = $bimageobj->{'backupsizeavg'}+0;
1456
        $nodevirtualsizeavg = $bimageobj->{'virtualsizeavg'}+0;
1457
        foreach $pool (@spl) {
1458
            $bimageobj = $bimagesreg{"$buser-$pool-$byear-$bmonth"};
1459
            $virtualsize += $bimageobj->{'virtualsize'};
1460
            $backupsize += $bimageobj->{'backupsize'};
1461
            $virtualsizeavg += $bimageobj->{'virtualsizeavg'};
1462
            $backupsizeavg += $bimageobj->{'backupsizeavg'};
1463
        }
1464
        untie %bimagesreg;
1465
1466
    # Server billing
1467
1468
        unless ( tie(%bserversreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_domains', key=>'usernodetime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
1469
        unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
1470
1471 c899e439 Origo
        my @usernodes = keys %nodereg;
1472 95b003ff Origo
        untie %nodereg;
1473
1474
        my @nodebills;
1475 c899e439 Origo
        foreach $mac (@usernodes) {
1476 95b003ff Origo
            my $bserverobj = $bserversreg{"$buser-$mac-$byear-$bmonth"};
1477
            $vcpu += $bserverobj->{'vcpu'};
1478
            $memory += $bserverobj->{'memory'};
1479
            $vcpuavg += $bserverobj->{'vcpuavg'};
1480
            $memoryavg += $bserverobj->{'memoryavg'};
1481
        }
1482
        untie %bserversreg;
1483
    }
1484
1485
    my $uservcpuprice = 0+ $register{$user}->{'vcpuprice'};
1486
    my $usermemoryprice = 0+ $register{$user}->{'memoryprice'};
1487
    my $userstorageprice = 0+ $register{$user}->{'storageprice'};
1488
    my $usernodestorageprice = 0+ $register{$user}->{'nodestorageprice'};
1489
    my $userexternalipprice = 0+ $register{$user}->{'externalipprice'};
1490
1491
    $vcpuprice = $uservcpuprice || $Stabile::config->get('VCPU_PRICE') + 0;
1492
    $memoryprice = $usermemoryprice || $Stabile::config->get('MEMORY_PRICE') + 0;
1493
    $storageprice = $userstorageprice || $Stabile::config->get('STORAGE_PRICE') + 0;
1494
    $nodestorageprice = $usernodestorageprice || $Stabile::config->get('NODESTORAGE_PRICE') + 0;
1495
    $externalipprice = $userexternalipprice || $Stabile::config->get('EXTERNALIP_PRICE') + 0;
1496
1497
    my $memorygb = int(0.5 + 100*$memory/1024)/100;
1498
    my $virtualsizegb = int(0.5 + 100*$virtualsize/1024/1024/1024)/100;
1499
    my $nodevirtualsizegb = int(0.5 + 100*$nodevirtualsize/1024/1024/1024)/100;
1500
    my $backupsizegb = int(0.5 + 100*$backupsize/1024/1024/1024)/100;
1501
1502
    my $totalprice = int(0.5 + 100*($vcpu*$vcpuprice + $memorygb*$memoryprice + $virtualsizegb*$storageprice
1503
     + $nodevirtualsizegb*$nodestorageprice + $backupsizegb*$storageprice + $externalip*$externalipprice)) /100;
1504
1505
    my $memoryavggb = int(0.5 + 100*$memoryavg/1024)/100;
1506
    my $virtualsizeavggb = int(0.5 + 100*$virtualsizeavg/1024/1024/1024)/100;
1507
    my $nodevirtualsizeavggb = int(0.5 + 100*$nodevirtualsizeavg/1024/1024/1024)/100;
1508
    my $backupsizeavggb = int(0.5 + 100*$backupsizeavg/1024/1024/1024)/100;
1509
1510
    my $monfac = 1;
1511
    if ($bmonth == $month) {
1512
        # Find 00:00 of first day of month - http://www.perlmonks.org/?node_id=97120
1513
        my $fstamp = POSIX::mktime(0,0,0,1,$mon,$year-1900,0,0,-1);
1514
        my $lstamp = POSIX::mktime(0,0,0,1,$mon+1,$year-1900,0,0,-1);
1515
        $monfac = ($current_time-$fstamp)/($lstamp-$fstamp);
1516
    }
1517
1518
    my $totalpriceavg = int(0.5 + 100*$monfac * ($vcpuavg*$vcpuprice + $memoryavggb*$memoryprice + $virtualsizeavggb*$storageprice
1519
     + $nodevirtualsizeavggb*$nodestorageprice + $backupsizeavggb*$storageprice + $externalipavg*$externalipprice)) /100;
1520
1521
    $prev_rx = 0 if ($prev_rx>$rx); # Something is fishy
1522
    $prev_tx = 0 if ($prev_tx>$tx);
1523
    my $rxgb = int(0.5 + 100*($rx-$prev_rx)/1024**3)/100;
1524
    my $txgb = int(0.5 + 100*($tx-$prev_tx)/1024**3)/100;
1525
1526
    my %stats;
1527
    $stats{'virtualsize'} = $virtualsizegb;
1528
    $stats{'backupsize'} = $backupsizegb;
1529
    $stats{'externalip'} = $externalip;
1530
    $stats{'memory'} = $memorygb;
1531
    $stats{'month'} = $bmonth;
1532
    $stats{'nodevirtualsize'} = $nodevirtualsizegb;
1533
    $stats{'rx'} = $rxgb;
1534
    $stats{'tx'} = $txgb;
1535
    $stats{'username'} = $buser;
1536
    $stats{'vcpu'} = $vcpu;
1537
    $stats{'year'} = $byear;
1538
    $stats{'totalcost'} = "$cur $totalprice" if ($showcost);
1539
    $stats{'curtotal'} = $totalprice if ($showcost);
1540
1541
    if (!$curuuid) {
1542
        $stats{'virtualsizeavg'} = $virtualsizeavggb;
1543
        $stats{'backupsizeavg'} = $backupsizeavggb;
1544
        $stats{'memoryavg'} = $memoryavggb;
1545
        $stats{'nodevirtualsizeavg'} = $nodevirtualsizeavggb;
1546
        $stats{'vcpuavg'} = int(0.5 + 100*$vcpuavg)/100;
1547
        $stats{'externalipavg'} = int(0.5 + 100*$externalipavg)/100;
1548
        $stats{'totalcostavg'} = "$cur $totalpriceavg" if ($showcost);
1549
    }
1550
    return %stats;
1551
}
1552
1553
sub do_resetpassword {
1554
    my ($uuid, $action, $obj) = @_;
1555
    if ($help) {
1556
        return <<END
1557
GET:username:
1558
Sends an email to a user with a link to reset his password. The user must have a valid email address.
1559
END
1560
    }
1561
    my $username = $obj->{'username'} || $user;
1562
    if ($register{$username} && ($username eq $user || $isadmin)) {
1563
        my $mailaddrs = $register{$username}->{'email'};
1564
        $mailaddrs = $username if (!$mailaddrs && $username =~ /\@/);
1565
        if ($mailaddrs) {
1566
            require (dirname(__FILE__)) . "/../auth/Apache/AuthTkt.pm";
1567
            my $tktname = 'auth_' . substr($engineid, 0, 8);
1568
            my $at = Apache::AuthTkt->new(conf => $ENV{MOD_AUTH_TKT_CONF});
1569
            my $tkt = $at->ticket(uid => $username, digest_type => 'SHA512', tokens => '', debug => 0);
1570
#            my $valid = $at->valid_ticket($tkt);
1571
1572
            my $mailhtml = <<END;
1573
<!DOCTYPE html
1574
	PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
1575
	 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1576
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
1577
	<head>
1578
		<title>Password reset</title>
1579
		<meta http-equiv="Pragma" content="no-cache" />
1580
		<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" />
1581
		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
1582
	</head>
1583
	<body class="tundra">
1584
		<div>
1585
			<div class="well" style="margin:20px;">
1586
				<h3 style="color: #e74c3c!important; margin-bottom:30px;">You requested a password reset at $enginename</h3>
1587
					To log in and set a new password, please click <a href="$baseurl/auth/autologin?$tktname=$tkt\&back=#chpwd">here</a>.<br>
1588
    				<div>Thanks,<br>your friendly infrastructure services</div>
1589
				</div>
1590
			</div>
1591
		</div>
1592
	</body>
1593
</html>
1594
END
1595
            ;
1596
            my $msg = MIME::Lite->new(
1597
                From     => "$enginename",
1598
                To       => $mailaddrs,
1599
                Type     => 'multipart/alternative',
1600
                Subject  => "Password reset on $enginename",
1601
            );
1602
            # my $att_text = MIME::Lite->new(
1603
            #     Type     => 'text',
1604
            #     Data     => $mailtext,
1605
            #     Encoding => 'quoted-printable',
1606
            # );
1607
            # $att_text->attr('content-type' => 'text/plain; charset=UTF-8');
1608
            # $msg->attach($att_text);
1609
            my $att_html = MIME::Lite->new(
1610
                Type     => 'text',
1611
                Data     => $mailhtml,
1612
                Encoding => 'quoted-printable',
1613
            );
1614
            $att_html->attr('content-type' => 'text/html; charset=UTF-8');
1615
            $msg->attach($att_html);
1616
            my $res = $msg->send;
1617
            $postreply = "Status=OK Password reset email sent to $mailaddrs\n";
1618
        } else {
1619
            $postreply = "Status=Error user does not have a registered email address\n";
1620
        }
1621
    } else {
1622
        $postreply = "Status=Error invalid data submitted\n";
1623
    }
1624
    return $postreply;
1625
}
1626
1627
sub do_changepassword {
1628
    my ($uuid, $action, $obj) = @_;
1629
    if ($help) {
1630
        return <<END
1631
GET:username,password:
1632
Changes the password for a user.
1633
END
1634
    }
1635
    my $username = $obj->{'username'} || $user;
1636
    my $password = $obj->{'password'};
1637
    if ($password && $register{$username} && ($username eq $user || $isadmin)) {
1638
        $MAXLEN = 20;
1639
        var $msg = IsBadPassword($password);
1640
        if ($msg) {
1641
            $postreply = "Status=Error $msg - please choose a stronger password\n";
1642
        } else {
1643
            $password = Digest::SHA::sha512_base64($password);
1644
            $register{$username}->{'password'} = $password;
1645
            $postreply = "Status=OK Password changed for $username\n";
1646
        }
1647
    } else {
1648
        $postreply = "Status=Error invalid data submitted\n";
1649
    }
1650
    return $postreply;
1651
}
1652
1653
sub do_remove {
1654
    my ($uuid, $action, $obj) = @_;
1655
    if ($help) {
1656
        return <<END
1657
GET:username:
1658
Removes a user.
1659
END
1660
    }
1661
    my $username = $obj->{'username'};
1662
    $postreply = remove($username);
1663
    return $postreply;
1664
}
1665
1666
sub remove {
1667
    my $username = shift;
1668
    if (!$isadmin && ($user ne $engineuser)) {
1669
        $postreply .= "Status=ERROR You are not allowed to remove user $username\n";
1670
    } elsif ($register{$username}) {
1671
        delete $register{$username};
1672
        tied(%register)->commit;
1673
        `/bin/rm /tmp/$username~*.tasks`;
1674
        unlink "../cgi/ui_update/$username~ui_update.cgi" if (-e "../cgi/ui_update/$username~ui_update.cgi");
1675
        $main::syslogit->($user, "info", "Deleted user $username from db");
1676
        if ($console) {
1677
            $postreply .= "Status=OK Deleted user $username\n";
1678
        } else {
1679
#            $main::updateUI->({ tab => 'users', type=>'update', user=>$user});
1680
            return "{}";
1681
        }
1682
        return $postreply;
1683
    } else {
1684
        $postreply .= "Status=ERROR No such user: $username\n";
1685
    }
1686
}
1687
1688 48fcda6b Origo
# Update engine users with users received from the registry
1689 95b003ff Origo
sub updateEngineUsers {
1690
    my ($json_text) = @_;
1691
    return unless ($isadmin || ($user eq $engineuser));
1692
    my $res;
1693
    my $json = JSON->new;
1694
    $json->utf8([1]);
1695
    my $json_obj = $json->decode($json_text);
1696
    my @ulist = @$json_obj;
1697
    my @efields = qw(password
1698
    	address city company country email fullname phone
1699 eb31fb38 hq
        state zip alertemail opemail opfullname opphone billto
1700 95b003ff Origo
        memoryquota storagequota vcpuquota externalipquota rxquota txquota nodestoragequota
1701 54401133 hq
        accounts accountsprivileges privileges modified dnsdomains appstoreurl totpsecret
1702 95b003ff Origo
    );
1703 48fcda6b Origo
    my $ures;
1704
    my $ucount = 0;
1705 95b003ff Origo
    foreach my $u (@ulist) {
1706
        my $username = $u->{'username'};
1707
        if (!$register{$username} && $u->{'password'}) {
1708
            $register{$username} = {
1709
                username => $username,
1710 d24d9a01 hq
                password => $u->{'password'},
1711
                allowinternalapi => 1
1712 95b003ff Origo
            };
1713 48fcda6b Origo
            $ures .= " *";
1714 95b003ff Origo
        }
1715
        next unless ($register{$username});
1716
        next if ($register{$username}->{'modified'} && $register{$username}->{'modified'} > $u->{'modified'});
1717
        foreach my $efield (@efields) {
1718
            if ($efield eq 'privileges') {
1719
                $u->{$efield} =~ tr/adnrpu//cd; # filter out non-valid privileges
1720
            }
1721
            if (defined $u->{$efield}) {
1722
                $u->{$efield} += 0 if ($efield =~ /(quota|price)$/);
1723
                $register{$username}->{$efield} = $u->{$efield};
1724
            }
1725
            delete $u->{$efield} if (defined $u->{$efield} && $u->{$efield} eq '' && $efield ne 'password')
1726
        }
1727 48fcda6b Origo
        $ures .= "$username ($u->{'fullname'}), ";
1728
        $ucount++;
1729 95b003ff Origo
        my $uid = `id -u irigo-$username`; chomp $uid;
1730
        if (!$uid) { # Check user has system account for disk quotas
1731
            $main::syslogit->($user, "info", "Adding system user $username");
1732
            `/usr/sbin/useradd -m "irigo-$username"`;
1733 104449f5 Origo
            `echo "[User]\nSystemAccount=true" > /var/lib/AccountsService/users/irigo-$username`; # Don't show in login screen
1734 95b003ff Origo
        }
1735
1736
    }
1737 48fcda6b Origo
    $ures = substr($res, 0, -2) . "\n";
1738 705b5366 hq
    $res .= "Status=OK Received $ucount updates on " .(scalar(@ulist)). " registry users\n";
1739 95b003ff Origo
    return $res;
1740
}
1741
1742
sub sendEngineUser {
1743
    my ($username) = @_;
1744
    if ($enginelinked) {
1745 48fcda6b Origo
    # Send engine user to the registry
1746 95b003ff Origo
        require LWP::Simple;
1747
        my $browser = LWP::UserAgent->new;
1748
        $browser->agent('stabile/1.0b');
1749
        $browser->protocols_allowed( [ 'http','https'] );
1750
1751
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
1752
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
1753
        my $tkthash = Digest::SHA::sha512_hex($tktkey);
1754
        my $json = '[' . JSON::to_json(\%{$register{$username}}) . ']';
1755
        $json =~ s/null/""/g;
1756
#        $json = uri_escape_utf8($json);
1757
        $json = URI::Escape::uri_escape($json);
1758 48fcda6b Origo
        my $posturl = "https://www.stabile.io/irigo/engine.cgi?action=update";
1759 95b003ff Origo
        my $postreq = ();
1760
        $postreq->{'POSTDATA'} = $json;
1761
        $postreq->{'engineid'} = $engineid;
1762
        $postreq->{'enginetkthash'} = $tkthash;
1763
1764
#        my $req = HTTP::Request->new(POST => $posturl);
1765
#        $req->content_type("application/json; charset='utf8'");
1766
#        $req->content($postreq);
1767
1768
        $content = $browser->post($posturl, $postreq)->content();
1769
#        $content = $browser->post($posturl, 'Content-type' => 'text/plain;charset=utf-8', Content => $postreq)->content();
1770
#        $content = $browser->request($req)->content();
1771
        my $fullname = $register{$username}->{'fullname'};
1772
        $fullname = Encode::decode('utf8', $fullname);
1773 71b897d3 hq
        return "Updated $fullname in registry\n";
1774 95b003ff Origo
    }
1775
}