Project

General

Profile

Download (83.4 KB) Statistics
| Branch: | Revision:
1
#!/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.stabile.io/info/stabiledocs/licensing/stabile-open-source-license
7

    
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
use Geo::IP;
20
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
            storagepools memoryquota storagequota nodestoragequota vcpuquota externalipquota rxquota txquota billto dnsdomains appstoreurl totpsecret);
79
        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
    $uservmemoryquota = 0+ $register{$user}->{'vmemoryquota'};
119
    $uservgpuquota = 0+ $register{$user}->{'vgpuquota'};
120
    $userexternalipquota = 0+ $register{$user}->{'externalipquota'};
121
    $userrxquota = 0+ $register{$user}->{'rxquota'};
122
    $usertxquota = 0+ $register{$user}->{'txquota'};
123

    
124
    $storagequota = $Stabile::userstoragequota || $defaultstoragequota;
125
    $nodestoragequota = $Stabile::usernodestoragequota || $defaultnodestoragequota;
126
    $memoryquota = $usermemoryquota || $defaultmemoryquota;
127
    $vcpuquota = $uservcpuquota || $defaultvcpuquota;
128
    $vmemoryquota = $uservmemoryquota || $defaultvmemoryquota;
129
    $vgpuquota = $uservgpuquota || $defaultvgpuquota;
130
    $externalipquota = $userexternalipquota || $defaultexternalipquota;
131
    $rxquota = $userrxquota || $defaultrxquota;
132
    $txquota = $usertxquota || $defaulttxquota;
133

    
134
    # Create aliases of functions
135
    *header = \&CGI::header;
136

    
137
    *Unlinkengine = \&Linkengine;
138
    *Updateengine = \&Linkengine;
139
    *Saveengine = \&Linkengine;
140
    *Syncusers = \&Linkengine;
141

    
142
    *do_help = \&action;
143
    *do_show = \&do_uuidshow;
144
    *do_delete = \&do_remove;
145
    *do_tablelist = \&do_list;
146
    *do_billingstatus = \&do_billing;
147
    *do_usage = \&do_billing;
148
    *do_usagestatus = \&do_billing;
149
    *do_billingavgstatus = \&do_billing;
150
    *do_usageavgstatus = \&do_billing;
151
    *do_upgradeengine = \&privileged_action;
152
    *do_gear_upgradeengine = \&do_gear_action;
153
    *do_backupengine = \&privileged_action;
154
    *do_gear_backupengine = \&do_gear_action;
155
    *do_restoreengine = \&privileged_action;
156
    *do_gear_restoreengine = \&do_gear_action;
157
    *do_releasepressure = \&privileged_action_async;
158
    *do_gear_releasepressure = \&do_gear_action;
159

    
160
    *do_linkengine = \&privileged_action;
161
    *do_gear_linkengine = \&do_gear_action;
162
    *do_saveengine = \&privileged_action_async;
163
    *do_gear_saveengine = \&do_gear_action;
164
    *do_unlinkengine = \&privileged_action;
165
    *do_gear_unlinkengine = \&do_gear_action;
166
    *do_updateengine = \&privileged_action;
167
    *do_syncusers = \&privileged_action;
168
    *do_gear_updateengine = \&do_gear_action;
169
    *do_gear_syncusers = \&do_gear_action;
170
    *do_deleteentirely = \&privileged_action;
171
    *do_gear_deleteentirely = \&do_gear_action;
172
    *do_vent = \&privileged_action_async;
173
    *do_gear_vent = \&do_gear_action;
174
    *do_gettimezone = \&privileged_action;
175
    *do_gear_gettimezone = \&do_gear_action;
176
    *do_updateui = \&privileged_action;
177
    *do_gear_updateui = \&do_gear_action;
178
}
179

    
180
sub do_listaccounts {
181
    my ($uuid, $action, $obj) = @_;
182
    if ($help) {
183
        return <<END
184
GET:common:
185
List other user accounts current user has access to use and switch to. This is an internal method which includes html
186
specifically for use with Dojo.
187
END
188
    }
189
    my $common = $params{'common'};
190
    my %bhash;
191
    my @accounts = split(/,\s*/, $register{$tktuser}->{'accounts'});
192
    my @accountsprivs = split(/,\s*/, $register{$tktuser}->{'accountsprivileges'});
193
    for my $i (0 .. $#accounts) {
194
        $bhash{$accounts[$i]} = {
195
            id=>$accounts[$i],
196
            privileges=>$accountsprivs[$i] || 'r'
197
        } if ($register{$accounts[$i]}); # Only include accounts that exist on this engine
198
    };
199
    $bhash{$tktuser} = {id=>$tktuser, privileges=>$privileges};
200
    delete $bhash{$user};
201
    $bhash{'common'} = {id=>'common', privileges=>'--'} if ($common);
202
    my @bvalues = values %bhash;
203
    unshift(@bvalues, {id=>$user, privileges=>$privileges});
204
    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 '};
205
    push(@bvalues, $logout) unless ($common);
206
    $postreply = "{\"identifier\": \"id\",\"label\": \"id\", \"items\":" . JSON::to_json(\@bvalues, {pretty=>1}) . "}";
207
    return $postreply;
208
}
209

    
210
sub do_listids {
211
    my ($uuid, $action, $obj) = @_;
212
    if ($help) {
213
        return <<END
214
GET::
215
List other user accounts current user has read access to. Call with flat=1 if you want a flat array.
216
END
217
    }
218
    require "$Stabile::basedir/cgi/images.cgi";
219
    my $backupdevice = Stabile::Images::Getbackupdevice('', 'getbackupdevice');
220
    my $imagesdevice = Stabile::Images::Getimagesdevice('', 'getimagesdevice');
221
    my $mounts = `cat /proc/mounts | grep zfs`;
222
    my %engine_h;
223
    my $zbackupavailable = ( (($mounts =~ /$backupdevice\/backup (\S+) zfs/) && ($mounts =~ /$imagesdevice\/images (\S+) zfs/) )?1:'');
224
    my $jsontext = qq|{"identifier": "id","label": "id", "items":[| .
225
              qq|{"id": "$user", "privileges": "$privileges", "userprivileges": "$dbprivileges", "tktuser": "$tktuser", |.
226
              qq|"storagequota": $storagequota, "nodestoragequota": $nodestoragequota, "memoryquota": $memoryquota, "vcpuquota": $vcpuquota, "vmemoryquota": $vmemoryquota, "vgpuquota": $vgpuquota, |.
227
              qq|"fullname": "$fullname", "email": "$email", "opemail": "$opemail", "alertemail": "$alertemail", |.
228
              qq|"phone": "$phone", "opphone": "$opphone", "opfullname": "$opfullname", "appstoreurl": "$appstoreurl", |.
229
              qq|"allowfrom": "$allowfrom", "lastlogin": "$lastlogin", "lastloginfrom": "$lastloginfrom", "allowinternalapi": "$allowinternalapi", "billto": "$billto", |.
230
              qq|"dnsdomain": "$dnsdomain", "appstoreurl": "$appstoreurl", |;
231

    
232
    if ($isadmin && $engineid) {
233
        $engine_h{"engineid"} = $engineid;
234
        $engine_h{"engineuser"} = $engineuser;
235
        $engine_h{"externaliprangestart"} = $externaliprangestart;
236
        $engine_h{"externaliprangeend"} = $externaliprangeend;
237
        $engine_h{"proxyiprangestart"} = $proxyiprangestart;
238
        $engine_h{"proxyiprangeend"} = $proxyiprangeend;
239
        $engine_h{"proxygw"} = $proxygw;
240

    
241
        $engine_h{"disablesnat"} = $disablesnat;
242
        $engine_h{"imagesdevice"} = $imagesdevice;
243
        $engine_h{"backupdevice"} = $backupdevice;
244

    
245
        my $nodecfg = new Config::Simple("/etc/stabile/nodeconfig.cfg");
246
        my $readlimit = $nodecfg->param('VM_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
247
        my $writelimit = $nodecfg->param('VM_WRITE_LIMIT');
248
        my $iopsreadlimit = $nodecfg->param('VM_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
249
        my $iopswritelimit = $nodecfg->param('VM_IOPS_WRITE_LIMIT');
250
        $engine_h{"vmreadlimit"} = $readlimit;
251
        $engine_h{"vmwritelimit"} = $writelimit;
252
        $engine_h{"vmiopsreadlimit"} = $iopsreadlimit;
253
        $engine_h{"vmiopswritelimit"} = $iopswritelimit;
254
        $engine_h{"enforceiolimits"} = $enforceiolimits;
255

    
256
        $engine_h{"zfsavailable"} = $zbackupavailable;
257
        $engine_h{"downloadmasters"} = $downloadmasters;
258
        $engine_h{"downloadallmasters"} = $downloadallmasters;
259
    }
260
    if (-e "/var/www/stabile/static/img/logo-icon-" . $ENV{HTTP_HOST} . ".png") {
261
        $jsontext .= qq|"favicon": "/stabile/static/img/logo-icon-$ENV{HTTP_HOST}.png", |;
262
    }
263
    $engine_h{"enginename"} = $enginename;
264
    $engine_h{"enginelinked"} = $enginelinked;
265
    $engine_h{"remoteipenabled"} = $Stabile::remoteipenabled;
266
    $engine_h{"gpupassthroughenabled"} = $Stabile::gpupassthroughenabled;
267
    $jsontext .= "\"showcost\": \"$showcost\", ";
268
    $jsontext .= "\"externalipquota\": $externalipquota, \"rxquota\": $rxquota, \"txquota\": $txquota, ";
269
    $jsontext .= qq|"defaultstoragequota": $defaultstoragequota, "defaultnodestoragequota": $defaultnodestoragequota, "defaultmemoryquota": $defaultmemoryquota, "defaultvcpuquota": $defaultvcpuquota, |;
270
    $jsontext .= "\"defaultexternalipquota\": $defaultexternalipquota, \"defaultrxquota\": $defaultrxquota, \"defaulttxquota\": $defaulttxquota, ";
271
    $jsontext .= qq|"engine": | . to_json(\%engine_h);
272
    $jsontext .= "},  ";
273
    # Add common user
274
    $jsontext .= "{\"id\": \"common\", \"privileges\": \"--\"," .
275
      "\"fullname\": \"--\", \"email\": \"--\"," .
276
      "\"storagequota\": 0, \"memoryquota\": 0, \"vcpuquota\": 0, \"vmemoryquota\": 0, \"vgpuquota\": 0, \"externalipquota\": 0," .
277
      "\"rxquota\": 0, \"txquota\": 0}";
278

    
279
    $jsontext .= ", {\"id\": \"$billto\"}" if ($billto && $billto ne '--');
280

    
281
    foreach my $aid (keys %ahash) {
282
        my $privs = $ahash{$aid};
283
        $jsontext .= qq|, {"id": "$aid", "privileges": "$privs"}| unless ($aid eq $user || $aid eq $billto);
284
    }
285

    
286
    $jsontext .= "]}";
287
    # Create ui_update link in case we are logging in with a remotely generated ticket, i.e. not passing through login.cgi
288
    `/bin/ln -s ../ui_update.cgi ../cgi/ui_update/$user~ui_update.cgi` unless (-e "../cgi/ui_update/$user~ui_update.cgi");
289
    $postreply = to_json(from_json($jsontext), {pretty=>1});
290
    return $postreply;
291
}
292

    
293
sub do_listengines{
294
    my ($uuid, $action, $obj) = @_;
295
    if ($help) {
296
        return <<END
297
GET::
298
List other engines user has access to
299
END
300
    }
301
    if ($enginelinked) {
302
        require LWP::Simple;
303
        my $browser = LWP::UserAgent->new;
304
        $browser->agent('stabile/1.0b');
305
        $browser->protocols_allowed( [ 'http','https'] );
306

    
307
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
308
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
309

    
310
        $postreq->{'engineid'} = $engineid;
311
        $postreq->{'user'} = $user;
312
        $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
313
        $postreq->{'api'} = $params{api};
314
        $postreq->{'usertkt'} = $params{auth_tkt};
315

    
316
        my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=listengines", $postreq)->content();
317
        if ($content =~ /ERROR:(.+)"/) {
318
            $postreply = qq|{"identifier": "url", "label": "name", "items": [{"url": "# $1", "name": "$enginename"}]}|;
319
        } else {
320
            $postreply = qq|{"identifier": "url", "label": "name", "items": $content}|;
321
        }
322
    } else {
323
        $postreply = qq|{"identifier": "url", "label": "name", "items": [{"url": "#", "name": "$enginename"}]}|;
324
    }
325
    return $postreply;
326
}
327

    
328
sub do_listengineconfigs{
329
    my ($uuid, $action, $obj) = @_;
330
    if ($help) {
331
        return <<END
332
GET::
333
List configs of engines user has access to
334
END
335
    }
336
    if ($enginelinked) {
337
        require LWP::Simple;
338
        my $browser = LWP::UserAgent->new;
339
        $browser->agent('stabile/1.0b');
340
        $browser->protocols_allowed( [ 'http','https'] );
341

    
342
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
343
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
344

    
345
        $postreq->{'engineid'} = $engineid;
346
        $postreq->{'user'} = $user;
347
        $postreq->{'username'} = $params{username};
348
        $postreq->{'usertkt'} = $params{auth_tkt};
349
        $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
350

    
351
        my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=listengineconfigs", $postreq)->content();
352
        $postreply = $content;
353
    } else {
354
        $postreply = qq|{"status": "Error", "message": "Engine not linked"}|;
355
    }
356
    return $postreply;
357
}
358

    
359
sub do_billing {
360
    my ($uuid, $action, $obj) = @_;
361
    if ($help) {
362
        return <<END
363
GET:uuid,username,month,startmonth,endmonth,format:
364
List usage data, optionally for specific server/system [uuid] or user [username]. May be called as usage, usagestatus or usageavgstatus.
365
When called as "usage", format may be csv, in which case startmonth and endmonth may be specified.
366
END
367
    }
368
    my $buser = $params{'user'} || $params{'username'} || $user;
369
    my $bmonth = $params{'month'} || $month;
370
    $bmonth = substr("0$bmonth", -2);
371
    my $byear = $params{'year'} || $year;
372
    my $vcpu=0, $memory=0, $vgpu=0, $vmemory=0, $virtualsize=0, $nodevirtualsize=0, $backupsize=0, $externalip=0;
373
    my $rx = 0;
374
    my $tx = 0;
375
    my $vcpuavg = 0;
376
    my $vgpuavg = 0;
377
    my $externalipavg = 0;
378
    $uuid = '' if ($register{$uuid}); # check if $uuid was set to $user because no actual uuid passed
379

    
380
    if ($user eq $buser || index($privileges,"a")!=-1) {
381
         my %stats = collectBillingData( $uuid, $buser, $bmonth, $byear, $showcost );
382
         my $memoryquotagb = int(0.5 + 100*$memoryquota/1024)/100;
383
         my $vmemoryquotagb = int(0.5 + 100*$vmemoryquota/1024)/100;
384
         my $storagequotagb = int(0.5 + 100*$storagequota/1024)/100;
385
         my $nodestoragequotagb = int(0.5 + 100*$nodestoragequota/1024)/100;
386
         my $irigo_cost = ($showcost?"showcost":"hidecost");
387

    
388
         if ($action eq "billing" || $action eq "usage") {
389
             if ($params{'format'} eq 'csv') {
390
                 $postreply = header("text/plain");
391
                 my $startmonth = $params{'startmonth'} || 1;
392
                 my $endmonth = $params{'endmonth'} || $bmonth;
393
                 my @vals;
394
                 for (my $i=$startmonth; $i<=$endmonth; $i++) {
395
                     my $m = substr("0$i", -2);
396
                     my %mstats = collectBillingData( $uuid, $buser, $m, $byear, $showcost );
397
                     push @vals, \%mstats;
398
                 }
399
                 csv(in => \@vals, out => \my $csvdata);
400
                 $postreply .= $csvdata;
401
             } else {
402
                 my $json_text = JSON::to_json(\%stats, {pretty => 1});
403
                 $postreply = "$json_text";
404
             }
405

    
406
         } elsif ($action eq "billingstatus" || $action eq "usagestatus") {
407
             my $virtualsizegb = $stats{'virtualsize'};
408
             my $backupsizegb = $stats{'backupsize'};
409
             my $externalip = $stats{'externalip'};
410
             my $memorygb = $stats{'memory'};
411
             my $vmemorygb = $stats{'vmemory'};
412
             my $nodevirtualsizegb = $stats{'nodevirtualsize'};
413
             $rx = $stats{'rx'};
414
             $tx = $stats{'tx'};
415
             $vcpu = $stats{'vcpu'};
416
             $vgpu = $stats{'vgpu'};
417

    
418
             my $res;
419
             if ($params{'format'} eq 'html') {
420
                 $postreply .= header("text/html");
421
                 $res .= qq[<tr><th>Ressource</th><th>Quantity</th><th class="$irigo_cost">Cost/month</th><th>Quota</th></tr>];
422

    
423
                 $res .= qq[<tr><td>vCPUs:</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>];
424
                 $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>];
425

    
426
                 $res .= qq[<tr><td>vGPUs:</td><td align="right">$vgpu</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$vgpu*$vgpuprice) . qq[</td><td align="right">$vgpuquota</td></tr>];
427
                 $res .= qq[<tr><td>vMemory:</td><td align="right">$vmemorygb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$vmemorygb*$vmemoryprice) . qq[</td><td align="right">$vmemoryquotagb GB</td></tr>];
428

    
429
                 $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>];
430
                 $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>];
431
                 $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>];
432
                 $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>];
433
                 if (!$uuid) {
434
                     $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>];
435
                     $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>];
436
                 }
437

    
438
                 $res =~ s/-1/&infin;/g;
439
                 $res =~ s/>0 .B<\/td><\/tr>/>&infin;<\/td><\/tr>/g;
440
                 $postreply .= qq[<table cellspacing="0" noframe="void" norules="rows" class="systemTables">$res</table>];
441
             } else {
442
                 my $bill = {
443
                     vcpus => {quantity => $vcpu, quota => $vcpuquota},
444
                     memory => {quantity => $memorygb, unit => 'GB', quota => $memoryquotagb},
445
                     vgpus => {quantity => $vgpu, quota => $vgpuquota},
446
                     vmemory => {quantity => $vmemorygb, unit => 'GB', quota => $vmemoryquotagb},
447
                     shared_storage => {quantity => $virtualsizegb, unit => 'GB', quota => $storagequotagb},
448
                     node_storage => {quantity => $nodevirtualsizegb, unit => 'GB', quota => $nodestoragequotagb},
449
                     backup_storage => {quantity => $backupsizegb, unit => 'GB'},
450
                     external_ips => {quantity => $externalip, quota => $externalipquota},
451
                     network_traffic_out => {quantity => $rx, unit => 'GB', quota => int(0.5 + $rxquota/1024/1024)},
452
                     network_traffic_in => {quantity => $tx, unit => 'GB', quota => int(0.5 + $txquota/1024/1024)}
453
                 };
454
                 if ($showcost) {
455
                     $bill->{vcpus}->{cost} = int(0.5+$vcpu*$vcpuprice);
456
                     $bill->{memory}->{cost} = int(0.5+$memorygb*$memoryprice);
457
                     $bill->{vgpus}->{cost} = int(0.5+$vgpu*$vgpuprice);
458
                     $bill->{vmemory}->{cost} = int(0.5+$vmemorygb*$vmemoryprice);
459
                     $bill->{shared_storage}->{cost} = int(0.5+$virtualsizegb*$storageprice);
460
                     $bill->{node_storage}->{cost} = int(0.5+$nodevirtualsizegb*$nodestorageprice);
461
                     $bill->{backup_storage}->{cost} = int(0.5+$backupsizegb*$storageprice);
462
                     $bill->{external_ips}->{cost} = int(0.5+$externalip*$externalipprice);
463
                     $bill->{currency} = $cur;
464
                     $bill->{username} = $buser;
465
                 }
466
                 $postreply .= to_json($bill, {pretty=>1});
467
             }
468
         } elsif ($action eq "billingavgstatus" || $action eq "usageavgstatus") {
469
             my $virtualsizeavggb = $stats{'virtualsizeavg'};
470
             my $backupsizeavggb = $stats{'backupsizeavg'};
471
             my $nodevirtualsizeavggb = $stats{'nodevirtualsizeavg'};
472
             my $memoryavggb = $stats{'memoryavg'};
473
             my $vmemoryavggb = $stats{'vmemoryavg'};
474
             $vcpuavg = $stats{'vcpuavg'};
475
             $vgpuavg = $stats{'vgpuavg'};
476
             $externalipavg = $stats{'externalipavg'};
477
             $rx = $stats{'rx'};
478
             $tx = $stats{'tx'};
479
             if ($params{'format'} eq 'html') {
480
                 $postreply .= header("text/html");
481
                 my $res;
482
                 $res .= qq[<tr><th>Ressource</th><th>Quantity</th><th class="$irigo_cost">Cost/month</th><th>Quota</th></tr>];
483

    
484
                 $res .= qq[<tr><td>vCPUs:</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>];
485
                 $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>];
486

    
487
                 $res .= qq[<tr><td>vGPUs:</td><td align="right">] . int(0.5+100*$vgpuavg)/100 . qq[</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$vgpuavg*$vgpuprice) . qq[</td><td align="right">$vgpuquota</td></tr>];
488
                 $res .= qq[<tr><td>vMemory:</td><td align="right">$vmemoryavggb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$vmemoryavggb*$vmemoryprice) . qq[</td><td align="right">$vmemoryquotagb GB</td></tr>];
489

    
490
                 $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>];
491
                 $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>];
492
                 $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>];
493
                 $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>];
494
                 $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>];
495
                 $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>];
496

    
497
                 $res =~ s/-1/&infin;/g;
498
                 $res =~ s/>0 .B<\/td><\/tr>/>&infin;<\/td><\/tr>/g;
499
                 $postreply .= qq[<table cellspacing="0" noframe="void" norules="rows" class="systemTables">$res</table>];
500
             } else {
501
                 my $bill = {
502
                     vcpus => {quantity => $vcpuavg, quota => $vcpuquota},
503
                     memory => {quantity => $memoryavggb, unit => 'GB', quota => $memoryquotagb},
504
                     vgpus => {quantity => $vgpuavg, quota => $vgpuquota},
505
                     vmemory => {quantity => $vmemoryavggb, unit => 'GB', quota => $vmemoryquotagb},
506
                     shared_storage => {quantity => $virtualsizeavggb, unit => 'GB', quota => $storagequotagb},
507
                     node_storage => {quantity => $nodevirtualsizeavggb, unit => 'GB', quota => $nodestoragequotagb},
508
                     backup_storage => {quantity => $backupsizeavggb, unit => 'GB'},
509
                     external_ips => {quantity => $externalipavg, quota => $externalipquota},
510
                     network_traffic_out => {quantity => int(0.5 + $rx), unit => 'GB', quota => int(0.5 + $rxquota/1024/1024)},
511
                     network_traffic_in => {quantity => int(0.5 + $tx), unit => 'GB', quota => int(0.5 + $txquota/1024/1024)}
512
                 };
513
                 if ($showcost) {
514
                     $bill->{vcpus}->{cost} = int(0.5+$vcpuavg*$vcpuprice);
515
                     $bill->{memory}->{cost} = int(0.5+$memoryavggb*$memoryprice);
516
                     $bill->{vgpus}->{cost} = int(0.5+$vgpuavg*$vgpuprice);
517
                     $bill->{vmemory}->{cost} = int(0.5+$vmemoryavggb*$vmemoryprice);
518
                     $bill->{shared_storage}->{cost} = int(0.5+$virtualsizeavggb*$storageprice);
519
                     $bill->{node_storage}->{cost} = int(0.5+$nodevirtualsizeavggb*$nodestorageprice);
520
                     $bill->{backup_storage}->{cost} = int(0.5+$backupsizeavggb*$storageprice);
521
                     $bill->{external_ips}->{cost} = int(0.5+$externalipavg*$externalipprice);
522
                     $bill->{currency} = $cur;
523
                     $bill->{username} = $buser;
524
                 }
525
                 $postreply .= to_json($bill, {pretty=>1});
526
             }
527
        }
528
    } else {
529
        $postreply .= "Status=ERROR no privileges!!\n";
530
    }
531
    return $postreply;
532
}
533

    
534
sub do_listenginebackups {
535
    my ($uuid, $action, $obj) = @_;
536
    if ($help) {
537
        return <<END
538
GET::
539
List the backups of this engine's configuration in the registry.
540
END
541
    }
542
    if ($enginelinked) {
543
        require LWP::Simple;
544
        my $browser = LWP::UserAgent->new;
545
        $browser->agent('stabile/1.0b');
546
        $browser->protocols_allowed( [ 'http','https'] );
547

    
548
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
549
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
550

    
551
        $postreq->{'engineid'} = $engineid;
552
        $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
553

    
554
        my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=listbackups", $postreq)->content();
555
        if ($content =~ /\[\]/) {
556
            $postreply = qq|{"identifier": "path", "label": "name", "items": [{"path": "#", "name": "No backups"}]}|;
557
        } else {
558
            $postreply = qq|{"identifier": "path", "label": "name", "items": $content}|;
559
        }
560
    } else {
561
        $postreply = qq|{"identifier": "path", "label": "name", "items": [{"path": "#", "name": "Engine not linked"}]}|;
562
    }
563
    return $postreply;
564
}
565

    
566
sub Backupengine {
567
    my ($uuid, $action, $obj) = @_;
568
    if ($help) {
569
        return <<END
570
GET::
571
Backup this engine's configuration to the registry.
572
END
573
    }
574
    my $backupname = "$enginename.$engineid.$pretty_time";
575
    $backupname =~ tr/:/-/; # tar has a problem with colons in filenames
576
    if (-e "/tmp/$backupname.tgz") {
577
        $postreply .= "Status=ERROR Engine is already being backed up";
578
    } else {
579
        $res .= `mysqldump --ignore-table=steamregister.nodeidentities steamregister > /etc/stabile/steamregister.sql`;
580
        $res .= `cp /etc/apache2/conf-available/auth_tkt_cgi.conf /etc/stabile`;
581
        $res .= `cp /etc/apache2/ssl/*.crt /etc/stabile`;
582
        $res .= `cp /etc/apache2/ssl/*.pem /etc/stabile`;
583
        $res .= `cp /etc/apache2/ssl/*.key /etc/stabile`;
584
        $res .= `cp /etc/hosts.allow /etc/stabile`;
585
        $res .= `cp /etc/mon/mon.cf /etc/stabile`;
586

    
587
        # copy default node configuration to /etc/stabile
588
        unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access identity register"};
589

    
590
        my $defaultpath = $idreg{'default'}->{'path'} . "/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
591
        $res .= `cp $defaultpath /etc/stabile`;
592

    
593
        # Make tarball
594
        my $cmd = qq[(cd /etc/stabile; /bin/tar -czf "/tmp/$backupname.tgz" * 2>/dev/null)];
595
        $res .= `$cmd`;
596

    
597
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
598
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
599
        my $enginetkthash = Digest::SHA::sha512_hex($tktkey);
600

    
601
        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`;
602
        if ($res =~ /OK: $backupname.tgz received/) {
603
            $postreply .= "Status=OK Engine configuration saved to the registry";
604
            $main::syslogit->($user, "info", "Engine configuration saved to the registry");
605
            unlink("/tmp/$backupname.tgz");
606
        } else {
607
            $postreply .= "Status=ERROR Problem backing configuration up to the registry\n$res\n";
608
        }
609
    }
610
    return $postreply;
611
}
612

    
613
sub Upgradeengine {
614
    my ($uuid, $action, $obj) = @_;
615
    if ($help) {
616
        return <<END
617
GET::
618
Try to upgrade this engine to latest release from the registry
619
END
620
    }
621
    $postreply = "Status=OK Requesting upgrade of Stabile\n";
622
    print header("text/plain"), $postreply;
623
    `echo "UPGRADE=1" >> /etc/stabile/config.cfg` unless ( `grep ^UPGRADE=1 /etc/stabile/config.cfg`);
624
    my $cmd = "echo 'sleep 5 ; /usr/bin/pkill pressurecontrol' | at now";
625
    system($cmd);
626
    exit 0;
627
}
628

    
629
sub do_billengine {
630
    my ($uuid, $action, $obj) = @_;
631
    if ($help) {
632
        return <<END
633
GET::
634
Submit billing data for this engine to the registry.
635
END
636
    }
637
    require LWP::Simple;
638
    my $browser = LWP::UserAgent->new;
639
    $browser->agent('stabile/1.0b');
640
    $browser->protocols_allowed( [ 'http','https'] );
641

    
642
    my $bmonth = $params{'month'} || $month;
643
    $bmonth = substr("0$bmonth", -2);
644
    my $byear = $params{'year'} || $year;
645
    $showcost = 1;
646

    
647
    my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
648
    my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
649
    my $tkthash = Digest::SHA::sha512_hex($tktkey);
650

    
651
    my $postreq = ();
652
    my %bill;
653
    my @regvalues = values %register; # Sort by id
654
    foreach my $valref (@regvalues) {
655
        my $cuser = $valref->{'username'};
656
        my %stats = collectBillingData( '', $cuser, $bmonth, $byear, $showcost );
657
        $bill{"$cuser-$byear-$bmonth"} = \%stats;
658
    }
659
    $postreq->{'engineid'} = $engineid;
660
    $postreq->{'enginetkthash'} = $tkthash;
661
    $postreq->{'keywords'} = JSON::to_json(\%bill, {pretty=>1});
662
    my $url = "https://www.stabile.io/irigo/engine.cgi";
663
    $content = $browser->post($url, $postreq)->content();
664
    $postreply = "Status=OK Billed this engine ($engineid)\n";
665
    $postreply .= "$postreq->{'keywords'}\n$content";
666
    return $postreply;
667
}
668

    
669
sub Linkengine {
670
    my ($uuid, $action, $obj) = @_;
671
    if ($help) {
672
        return <<END
673
PUT:username,password,engineid,enginename,engineurl:
674
Links engine to the registry
675
END
676
    }
677
    return "Status=Error Not allowed\n" unless ($isadmin || ($user eq $engineuser));
678
    my $linkaction = 'update';
679
    $linkaction = 'link' if ($action eq 'linkengine');
680
    $linkaction = 'unlink' if ($action eq 'unlinkengine');
681
    $linkaction = 'update' if ($action eq 'updateengine');
682
    $linkaction = 'update' if ($action eq 'syncusers');
683

    
684
    require LWP::Simple;
685
    my $browser = LWP::UserAgent->new;
686
    $browser->agent('stabile/1.0b');
687
    $browser->protocols_allowed( [ 'http','https'] );
688

    
689
    my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
690
    my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
691

    
692
    my $postreq = ();
693
    $postreq->{'user'} = $user || $obj->{'username'};
694
    $postreq->{'engineid'} = $obj->{'engineid'} || $engineid;
695
    $postreq->{'pwd'} = $obj->{'pwd'} if ($obj->{'pwd'});
696
    $postreq->{'enginename'} = $obj->{'enginename'} if ($obj->{'enginename'});
697
    $postreq->{'engineurl'} = $obj->{'engineurl'} if ($obj->{'engineurl'});
698
    if ($tktkey) {
699
        if ($action eq 'linkengine') {
700
            $main::syslogit->($user, "info", "Linking engine with the registry");
701
            $postreq->{'enginetktkey'} = $tktkey;
702
        } else {
703
            $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
704
        }
705
    }
706
    if ($action eq "saveengine") { # Save request from the registry - don't post back
707
        # Pressurecontrol reads new configuration data from the registry, simply reload it
708
        my $pressureon = !(`systemctl is-active pressurecontrol` =~ /inactive/);
709
        $postreply = ($pressureon)? "Status=OK Engine updating...\n":"Status=OK Engine not updating because pressurecontrol not active\n";
710
        $postreply .= `systemctl restart pressurecontrol` if ($pressureon);
711
    } else {
712
        my $res;
713
        my $cfg = new Config::Simple("/etc/stabile/config.cfg");
714
        if ($action eq 'linkengine' || $action eq 'syncusers') {
715
            # Send engine users to the registry
716
            my @vals = values %register;
717
            my $json = JSON::to_json(\@vals);
718
            $json =~ s/null/""/g;
719
            $json = URI::Escape::uri_escape($json);
720
            $postreq->{'POSTDATA'} = $json;
721
        }
722
        if ($action eq 'linkengine' || $action eq 'updateengine') {
723
            # Update name in config file
724
            if ($postreq->{'enginename'} && $cfg->param("ENGINENAME") ne $postreq->{'enginename'}) {
725
                $cfg->param("ENGINENAME", $postreq->{'enginename'});
726
                $cfg->save();
727
            }
728
            # Send entire engine config file to the registry
729
            my %cfghash = $cfg->vars();
730
            foreach my $param (keys %cfghash) {
731
                $param =~ /default\.(.+)/; # Get rid of default. prefix
732
                if ($1) {
733
                    my $k = $1;
734
                    my @cvals = $cfg->param($param);
735
                    my $cval = join(", ", @cvals);
736
                    $postreq->{$k} = URI::Escape::uri_escape($cval);
737
                }
738
            }
739
            # Send entire engine piston config file to the registry
740
            my $nodeconfigfile = "/mnt/stabile/tftp/bionic/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
741
            if (-e $nodeconfigfile) {
742
                my $pistoncfg = new Config::Simple($nodeconfigfile);
743
                %cfghash = $pistoncfg->vars();
744
                foreach my $param (keys %cfghash) {
745
                    $param =~ /default\.(.+)/; # Get rid of default. prefix
746
                    if ($1) {
747
                        my $k = $1;
748
                        my @cvals = $pistoncfg->param($param);
749
                        my $cval = join(", ", @cvals);
750
                        $postreq->{$k} = URI::Escape::uri_escape($cval);
751
                    }
752
                }
753
            }
754
        }
755
        if ($linkaction eq 'link' || $enginelinked) {
756
            my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=$linkaction", $postreq)->content();
757
            if ($content =~ /(Engine linked|Engine not linked|Engine unlinked|Engine updated|Unknown engine|Invalid credentials .+\.)/i) {
758
                $res = "Status=OK $1";
759
                my $linked = 1;
760
                $linked = 0 unless ($content =~ /Engine linked/i || $content =~ /Engine updated/i);
761
                $cfg->param("ENGINE_LINKED", $linked);
762
                $cfg->save();
763
            } elsif ($action eq 'syncusers' || $action eq 'linkengine') { # If we send user list to the registry we get merged list back
764
                if ($content =~ /^\[/) { # Sanity check to see if we got json back
765
                    $res .= "Status=OK Engine linked\n" if ($action eq 'linkengine');
766
                    # Update engine users with users from the registry
767
                    $res .= updateEngineUsers($content);
768
                    $res .= "Status=OK Users synced with registry\n";
769
                    $main::updateUI->({ tab => 'users', type=>'update', user=>$user});
770
                }
771
                $res .= "$content" unless ($res =~ /Status=OK/); # Only add if there are problems
772
            }
773
            $postreply = $res;
774
            $content =~ s/\n/ - /;
775
            $res =~ s/\n/ - /;
776
        #    $main::syslogit->($user, "info", "$content");
777
            $main::syslogit->($user, "info", "Synced users");
778
        } else {
779
            $postreply .= "Status=OK Engine not linked, saving name\n";
780
        }
781
    }
782
    return $postreply;
783
}
784

    
785
sub Releasepressure {
786
    my ($uuid, $action, $obj) = @_;
787
    if ($help) {
788
        return <<END
789
GET::
790
Restarts pressurecontrol.
791
END
792
    }
793
    my $res;
794
    unless (`systemctl is-active pressurecontrol` =~ /inactive/) {
795
        my $daemon = Proc::Daemon->new(
796
            work_dir => '/usr/local/bin',
797
            exec_command => "systemctl restart pressurecontrol"
798
        ) or do {$postreply .= "Status=ERROR $@\n";};
799
        my $pid = $daemon->Init();
800
#        $res = `systemctl restart pressurecontrol`;
801
        return "Status=OK Venting...\n";
802
    } else {
803
        return "Status=OK Not venting\n";
804
    }
805
}
806

    
807
sub do_enable {
808
    my ($uuid, $action, $obj) = @_;
809
    if ($help) {
810
        return <<END
811
GET:username:
812
Enable a user.
813
END
814
    }
815
    my $username = $obj->{'username'};
816
    return unless ($username);
817
    if ($isadmin || ($user eq $engineuser)) {
818
        # Create user on this engine if not yet created
819
        do_save($username, 'save', $obj);
820
        my $uprivileges = $register{$username}->{'privileges'};
821
        $uprivileges =~ s/d//;
822
        $uprivileges .= 'n' unless ($uprivileges =~ /n/);# These are constant sources of problems - enable by default when enabling users to alleviate situation
823
        $register{$username}->{'privileges'} = $uprivileges;
824
        $register{$username}->{'allowinternalapi'} = 1;
825
        $postreply .= "Status=OK User $username enabled\n";
826
    } else {
827
        $postreply .= "Status=ERROR Not allowed\n";
828
    }
829
    $uiuuid = $username;
830
    return $postreply;
831
}
832

    
833
sub do_disable {
834
    my ($uuid, $action, $obj) = @_;
835
    if ($help) {
836
        return <<END
837
GET:username:
838
Disable a user.
839
END
840
    }
841
    my $username = $obj->{'username'};
842
    if ($isadmin || ($user eq $engineuser)) {
843
        my $uprivileges = $register{$username}->{'privileges'};
844
        $uprivileges .= 'd' unless ($uprivileges =~ /d/);
845
        $register{$username}->{'privileges'} = $uprivileges;
846
        $postreply .= "Stream=OK User $username disabled, halting servers...\n";
847
        require "$Stabile::basedir/cgi/servers.cgi";
848
        $Stabile::Servers::console = 1;
849
        $postreply .= Stabile::Servers::destroyUserServers($username,1);
850
        `/bin/rm /tmp/$username~*.tasks`;
851
    } else {
852
        $postreply .= "Status=ERROR Not allowed\n";
853
    }
854
    $uiuuid = $username;
855
    return $postreply;
856
}
857

    
858
sub Updateui {
859
    my ($uuid, $action, $obj) = @_;
860
    if ($help) {
861
        return <<END
862
GET:username,message,tab:
863
Update the UI for given user if logged into UI.
864
END
865
    }
866
    my $username = $obj->{'username'} || $user;
867
    my $message = $obj->{'message'};
868
    my $tab = $obj->{'tab'} || 'home';
869
    if ($isadmin || ($username eq $user) || ($user eq $engineuser)) {
870
        $postreply = $main::updateUI->({ tab => $tab, user => $username, message =>$message, type=>'update'});
871
    } else {
872
        $postreply = "Status=ERROR Not allowed\n";
873
    }
874
}
875

    
876
sub do_updateclientui {
877
    my ($uuid, $action, $obj) = @_;
878
    if ($help) {
879
        return <<END
880
GET:username,message,tab,type:
881
Update the UI for given user if logged into UI.
882
END
883
    }
884
    my $username = $obj->{'username'} || $user;
885
    my $message = $obj->{'message'};
886
    my $tab = $obj->{'tab'} || 'home';
887
    my $type= $obj->{'type'} || 'update';
888
    if ($isadmin || ($username eq $user) || ($user eq $engineuser)) {
889
        $postreply = $main::updateUI->({ tab => $tab, user => $username, message =>$message, type=>$type});
890
    } else {
891
        $postreply = "Status=ERROR Not allowed\n";
892
    }
893
}
894

    
895
sub Gettimezone {
896
    my ($uuid, $action, $obj) = @_;
897
    if ($help) {
898
        return <<END
899
GET::
900
Returns the timezone of the engine. Useful for setting timezone on VMs, specifically Kubernetes nodes.
901
END
902
    }
903
    my $tz = `cat /etc/timezone`;
904
    chomp $tz;
905
    $postreply = qq|{"timezone": "$tz"}\n|;
906
    return $postreply;
907
}
908

    
909
sub Vent {
910
    my ($uuid, $action, $obj) = @_;
911
    if ($help) {
912
        return <<END
913
GET::
914
Restart pressurecontrol.
915
END
916
    }
917
    if ($isadmin) {
918
        my $daemon = Proc::Daemon->new(
919
            work_dir => '/tmp',
920
            exec_command => "systemctl restart pressurecontrol"
921
        ) or do {$postreply .= "Status=ERROR $@\n";};
922
        my $pid = $daemon->Init();
923
        $postreply = "Status=OK Restarting pressurecontrol\n";
924
    } else {
925
        $postreply = "Status=Error Not allowed\n";
926
    }
927
    return $postreply;
928
}
929

    
930
sub Deleteentirely {
931
    my ($uuid, $action, $obj) = @_;
932
    if ($help) {
933
        return <<END
934
GET:username:
935
Deletes a user and all the user's servers, images, networks etc. Warning: This destroys data
936
END
937
    }
938
    my $username = $obj->{'username'};
939
    my $reply = "Status=OK Removed $username";
940
    if (($isadmin || ($user eq $engineuser)) && $register{$username} && !($register{$username}->{'privileges'} =~ /a/) && !($username eq $engineuser)) {
941
        #Never delete admins
942
        my @dusers = ($username);
943
        # Add list of subusers - does not look like a good idea
944
        # foreach my $u (values %register) {
945
        #     push @dusers, $u->{'username'} if ($u->{'billto'} && $u->{'billto'} eq $username);
946
        # };
947

    
948
        foreach my $uname (@dusers) {
949
            if ($register{$uname}->{privileges} =~ /a/) { #Never delete admins
950
                $postreply .= "Stream=OK Not deleting user $uname - demote before deleting!\n";
951
                next;
952
            }
953
            $main::updateUI->({ tab => 'users', type=>'update', user=>$user, username=>$username, status=>'deleting'});
954

    
955
            $postreply .= "Stream=OK Deleting user $uname and all associated data!!!\n";
956
            $main::syslogit->($user, "info", "Deleting user $uname and all associated data");
957

    
958
            require "$Stabile::basedir/cgi/servers.cgi";
959
            $Stabile::Servers::console = 1;
960
            $Stabile::Servers::isadmin = $isadmin;
961
            require "$Stabile::basedir/cgi/systems.cgi";
962
            $Stabile::Systems::console = 1;
963
            $Stabile::Systems::isadmin = $isadmin;
964
            Stabile::Systems::removeusersystems($uname);
965
            Stabile::Servers::removeUserServers($uname);
966

    
967
            require "$Stabile::basedir/cgi/images.cgi";
968
            $Stabile::Images::console = 1;
969
            $postreply .= Stabile::Images::removeUserImages($uname);
970

    
971
            require "$Stabile::basedir/cgi/networks.cgi";
972
            $Stabile::Networks::console = 1;
973
            $Stabile::Networks::isadmin = $isadmin;
974
            Stabile::Networks::Removeusernetworks($uname);
975
            remove($uname);
976
            $reply = "$reply\n$postreply";
977

    
978
            do_billengine(); # Send latest billing data to origo before removing user
979
            # Also remove billing data from previous months - these are assumed reported to origo for linked and billed engines
980
            `echo "delete from billing_domains where (usernodetime like '$uname-%') AND (not (usernodetime LIKE '$uname-%-$year-$month'));" | mysql steamregister`;
981
            `echo "delete from billing_images where (userstoragepooltime like '$uname-%') AND (not (userstoragepooltime LIKE '$uname-%-$year-$month'));" | mysql steamregister`;
982
            `echo "delete from billing_networks where (useridtime like '$uname-%') AND (not (useridtime LIKE '$uname-%-$year-$month'));" | mysql steamregister`;
983
        }
984
        $main::updateUI->({tab => 'users', type=>'update', user=>$user});
985

    
986
    } else {
987
        $postreply .= "Stream=ERROR Cannot delete user $username - you cannot delete administrators!\n";
988
        $reply = $postreply;
989
    }
990
    return $reply;
991
}
992

    
993
sub do_save {
994
    my ($username, $action, $obj) = @_;
995
    if ($help) {
996
        return <<END
997
POST:username, password, privileges, fullname, email, opemail, alertemail, phone, opphone, opfullname, allowfrom, allowinternalapi, accounts, accountsprivileges, storagepools, memoryquota, vcpuquota, vmemoryquota, vgpuquota, storagequota, nodestoragequota, externalipquota, rxquota, txquota:
998
Saves a user. If [username] does not exist, it is created if privileges allow this. [password] can be plaintext or a SHA256 hash.
999
END
1000
    }
1001
    $username = $username || $obj->{"username"};
1002
    unless ($username && (($user eq $username) || $isadmin || ($user eq $engineuser))) {
1003
        $postreply = "Status=ERROR Please provide a valid username\n";
1004
        return $postreply;
1005
    }
1006
    my $password = '';
1007
    my $reguser = $register{$username};
1008
    if ($obj->{"password"} && $obj->{"password"} ne '--') {
1009
        if (length $obj->{'password'} == 86) {
1010
            $password = $obj->{"password"}; # This is already encoded
1011
        } else {
1012
            $password = $obj->{"password"};
1013
            $MAXLEN = 20;
1014
            my $msg = IsBadPassword($password);
1015
            if ($msg) {
1016
                $postreply = "Status=Error $msg - please choose a stronger password\n";
1017
                $postmsg = "$msg - please choose a stronger password";
1018
                return $postreply;
1019
            } else {
1020
                $password = Digest::SHA::sha512_base64($password);
1021
            }
1022
        }
1023
    } else {
1024
        $password = $reguser->{'password'};
1025
    }
1026
    my $fullname = $obj->{"fullname"} || $reguser->{'fullname'};
1027
    my $email = $obj->{"email"} || $reguser->{'email'};
1028
    my $opemail = $obj->{"opemail"} || $reguser->{'opemail'};
1029
    my $alertemail = $obj->{"alertemail"} || $reguser->{'alertemail'};
1030
    my $phone = $obj->{"phone"} || $reguser->{'phone'};
1031
    my $opphone = $obj->{"opphone"} || $reguser->{'opphone'};
1032
    my $opfullname = $obj->{"opfullname"} || $reguser->{'opfullname'};
1033
    my $allowfrom = $obj->{"allowfrom"};
1034
    my $totpsecret = $reguser->{'totpsecret'};
1035
    $totpsecret = $obj->{"totpsecret"} if (defined $obj->{"totpsecret"});
1036
    my $allowinternalapi = $obj->{"allowinternalapi"} || $reguser->{'allowinternalapi'};
1037

    
1038
    if (defined $obj->{"allowfrom"}) {
1039
        my @allows = split(/(,\s*|\s+)/, $allowfrom);
1040
        $allowfrom = '';
1041
        my %allowshash;
1042
        foreach my $ip (@allows) {
1043
            $allowshash{"$1$2"} = 1 if ($ip =~ /(\d+\.\d+\.\d+\.\d+)(\/\d+)?/);
1044
            if ($ip =~ /\w\w/) { # Check if we are dealing with a country code
1045
                $ip = uc $ip;
1046
                my $geoip = Geo::IP->new(GEOIP_MEMORY_CACHE);
1047
                my $tz = $geoip->time_zone($ip, '');
1048
                $allowshash{$ip} = 1 if ($tz); # We have a valid country code
1049
            }
1050
        }
1051
        $allowfrom = join(", ", sort(keys %allowshash));
1052
    }
1053

    
1054
    my $uprivileges = $reguser->{'privileges'};
1055
    my $uaccounts = $reguser->{'accounts'};
1056
    my $uaccountsprivileges = $reguser->{'accountsprivileges'};
1057
    my $storagepools = $reguser->{'storagepools'};
1058
    my $storagequota = $reguser->{'storagequota'};
1059
    my $nodestoragequota = $reguser->{'nodestoragequota'};
1060
    my $memoryquota = $reguser->{'memoryquota'};
1061
    my $vcpuquota = $reguser->{'vcpuquota'};
1062
    my $vmemoryquota = $reguser->{'vmemoryquota'};
1063
    my $vgpuquota = $reguser->{'vgpuquota'};
1064
    my $externalipquota = $reguser->{'externalipquota'};
1065
    my $rxquota = $reguser->{'rxquota'};
1066
    my $txquota = $reguser->{'txquota'};
1067
    my $tasks = $reguser->{'tasks'};
1068
    my $ubillto = $reguser->{'billto'};
1069
    my $udnsdomains = $reguser->{'dnsdomains'};
1070
    my $uappstoreurl = $reguser->{'appstoreurl'}; $uappstoreurl = '' if ($uappstoreurl eq '--');
1071
    my $created = $reguser->{'created'} || $current_time; # set created timestamp for new users
1072

    
1073
    # Only allow admins to change user privileges and quotas
1074
    if ($isadmin || $user eq $engineuser) {
1075
        $uprivileges = $obj->{"privileges"} || $reguser->{'privileges'};
1076
        $uprivileges = '' if ($uprivileges eq '--');
1077
        $uprivileges = 'n' if (!$reguser->{'username'} && !$uprivileges); # Allow new users to use node storage unless explicitly disallowed
1078
        $uprivileges =~ tr/adnrpu//cd; # filter out non-valid privileges
1079
        $uprivileges =~ s/(.)(?=.*?\1)//g; # filter out duplicates using positive lookahead
1080
        $storagepools = ($obj->{"storagepools"} || $obj->{"storagepools"} eq '0')?$obj->{"storagepools"} : $reguser->{'storagepools'};
1081
        $memoryquota = (defined $obj->{"memoryquota"}) ? $obj->{"memoryquota"} : $reguser->{'memoryquota'};
1082
        $vcpuquota = (defined $obj->{"vcpuquota"}) ? $obj->{"vcpuquota"} : $reguser->{'vcpuquota'};
1083
        $vmemoryquota = (defined $obj->{"vmemoryquota"}) ? $obj->{"vmemoryquota"} : $reguser->{'vmemoryquota'};
1084
        $vgpuquota = (defined $obj->{"vgpuquota"}) ? $obj->{"vgpuquota"} : $reguser->{'vgpuquota'};
1085
        $storagequota = (defined $obj->{"storagequota"}) ? $obj->{"storagequota"} : $reguser->{'storagequota'};
1086
        $nodestoragequota = (defined $obj->{"nodestoragequota"}) ? $obj->{"nodestoragequota"} : $reguser->{'nodestoragequota'};
1087
        $externalipquota = (defined $obj->{"externalipquota"}) ? $obj->{"externalipquota"} : $reguser->{'externalipquota'};
1088
        $rxquota = (defined $obj->{"rxquota"}) ? $obj->{"rxquota"} : $reguser->{'rxquota'};
1089
        $txquota = (defined $obj->{"txquota"}) ? $obj->{"txquota"} : $reguser->{'txquota'};
1090
        $tasks = $obj->{"tasks"} || $reguser->{'tasks'};
1091
        $ubillto = $obj->{"billto"} || $reguser->{'billto'};
1092
        $udnsdomains = $obj->{"dnsdomains"} || $udnsdomains; $udnsdomains = '' if ($udnsdomains eq '--');
1093
        $uappstoreurl = $obj->{"appstoreurl"} || $uappstoreurl;
1094
        $uaccounts = $obj->{"accounts"} || $reguser->{'accounts'};
1095
        $uaccountsprivileges = $obj->{"accountsprivileges"} || $reguser->{'accountsprivileges'};
1096
        my @ua = split(/, ?/, $uaccounts);
1097
        my @up = split(/, ?/, $uaccountsprivileges);
1098
        my @ua2 = ();
1099
        my @up2 = ();
1100
        my $i = 0;
1101
        foreach my $u (@ua) {
1102
            if ($register{$u} && ($u ne $username)) {
1103
                push @ua2, $u;
1104
                my $uprivs = $up[$i] || 'u';
1105
                $uprivs =~ tr/adnrpu//cd; # filter out non-valid privileges
1106
                $uprivs =~ s/(.)(?=.*?\1)//g; # filter out duplicates using positive lookahead
1107
                push @up2, $uprivs;
1108
            }
1109
            $i++;
1110
        }
1111
        $uaccounts = join(", ", @ua2);
1112
        $uaccountsprivileges = join(", ", @up2);
1113
    }
1114

    
1115
    # Sanity checks
1116
    if (
1117
        ($fullname && length $fullname > 255)
1118
            || ($password && length $password > 255)
1119
    ) {
1120
        $postreply .= "Status=ERROR Bad data: $username\n";
1121
        return  $postreply;
1122
    }
1123
    # Only allow new users to be created by admins, i.e. no auto-registration
1124
    if ($reguser->{'username'} || $isadmin) {
1125
        $register{$username} = {
1126
            password           => $password,
1127
            fullname           => $fullname,
1128
            email              => $email,
1129
            opemail            => $opemail,
1130
            alertemail         => $alertemail,
1131
            phone              => $phone,
1132
            opphone            => $opphone,
1133
            opfullname         => $opfullname,
1134
            allowfrom          => $allowfrom,
1135
            totpsecret         => $totpsecret,
1136
            privileges         => $uprivileges,
1137
            accounts           => $uaccounts,
1138
            accountsprivileges => $uaccountsprivileges,
1139
            storagepools       => $storagepools,
1140
            memoryquota        => $memoryquota+0,
1141
            vcpuquota          => $vcpuquota+0,
1142
            vmemoryquota       => $vmemoryquota+0,
1143
            vgpuquota          => $vgpuquota+0,
1144
            storagequota       => $storagequota+0,
1145
            nodestoragequota   => $nodestoragequota+0,
1146
            externalipquota    => $externalipquota+0,
1147
            rxquota            => $rxquota+0,
1148
            txquota            => $txquota+0,
1149
            tasks              => $tasks,
1150
            allowinternalapi   => $allowinternalapi || 1, # specify '--' to explicitly disallow
1151
            billto             => $ubillto,
1152
            dnsdomains         => $udnsdomains,
1153
            appstoreurl        => $uappstoreurl,
1154
            created            => $created,
1155
            modified           => $current_time,
1156
            action             => ""
1157
        };
1158
        my %uref = %{$register{$username}};
1159
        $uref{result} = "OK";
1160
        $uref{password} = "";
1161
        $uref{status} = ($uprivileges =~ /d/)?'disabled':'enabled';
1162
        $postreply = JSON::to_json(\%uref, { pretty => 1 });
1163
#        $postreply =~ s/""/"--"/g;
1164
        $postreply =~ s/null/""/g;
1165
#        $postreply =~ s/\x/ /g;
1166
    }
1167
    return $postreply;
1168
}
1169

    
1170
sub do_list {
1171
    my ($uuid, $action, $obj) = @_;
1172
    if ($help) {
1173
        return <<END
1174
GET::
1175
List users registered on this engine.
1176
END
1177
    }
1178
    my $userfilter;
1179
    my $usermatch;
1180
    my $propmatch;
1181
    if ($uripath =~ /users(\.cgi)?\/(\?|)(me|this)/) {
1182
        $usermatch = $user;
1183
        $propmatch = $4 if ($uripath =~ /users(\.cgi)?\/(\?|)(me|this)\/(.+)/);
1184
    } elsif ($uripath =~ /users(\.cgi)?\/(\?|)(username)/) {
1185
        $userfilter = $3 if ($uripath =~ /users(\.cgi)?\/\??username(:|=)(.+)/);
1186
        $userfilter = $1 if ($userfilter =~ /(.*)\*/);
1187
    } elsif ($uripath =~ /users(\.cgi)?\/(\S+)/) {
1188
        $usermatch = $2;
1189
        $propmatch = $4 if ($uripath =~ /users(\.cgi)?\/(\S+)\/(.+)/);
1190
    }
1191

    
1192
    my @regvalues = (sort {$a->{'id'} <=> $b->{'id'}} values %register); # Sort by id
1193
    my @curregvalues;
1194

    
1195
    foreach my $valref (@regvalues) {
1196
        my $reguser = $valref->{'username'};
1197
        if ($user eq $reguser || $isadmin) {
1198
            next if ($reguser eq 'irigo' || $reguser eq 'guest');
1199
            my %val = %{$valref}; # Deference and assign to new ass array, effectively cloning object
1200
            $val{'password'} = '';
1201
            $val{'status'} = ($val{'privileges'} =~ /d/)?'disabled':'enabled';
1202
            if ((!$userfilter && !$usermatch) || ($userfilter && $reguser =~ /$userfilter/) || $reguser eq $usermatch) {
1203
                push @curregvalues,\%val;
1204
            }
1205
        }
1206
    }
1207
    if ($action eq 'tablelist') {
1208
        my $t2 = Text::SimpleTable->new(14,32,24,10);
1209

    
1210
        $t2->row('username', 'fullname', 'lastlogin', 'privileges');
1211
        $t2->hr;
1212
        my $pattern = $options{m};
1213
        foreach $rowref (@curregvalues){
1214
            if ($pattern) {
1215
                my $rowtext = $rowref->{'username'} . " " . $rowref->{'fullname'} . " " . $rowref->{'lastlogin'}
1216
                               . " " .  $rowref->{'privileges'};
1217
                $rowtext .= " " . $rowref->{'mac'} if ($isadmin);
1218
                next unless ($rowtext =~ /$pattern/i);
1219
            }
1220
            $t2->row($rowref->{'username'}, $rowref->{'fullname'}||'--', localtime($rowref->{'lastlogin'})||'--',
1221
            $rowref->{'privileges'}||'--');
1222
        }
1223
        #$t2->row('common', '--', '--', '--');
1224
        #$t2->row('all', '--', '--', '--') if (index($privileges,"a")!=-1);
1225
        $postreply .= $t2->draw;
1226
    } elsif ($console) {
1227
        $postreply = Dumper(\@curregvalues);
1228
    } else {
1229
        my $json_text;
1230
        if ($propmatch) {
1231
            $json_text = JSON::to_json($curregvalues[0]->{$propmatch}, {allow_nonref=>1});
1232
        } else {
1233
            $json_text = JSON::to_json(\@curregvalues, {pretty=>1});
1234
        }
1235
        $json_text =~ s/"--"/""/g;
1236
        $json_text =~ s/null/""/g;
1237
#        $json_text =~ s/\x/ /g;
1238
        $postreply = qq|{"identifier": "username", "label": "username", "items": | unless ($usermatch || $action ne 'listusers');
1239
        $postreply .= $json_text;
1240
        $postreply .= "}\n" unless ($usermatch || $action ne 'listusers');
1241
    }
1242
    return $postreply;
1243
}
1244

    
1245
sub do_uuidlookup {
1246
    if ($help) {
1247
        return <<END
1248
GET:uuid:
1249
Simple action for looking up a username (uuid) or part of a username and returning the complete username.
1250
END
1251
    }
1252
    my $u = $options{u};
1253
    $u = $params{'uuid'} unless ($u || $u eq '0');
1254
    if ($u || $u eq '0') {
1255
        foreach my $uuid (keys %register) {
1256
            if ($uuid =~ /^$u/) {
1257
                return "$uuid\n" if ($uuid eq $user || index($privileges,"a")!=-1);
1258
            }
1259
        }
1260
    }
1261
}
1262

    
1263
sub do_uuidshow {
1264
    if ($help) {
1265
        return <<END
1266
GET:uuid:
1267
Simple action for showing a single user. Pass username as uuid.
1268
END
1269
    }
1270
    my $u = $options{u};
1271
    $u = $params{'uuid'} unless ($u || $u eq '0');
1272
    if ($u eq $user || index($privileges,"a")!=-1) {
1273
        foreach my $uuid (keys %register) {
1274
            if ($uuid =~ /^$u/) {
1275
                my %hash = %{$register{$uuid}};
1276
                delete $hash{'action'};
1277
                my $dump = to_json(\%hash, {pretty=>1});
1278
                $dump =~ s/undef/"--"/g;
1279
                return $dump;
1280
            }
1281
        }
1282
    }
1283
}
1284

    
1285
sub Restoreengine {
1286
    my ($uuid, $action, $obj) = @_;
1287
    if ($help) {
1288
        return <<END
1289
GET:restorefile:
1290
Restores this engine's configuration from "restorefile", which must be one of the paths listed in listenginebackups
1291
END
1292
    }
1293
    if (!$isadmin) {
1294
        $postreply = "Status=ERROR You must be an administrator in order to restore this engine";
1295
    } else {
1296
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
1297
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
1298
        my $enginetkthash = Digest::SHA::sha512_hex($tktkey);
1299

    
1300
        my $restoredir = "/etc";
1301
        my $dbname = "steamregister";
1302
        my $restorefile = $obj->{'restorefile'};
1303

    
1304
        if ($restorefile && !($restorefile =~ /\//)) {
1305
            my $urifile = URI::Escape::uri_escape($restorefile);
1306
            my $uri = "https://www.stabile.io/irigo/engine.cgi";
1307
            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"|;
1308
            my $res = `$cmd`;
1309
            if (-s "/tmp/$restorefile") {
1310
                $res .= `(mkdir $restoredir/stabile; cd $restoredir/stabile; /bin/tar -zxf "/tmp/$restorefile")`;
1311
                $res .= `/usr/bin/mysql -e "create database $dbname;"`;
1312
                $res .= `/usr/bin/mysql $dbname < $restoredir/stabile/steamregister.sql`;
1313
                $res .= `cp -b $restoredir/stabile/hosts.allow /etc/hosts.allow`;
1314
                $res .= `cp -b $restoredir/stabile/auth_tkt_cgi.conf /etc/apache2/conf.d/`;
1315
                $res .= `cp -b $restoredir/stabile/*.crt /etc/apache2/ssl/`;
1316
                $res .= `cp -b $restoredir/stabile/*.key /etc/apache2/ssl/`;
1317
                $res .= `cp -b $restoredir/stabile/mon.cf /etc/mon/`;
1318
                $res .= `service apache2 reload`;
1319

    
1320
                # Restore default node configuration
1321
                unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access identity register"};
1322
                my $defaultpath = $idreg{'default'}->{'path'} . "/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
1323
                untie %idreg;
1324
                $res .=  `cp $restoredir/stabile/nodeconfig.cfg $defaultpath`;
1325
                $main::syslogit->($user, "info", "Engine configuration $restorefile restored from the registry");
1326
                $postreply .= "Status=OK Engine configuration $restorefile restored from the registry - reloading UI\n";
1327
            } else {
1328
                $postreply .= "Status=ERROR Restore failed, $restorefile not found...\n";
1329
            }
1330
        } else {
1331
            $postreply .= "Status=ERROR You must select a restore file\n";
1332
        }
1333
    }
1334
    return $postreply;
1335
}
1336

    
1337
# Print list of available actions on objects
1338
sub do_plainhelp {
1339
    my $res;
1340
    $res .= header('text/plain') unless $console;
1341
    $res .= <<END
1342
new [username="name", password="password"]
1343
* enable: Enables a disabled user
1344
* disable: Disables a user, disallowing login
1345
* remove: Deletes a user, leaving servers, images, networks etc. untouched
1346
* deleteentirely: Deletes a user and all the user's servers, images, networks etc. Warning: This destroys data
1347

    
1348
END
1349
;
1350
}
1351

    
1352
sub do_cleanbillingdata {
1353
    my ($uuid, $action, $obj) = @_;
1354
    if ($help) {
1355
        return <<END
1356
GET:year,dryrun,cleanup:
1357
Deletes billing from [year]. Default is current year-2. Set dryrun to do a test run. Set cleanup to remove invalid entries.
1358
END
1359
    }
1360
    return "Status=Error Not allowed\n" unless ($isadmin);
1361

    
1362
    my $y = $params{'year'} || ($year-2);
1363
    my $dryrun = $params{'dryrun'};
1364
    my $cleanup = $params{'cleanup'};
1365
    my $pattern = qq|like '%-$y-__'|;
1366
    if ($cleanup) {
1367
        $pattern = qq|not like '%-____-__'|;
1368
        $y = '';
1369
    }
1370

    
1371
    unless ( tie(%bnetworksreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_networks', key=>'useridtime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1372
    my @bkeys = (tied %bnetworksreg)->select_where("useridtime $pattern");
1373
    $postreply .= "Status=OK -- this is only a test run ---\n" if ($dryrun);
1374
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y network rows\n";
1375
    foreach my $bkey (@bkeys) {
1376
        $postreply .= "Status=OK removing $bnetworksreg{$bkey}->{useridtime}\n";
1377
        delete($bnetworksreg{$bkey}) unless ($dryrun);
1378
    }
1379
    untie(%bnetworksreg);
1380

    
1381
    unless ( tie(%bimagesreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_images', key=>'userstoragepooltime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1382
    my @bkeys = (tied %bimagesreg)->select_where("userstoragepooltime $pattern");
1383
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y image rows\n";
1384
    foreach my $bkey (@bkeys) {
1385
        $postreply .= "Status=OK removing $bimagesreg{$bkey}->{userstoragepooltime}\n";
1386
        delete($bimagesreg{$bkey}) unless ($dryrun);
1387
    }
1388
    untie(%bimagesreg);
1389

    
1390
    unless ( tie(%bserversreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_domains', key=>'usernodetime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1391
    my @bkeys = (tied %bserversreg)->select_where("usernodetime $pattern");
1392
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y server rows\n";
1393
    foreach my $bkey (@bkeys) {
1394
        $postreply .= "Status=OK removing $bserversreg{$bkey}->{usernodetime}\n";
1395
        delete($bserversreg{$bkey}) unless ($dryrun);
1396
    }
1397
    untie(%bserversreg);
1398

    
1399
    return $postreply;
1400

    
1401
}
1402

    
1403
sub collectBillingData {
1404
    my ( $curuuid, $buser, $bmonth, $byear, $showcost ) = @_;
1405

    
1406
    my $rx = 0;
1407
    my $tx = 0;
1408

    
1409
    my $vcpu=0;
1410
    my $vcpuavg = 0;
1411
    my $memory = 0;
1412
    my $memoryavg = 0;
1413

    
1414
    my $vgpu=0;
1415
    my $vgpuavg = 0;
1416
    my $vmemory = 0;
1417
    my $vmemoryavg = 0;
1418

    
1419
    my $backupsize = 0;
1420
    my $backupsizeavg = 0;
1421
    my $nodevirtualsize = 0;
1422
    my $nodevirtualsizeavg = 0;
1423
    my $virtualsize = 0;
1424
    my $virtualsizeavg = 0;
1425
    my $externalip = 0;
1426
    my $externalipavg = 0;
1427

    
1428
    my $prevmonth = $bmonth-1;
1429
    my $prevyear = $byear;
1430
    if ($prevmonth == 0) {$prevmonth=12; $prevyear--;};
1431
    $prevmonth = substr("0" . $prevmonth, -2);
1432
    my $prev_rx = 0;
1433
    my $prev_tx = 0;
1434
    # List pricing for a single system/server
1435
    if ($curuuid) {
1436
        unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domains register"};
1437
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images',key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1438
        unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
1439

    
1440
        my @domains;
1441
        my $isserver = 1 if ($domreg{$curuuid});
1442
        if ($isserver) {
1443
            @domains = $domreg{$curuuid};
1444
        } else {
1445
            @domains = values %domreg;
1446
        }
1447
        foreach my $valref (@domains) {
1448
            if ($valref->{'system'} eq $curuuid || $isserver) {
1449
                $memory += $valref->{'memory'};
1450
                $vcpu += $valref->{'vcpu'};
1451
                $vmemory += $valref->{'vmemory'};
1452
                $vgpu += $valref->{'vgpu'};
1453
                my $image = $valref->{'image'};
1454
                my $storagepool;
1455
                if ($imagereg{$image}) {
1456
                    $storagepool = $imagereg{$image}->{'storagepool'};
1457
                    if ($storagepool == -1) {
1458
                        $nodevirtualsize += $imagereg{$image}->{'virtualsize'};
1459
                    } else {
1460
                        $virtualsize += $imagereg{$image}->{'virtualsize'};
1461
                    }
1462
                    $backupsize += $imagereg{$image}->{'backupsize'};
1463
                }
1464
                $image = $valref->{'image2'};
1465
                if ($imagereg{$image}) {
1466
                    $storagepool = $imagereg{$image}->{'storagepool'};
1467
                    if ($storagepool == -1) {
1468
                        $nodevirtualsize += $imagereg{$image}->{'virtualsize'};
1469
                    } else {
1470
                        $virtualsize += $imagereg{$image}->{'virtualsize'};
1471
                    }
1472
                    $backupsize += $imagereg{$image}->{'backupsize'};
1473
                }
1474
                my $networkuuid = $valref->{'networkuuid1'};
1475
                my $networktype = $networkreg{$networkuuid}->{'type'};
1476
                $externalip++ if ($networktype eq 'externalip'|| $networktype eq 'ipmapping');
1477
                $networkuuid = $valref->{'networkuuid2'};
1478
                if ($networkreg{$networkuuid}) {
1479
                    $networktype = $networkreg{$networkuuid}->{'type'};
1480
                    $externalip++ if ($networktype eq 'externalip'|| $networktype eq 'ipmapping');
1481
                }
1482
            }
1483
        }
1484
        untie %domreg;
1485
        untie %imagereg;
1486
        untie %networkreg;
1487

    
1488
    # List pricing for all servers
1489
    } else {
1490
        # Network billing
1491
        unless ( tie(%bnetworksreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_networks', key=>'useridtime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
1492
        unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
1493

    
1494
        # Build list of the user's network id's
1495
        my %usernetworks;
1496
        my @nkeys = (tied %networkreg)->select_where("user = '$buser'");
1497
        foreach $network (@nkeys) {
1498
            my $id = $networkreg{$network}->{'id'};
1499
            $usernetworks{$id} = $id unless ($usernetworks{$id} || $id==0 || $id==1);
1500
        }
1501
        untie %networkreg;
1502

    
1503
        foreach $id (keys %usernetworks) {
1504
            my $networkobj = $bnetworksreg{"$buser-$id-$byear-$bmonth"};
1505
            my $prevnetworkobj = $bnetworksreg{"$buser-$id-$prevyear-$prevmonth"};
1506
            $externalip += $networkobj->{'externalip'};
1507
            $externalipavg += $networkobj->{'externalipavg'};
1508
            $rx += $networkobj->{'rx'};
1509
            $tx += $networkobj->{'tx'};
1510
            $prev_rx += $prevnetworkobj->{'rx'};
1511
            $prev_tx += $prevnetworkobj->{'tx'};
1512
        }
1513
        untie %bnetworksreg;
1514

    
1515
    # Image billing
1516

    
1517
        unless ( tie(%bimagesreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_images', key=>'userstoragepooltime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
1518

    
1519
        # Build list of the users storage pools
1520
        my $storagepools = $Stabile::config->get('STORAGE_POOLS_DEFAULTS') || "0";
1521
        my $upools = $register{$buser}->{'storagepools'}; # Prioritized list of users storage pools as numbers, e.g. "0,2,1"
1522
        $storagepools = $upools if ($upools && $upools ne '--');
1523
        my @spl = split(/,\s*/, $storagepools);
1524
        my $bimageobj = $bimagesreg{"$buser--1-$byear-$bmonth"};
1525
        $backupsize = $bimageobj->{'backupsize'}+0;
1526
        $nodevirtualsize = $bimageobj->{'virtualsize'}+0;
1527
        $backupsizeavg = $bimageobj->{'backupsizeavg'}+0;
1528
        $nodevirtualsizeavg = $bimageobj->{'virtualsizeavg'}+0;
1529
        foreach $pool (@spl) {
1530
            $bimageobj = $bimagesreg{"$buser-$pool-$byear-$bmonth"};
1531
            $virtualsize += $bimageobj->{'virtualsize'};
1532
            $backupsize += $bimageobj->{'backupsize'};
1533
            $virtualsizeavg += $bimageobj->{'virtualsizeavg'};
1534
            $backupsizeavg += $bimageobj->{'backupsizeavg'};
1535
        }
1536
        untie %bimagesreg;
1537

    
1538
    # Server billing
1539

    
1540
        unless ( tie(%bserversreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_domains', key=>'usernodetime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
1541
        unless ( tie(%nodereg,'Tie::DBI', Hash::Merge::merge({table=>'nodes', key=>'mac'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
1542

    
1543
        my @usernodes = keys %nodereg;
1544
        untie %nodereg;
1545

    
1546
        my @nodebills;
1547
        foreach $mac (@usernodes) {
1548
            my $bserverobj = $bserversreg{"$buser-$mac-$byear-$bmonth"};
1549
            $vcpu += $bserverobj->{'vcpu'};
1550
            $memory += $bserverobj->{'memory'};
1551
            $vcpuavg += $bserverobj->{'vcpuavg'};
1552
            $memoryavg += $bserverobj->{'memoryavg'};
1553

    
1554
            $vgpu += $bserverobj->{'vgpu'};
1555
            $vmemory += $bserverobj->{'vmemory'};
1556
            $vgpuavg += $bserverobj->{'vgpuavg'};
1557
            $vmemoryavg += $bserverobj->{'vmemoryavg'};
1558
        }
1559
        untie %bserversreg;
1560
    }
1561

    
1562
    my $uservcpuprice = 0+ $register{$user}->{'vcpuprice'};
1563
    my $usermemoryprice = 0+ $register{$user}->{'memoryprice'};
1564
    my $uservgpuprice = 0+ $register{$user}->{'vgpuprice'};
1565
    my $uservmemoryprice = 0+ $register{$user}->{'vmemoryprice'};
1566
    my $userstorageprice = 0+ $register{$user}->{'storageprice'};
1567
    my $usernodestorageprice = 0+ $register{$user}->{'nodestorageprice'};
1568
    my $userexternalipprice = 0+ $register{$user}->{'externalipprice'};
1569

    
1570
    $vcpuprice = $uservcpuprice || $Stabile::config->get('VCPU_PRICE') + 0;
1571
    $memoryprice = $usermemoryprice || $Stabile::config->get('MEMORY_PRICE') + 0;
1572
    $vgpuprice = $uservgpuprice || $Stabile::config->get('VGPU_PRICE') + 0;
1573
    $vmemoryprice = $uservmemoryprice || $Stabile::config->get('VMEMORY_PRICE') + 0;
1574
    $storageprice = $userstorageprice || $Stabile::config->get('STORAGE_PRICE') + 0;
1575
    $nodestorageprice = $usernodestorageprice || $Stabile::config->get('NODESTORAGE_PRICE') + 0;
1576
    $externalipprice = $userexternalipprice || $Stabile::config->get('EXTERNALIP_PRICE') + 0;
1577

    
1578
    my $memorygb = int(0.5 + 100*$memory/1024)/100;
1579
    my $vmemorygb = int(0.5 + 100*$vmemory/1024)/100;
1580
    my $virtualsizegb = int(0.5 + 100*$virtualsize/1024/1024/1024)/100;
1581
    my $nodevirtualsizegb = int(0.5 + 100*$nodevirtualsize/1024/1024/1024)/100;
1582
    my $backupsizegb = int(0.5 + 100*$backupsize/1024/1024/1024)/100;
1583

    
1584
    my $totalprice = int(0.5 + 100*(
1585
        $vcpu*$vcpuprice + $memorygb*$memoryprice
1586
        + $vgpu*$vgpuprice + $vmemorygb*$vemoryprice
1587
        + $virtualsizegb*$storageprice
1588
        + $nodevirtualsizegb*$nodestorageprice + $backupsizegb*$storageprice + $externalip*$externalipprice)
1589
    ) /100;
1590

    
1591
    my $memoryavggb = int(0.5 + 100*$memoryavg/1024)/100;
1592
    my $vmemoryavggb = int(0.5 + 100*$vmemoryavg/1024)/100;
1593
    my $virtualsizeavggb = int(0.5 + 100*$virtualsizeavg/1024/1024/1024)/100;
1594
    my $nodevirtualsizeavggb = int(0.5 + 100*$nodevirtualsizeavg/1024/1024/1024)/100;
1595
    my $backupsizeavggb = int(0.5 + 100*$backupsizeavg/1024/1024/1024)/100;
1596

    
1597
    my $monfac = 1;
1598
    if ($bmonth == $month) {
1599
        # Find 00:00 of first day of month - http://www.perlmonks.org/?node_id=97120
1600
        my $fstamp = POSIX::mktime(0,0,0,1,$mon,$year-1900,0,0,-1);
1601
        my $lstamp = POSIX::mktime(0,0,0,1,$mon+1,$year-1900,0,0,-1);
1602
        $monfac = ($current_time-$fstamp)/($lstamp-$fstamp);
1603
    }
1604

    
1605
    my $totalpriceavg = int(0.5 + 100*$monfac * (
1606
        $vcpuavg*$vcpuprice + $memoryavggb*$memoryprice
1607
        + $vgpuavg*$vcpuprice + $vmemoryavggb*$memoryprice
1608
        + $virtualsizeavggb*$storageprice
1609
        + $nodevirtualsizeavggb*$nodestorageprice + $backupsizeavggb*$storageprice + $externalipavg*$externalipprice)
1610
    ) /100;
1611

    
1612
    $prev_rx = 0 if ($prev_rx>$rx); # Something is fishy
1613
    $prev_tx = 0 if ($prev_tx>$tx);
1614
    my $rxgb = int(0.5 + 100*($rx-$prev_rx)/1024**3)/100;
1615
    my $txgb = int(0.5 + 100*($tx-$prev_tx)/1024**3)/100;
1616

    
1617
    my %stats;
1618
    $stats{'virtualsize'} = $virtualsizegb;
1619
    $stats{'backupsize'} = $backupsizegb;
1620
    $stats{'externalip'} = $externalip;
1621
    $stats{'memory'} = $memorygb;
1622
    $stats{'vcpu'} = $vcpu;
1623
    $stats{'vmemory'} = $vmemorygb;
1624
    $stats{'vgpu'} = $vgpu;
1625
    $stats{'month'} = $bmonth;
1626
    $stats{'nodevirtualsize'} = $nodevirtualsizegb;
1627
    $stats{'rx'} = $rxgb;
1628
    $stats{'tx'} = $txgb;
1629
    $stats{'username'} = $buser;
1630
    $stats{'year'} = $byear;
1631
    $stats{'totalcost'} = "$cur $totalprice" if ($showcost);
1632
    $stats{'curtotal'} = $totalprice if ($showcost);
1633

    
1634
    if (!$curuuid) {
1635
        $stats{'virtualsizeavg'} = $virtualsizeavggb;
1636
        $stats{'backupsizeavg'} = $backupsizeavggb;
1637
        $stats{'memoryavg'} = $memoryavggb;
1638
        $stats{'vcpuavg'} = int(0.5 + 100*$vcpuavg)/100;
1639
        $stats{'vmemoryavg'} = $vmemoryavggb;
1640
        $stats{'vgpuavg'} = int(0.5 + 100*$vgpuavg)/100;
1641
        $stats{'nodevirtualsizeavg'} = $nodevirtualsizeavggb;
1642
        $stats{'externalipavg'} = int(0.5 + 100*$externalipavg)/100;
1643
        $stats{'totalcostavg'} = "$cur $totalpriceavg" if ($showcost);
1644
    }
1645
    return %stats;
1646
}
1647

    
1648
sub do_resetpassword {
1649
    my ($uuid, $action, $obj) = @_;
1650
    if ($help) {
1651
        return <<END
1652
GET:username:
1653
Sends an email to a user with a link to reset his password. The user must have a valid email address.
1654
END
1655
    }
1656
    my $username = $obj->{'username'} || $user;
1657
    if ($register{$username} && ($username eq $user || $isadmin)) {
1658
        my $mailaddrs = $register{$username}->{'email'};
1659
        $mailaddrs = $username if (!$mailaddrs && $username =~ /\@/);
1660
        if ($mailaddrs) {
1661
            require (dirname(__FILE__)) . "/../auth/Apache/AuthTkt.pm";
1662
            my $tktname = 'auth_' . substr($engineid, 0, 8);
1663
            my $at = Apache::AuthTkt->new(conf => $ENV{MOD_AUTH_TKT_CONF});
1664
            my $tkt = $at->ticket(uid => $username, digest_type => 'SHA512', tokens => '', debug => 0);
1665
#            my $valid = $at->valid_ticket($tkt);
1666

    
1667
            my $mailhtml = <<END;
1668
<!DOCTYPE html
1669
	PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
1670
	 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1671
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
1672
	<head>
1673
		<title>Password reset</title>
1674
		<meta http-equiv="Pragma" content="no-cache" />
1675
		<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" />
1676
		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
1677
	</head>
1678
	<body class="tundra">
1679
		<div>
1680
			<div class="well" style="margin:20px;">
1681
				<h3 style="color: #e74c3c!important; margin-bottom:30px;">You requested a password reset at $enginename</h3>
1682
					To log in and set a new password, please click <a href="$baseurl/auth/autologin?$tktname=$tkt\&back=#chpwd">here</a>.<br>
1683
    				<div>Thanks,<br>your friendly infrastructure services</div>
1684
				</div>
1685
			</div>
1686
		</div>
1687
	</body>
1688
</html>
1689
END
1690
            ;
1691
            my $msg = MIME::Lite->new(
1692
                From     => "$enginename",
1693
                To       => $mailaddrs,
1694
                Type     => 'multipart/alternative',
1695
                Subject  => "Password reset on $enginename",
1696
            );
1697
            # my $att_text = MIME::Lite->new(
1698
            #     Type     => 'text',
1699
            #     Data     => $mailtext,
1700
            #     Encoding => 'quoted-printable',
1701
            # );
1702
            # $att_text->attr('content-type' => 'text/plain; charset=UTF-8');
1703
            # $msg->attach($att_text);
1704
            my $att_html = MIME::Lite->new(
1705
                Type     => 'text',
1706
                Data     => $mailhtml,
1707
                Encoding => 'quoted-printable',
1708
            );
1709
            $att_html->attr('content-type' => 'text/html; charset=UTF-8');
1710
            $msg->attach($att_html);
1711
            my $res = $msg->send;
1712
            $postreply = "Status=OK Password reset email sent to $mailaddrs\n";
1713
        } else {
1714
            $postreply = "Status=Error user does not have a registered email address\n";
1715
        }
1716
    } else {
1717
        $postreply = "Status=Error invalid data submitted\n";
1718
    }
1719
    return $postreply;
1720
}
1721

    
1722
sub do_changepassword {
1723
    my ($uuid, $action, $obj) = @_;
1724
    if ($help) {
1725
        return <<END
1726
GET:username,password:
1727
Changes the password for a user.
1728
END
1729
    }
1730
    my $username = $obj->{'username'} || $user;
1731
    my $password = $obj->{'password'};
1732
    if ($password && $register{$username} && ($username eq $user || $isadmin)) {
1733
        $MAXLEN = 20;
1734
        var $msg = IsBadPassword($password);
1735
        if ($msg) {
1736
            $postreply = "Status=Error $msg - please choose a stronger password\n";
1737
        } else {
1738
            $password = Digest::SHA::sha512_base64($password);
1739
            $register{$username}->{'password'} = $password;
1740
            $postreply = "Status=OK Password changed for $username\n";
1741
        }
1742
    } else {
1743
        $postreply = "Status=Error invalid data submitted\n";
1744
    }
1745
    return $postreply;
1746
}
1747

    
1748
sub do_remove {
1749
    my ($uuid, $action, $obj) = @_;
1750
    if ($help) {
1751
        return <<END
1752
GET:username:
1753
Removes a user.
1754
END
1755
    }
1756
    my $username = $obj->{'username'};
1757
    $postreply = remove($username);
1758
    return $postreply;
1759
}
1760

    
1761
sub remove {
1762
    my $username = shift;
1763
    if (!$isadmin && ($user ne $engineuser)) {
1764
        $postreply .= "Status=ERROR You are not allowed to remove user $username\n";
1765
    } elsif ($register{$username}) {
1766
        delete $register{$username};
1767
        tied(%register)->commit;
1768
        `/bin/rm /tmp/$username~*.tasks`;
1769
        unlink "../cgi/ui_update/$username~ui_update.cgi" if (-e "../cgi/ui_update/$username~ui_update.cgi");
1770
        $main::syslogit->($user, "info", "Deleted user $username from db");
1771
        if ($console) {
1772
            $postreply .= "Status=OK Deleted user $username\n";
1773
        } else {
1774
#            $main::updateUI->({ tab => 'users', type=>'update', user=>$user});
1775
            return "{}";
1776
        }
1777
        return $postreply;
1778
    } else {
1779
        $postreply .= "Status=ERROR No such user: $username\n";
1780
    }
1781
}
1782

    
1783
# Update engine users with users received from the registry
1784
sub updateEngineUsers {
1785
    my ($json_text) = @_;
1786
    return unless ($isadmin || ($user eq $engineuser));
1787
    my $res;
1788
    my $json = JSON->new;
1789
    $json->utf8([1]);
1790
    my $json_obj = $json->decode($json_text);
1791
    my @ulist = @$json_obj;
1792
    my @efields = qw(password
1793
    	address city company country email fullname phone
1794
        state zip alertemail opemail opfullname opphone billto
1795
        memoryquota storagequota vcpuquota externalipquota rxquota txquota nodestoragequota
1796
        accounts accountsprivileges privileges modified dnsdomains appstoreurl totpsecret
1797
    );
1798
    my $ures;
1799
    my $ucount = 0;
1800
    foreach my $u (@ulist) {
1801
        my $username = $u->{'username'};
1802
        if (!$register{$username} && $u->{'password'}) {
1803
            $register{$username} = {
1804
                username => $username,
1805
                password => $u->{'password'},
1806
                allowinternalapi => 1
1807
            };
1808
            $ures .= " *";
1809
        }
1810
        next unless ($register{$username});
1811
        next if ($register{$username}->{'modified'} && $register{$username}->{'modified'} > $u->{'modified'});
1812
        foreach my $efield (@efields) {
1813
            if ($efield eq 'privileges') {
1814
                $u->{$efield} =~ tr/adnrpu//cd; # filter out non-valid privileges
1815
            }
1816
            if (defined $u->{$efield}) {
1817
                $u->{$efield} += 0 if ($efield =~ /(quota|price)$/);
1818
                $register{$username}->{$efield} = $u->{$efield};
1819
            }
1820
            delete $u->{$efield} if (defined $u->{$efield} && $u->{$efield} eq '' && $efield ne 'password')
1821
        }
1822
        $ures .= "$username ($u->{'fullname'}), ";
1823
        $ucount++;
1824
        my $uid = `id -u irigo-$username`; chomp $uid;
1825
        if (!$uid) { # Check user has system account for disk quotas
1826
            $main::syslogit->($user, "info", "Adding system user $username");
1827
            `/usr/sbin/useradd -m "irigo-$username"`;
1828
            `echo "[User]\nSystemAccount=true" > /var/lib/AccountsService/users/irigo-$username`; # Don't show in login screen
1829
        }
1830

    
1831
    }
1832
    $ures = substr($res, 0, -2) . "\n";
1833
    $res .= "Status=OK Received $ucount updates on " .(scalar(@ulist)). " registry users\n";
1834
    return $res;
1835
}
1836

    
1837
sub sendEngineUser {
1838
    my ($username) = @_;
1839
    if ($enginelinked) {
1840
    # Send engine user to the registry
1841
        require LWP::Simple;
1842
        my $browser = LWP::UserAgent->new;
1843
        $browser->agent('stabile/1.0b');
1844
        $browser->protocols_allowed( [ 'http','https'] );
1845

    
1846
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
1847
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
1848
        my $tkthash = Digest::SHA::sha512_hex($tktkey);
1849
        my $json = '[' . JSON::to_json(\%{$register{$username}}) . ']';
1850
        $json =~ s/null/""/g;
1851
#        $json = uri_escape_utf8($json);
1852
        $json = URI::Escape::uri_escape($json);
1853
        my $posturl = "https://www.stabile.io/irigo/engine.cgi?action=update";
1854
        my $postreq = ();
1855
        $postreq->{'POSTDATA'} = $json;
1856
        $postreq->{'engineid'} = $engineid;
1857
        $postreq->{'enginetkthash'} = $tkthash;
1858

    
1859
#        my $req = HTTP::Request->new(POST => $posturl);
1860
#        $req->content_type("application/json; charset='utf8'");
1861
#        $req->content($postreq);
1862

    
1863
        $content = $browser->post($posturl, $postreq)->content();
1864
#        $content = $browser->post($posturl, 'Content-type' => 'text/plain;charset=utf-8', Content => $postreq)->content();
1865
#        $content = $browser->request($req)->content();
1866
        my $fullname = $register{$username}->{'fullname'};
1867
        $fullname = Encode::decode('utf8', $fullname);
1868
        return "Updated $fullname in registry\n";
1869
    }
1870
}
(9-9/9)