Project

General

Profile

Download (76.7 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 lib dirname (__FILE__);
20
use Stabile;
21

    
22
$engineid = $Stabile::config->get('ENGINEID') || "";
23
$enginename = $Stabile::config->get('ENGINENAME') || "";
24
#$enginelinked = $Stabile::config->get('ENGINE_LINKED') || "";
25
$showcost = $Stabile::config->get('SHOW_COST') || "";
26
$cur = $Stabile::config->get('CURRENCY') || "USD";
27
$engineuser = $Stabile::config->get('ENGINEUSER') || "";
28
$externaliprangestart = $Stabile::config->get('EXTERNAL_IP_RANGE_START') || "";
29
$externaliprangeend = $Stabile::config->get('EXTERNAL_IP_RANGE_END') || "";
30
$proxyiprangestart = $Stabile::config->get('PROXY_IP_RANGE_START') || "";
31
$proxyiprangeend = $Stabile::config->get('PROXY_IP_RANGE_END') || "";
32
$proxygw = $Stabile::config->get('PROXY_GW') || "";
33

    
34
$uiuuid;
35
$uistatus;
36
$help = 0; # If this is set, functions output help
37

    
38
#our %options=();
39
# -a action -h help -u uuid -m match pattern -f full list, i.e. all users
40
# -v verbose, include HTTP headers -s impersonate subaccount -t target [uuid or image]
41
# -g args to gearman task
42
#Getopt::Std::getopts("a:hfu:g:m:vs:t:", \%options);
43

    
44
try {
45
    Init(); # Perform various initalization tasks
46
    process() if ($package);
47

    
48
} catch Error with {
49
    my $ex = shift;
50
    print header('text/html', '500 Internal Server Error') unless ($console);
51
    if ($ex->{-text}) {
52
        print "Got error: ", $ex->{-text}, " on line ", $ex->{-line}, "\n";
53
    } else {
54
        print "Status=ERROR\n";
55
    }
56
} finally {
57
};
58

    
59
1;
60

    
61
sub getObj {
62
    my %h = %{@_[0]};
63
    $console = 1 if $h{"console"};
64
    $api = 1 if $h{"api"};
65
    my $username = $h{"username"} || $h{"uuid"};
66
    my $obj;
67
    $action = $action || $h{'action'};
68
    if ($action=~ /engine$|updateclientui$|updateui$/) {
69
        $obj = \%h;
70
        $obj->{pwd} = $obj->{password} if ($obj->{password});
71
    } else {
72
        $obj = $register{$username};
73
        my %hobj = %{$register{$username}};
74
        $obj = \%hobj; # We do this to get around a weird problem with freeze...
75
        my @props = qw ( restorefile engineid enginename engineurl username user password pwd fullname email
76
            opemail alertemail phone opphone opfullname allowfrom allowinternalapi privileges accounts accountsprivileges
77
            storagepools memoryquota storagequota nodestoragequota vcpuquota externalipquota rxquota txquota billto dnsdomains appstoreurl totpsecret );
78
        foreach my $prop (@props) {
79
            if (defined $h{$prop}) {
80
                $obj->{$prop} = $h{$prop};
81
            }
82
        }
83
    }
84
    return $obj;
85
}
86

    
87
sub Init {
88
    # Tie database tables to hashes
89
    unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username'}, $Stabile::dbopts)) ) {return "Unable to access users register"};
90

    
91
    # simplify globals initialized in Stabile.pm
92
    $tktuser = $tktuser || $Stabile::tktuser;
93
    $user = $user || $Stabile::user;
94

    
95
    $fullname = $register{$user}->{'fullname'};
96
    $email = $register{$user}->{'email'};
97
    $opemail = $register{$user}->{'opemail'};
98
    $alertemail = $register{$user}->{'alertemail'};
99
    $phone = $register{$user}->{'phone'};
100
    $opphone = $register{$user}->{'opphone'};
101
    $opfullname = $register{$user}->{'opfullname'};
102
    $allowfrom = $register{$user}->{'allowfrom'};
103
    $allowinternalapi = $register{$user}->{'allowinternalapi'};
104
    $lastlogin = $register{$user}->{'lastlogin'};
105
    $lastloginfrom = $register{$user}->{'lastloginfrom'};
106

    
107
#    if ($register{$user}->{'lastlogin'} ne $tkt) {
108
#        $register{$user}->{'lastlogin'} = time;
109
#        $register{$user}->{'lastloginfrom'} = $ENV{'REMOTE_ADDR'};
110
#        $register{$user}->{'lasttkt'} = $tkt;
111
#    }
112

    
113
    $Stabile::userstoragequota = 0+ $register{$user}->{'storagequota'};
114
    $Stabile::usernodestoragequota = 0+ $register{$user}->{'nodestoragequota'};
115
    $usermemoryquota = 0+ $register{$user}->{'memoryquota'};
116
    $uservcpuquota = 0+ $register{$user}->{'vcpuquota'};
117
    $userexternalipquota = 0+ $register{$user}->{'externalipquota'};
118
    $userrxquota = 0+ $register{$user}->{'rxquota'};
119
    $usertxquota = 0+ $register{$user}->{'txquota'};
120

    
121
    $storagequota = $Stabile::userstoragequota || $defaultstoragequota;
122
    $nodestoragequota = $Stabile::usernodestoragequota || $defaultnodestoragequota;
123
    $memoryquota = $usermemoryquota || $defaultmemoryquota;
124
    $vcpuquota = $uservcpuquota || $defaultvcpuquota;
125
    $externalipquota = $userexternalipquota || $defaultexternalipquota;
126
    $rxquota = $userrxquota || $defaultrxquota;
127
    $txquota = $usertxquota || $defaulttxquota;
128

    
129
    # Create aliases of functions
130
    *header = \&CGI::header;
131

    
132
    *Unlinkengine = \&Linkengine;
133
    *Updateengine = \&Linkengine;
134
    *Saveengine = \&Linkengine;
135
    *Syncusers = \&Linkengine;
136

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

    
155
    *do_linkengine = \&privileged_action;
156
    *do_gear_linkengine = \&do_gear_action;
157
    *do_saveengine = \&privileged_action_async;
158
    *do_gear_saveengine = \&do_gear_action;
159
    *do_unlinkengine = \&privileged_action;
160
    *do_gear_unlinkengine = \&do_gear_action;
161
    *do_updateengine = \&privileged_action;
162
    *do_syncusers = \&privileged_action;
163
    *do_gear_updateengine = \&do_gear_action;
164
    *do_gear_syncusers = \&do_gear_action;
165
    *do_deleteentirely = \&privileged_action;
166
    *do_gear_deleteentirely = \&do_gear_action;
167
    *do_vent = \&privileged_action;
168
    *do_gear_vent = \&do_gear_action;
169
    *do_updateui = \&privileged_action;
170
    *do_gear_updateui = \&do_gear_action;
171
}
172

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

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

    
225
    if ($isadmin && $engineid) {
226
        $engine_h{"engineid"} = $engineid;
227
        $engine_h{"engineuser"} = $engineuser;
228
        $engine_h{"externaliprangestart"} = $externaliprangestart;
229
        $engine_h{"externaliprangeend"} = $externaliprangeend;
230
        $engine_h{"proxyiprangestart"} = $proxyiprangestart;
231
        $engine_h{"proxyiprangeend"} = $proxyiprangeend;
232
        $engine_h{"proxygw"} = $proxygw;
233

    
234
        $engine_h{"disablesnat"} = $disablesnat;
235
        $engine_h{"imagesdevice"} = $imagesdevice;
236
        $engine_h{"backupdevice"} = $backupdevice;
237

    
238
        my $nodecfg = new Config::Simple("/etc/stabile/nodeconfig.cfg");
239
        my $readlimit = $nodecfg->param('VM_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
240
        my $writelimit = $nodecfg->param('VM_WRITE_LIMIT');
241
        my $iopsreadlimit = $nodecfg->param('VM_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
242
        my $iopswritelimit = $nodecfg->param('VM_IOPS_WRITE_LIMIT');
243
        $engine_h{"vmreadlimit"} = $readlimit;
244
        $engine_h{"vmwritelimit"} = $writelimit;
245
        $engine_h{"vmiopsreadlimit"} = $iopsreadlimit;
246
        $engine_h{"vmiopswritelimit"} = $iopswritelimit;
247

    
248
        $engine_h{"zfsavailable"} = $zbackupavailable;
249
        $engine_h{"downloadmasters"} = $downloadmasters;
250
    }
251
    if (-e "/var/www/stabile/static/img/logo-icon-" . $ENV{HTTP_HOST} . ".png") {
252
        $jsontext .= qq|"favicon": "/stabile/static/img/logo-icon-$ENV{HTTP_HOST}.png", |;
253
    }
254
    $engine_h{"enginename"} = $enginename;
255
    $engine_h{"enginelinked"} = $enginelinked;
256
    $jsontext .= "\"showcost\": \"$showcost\", ";
257
    $jsontext .= "\"externalipquota\": $externalipquota, \"rxquota\": $rxquota, \"txquota\": $txquota, ";
258
    $jsontext .= qq|"defaultstoragequota": $defaultstoragequota, "defaultnodestoragequota": $defaultnodestoragequota, "defaultmemoryquota": $defaultmemoryquota, "defaultvcpuquota": $defaultvcpuquota, |;
259
    $jsontext .= "\"defaultexternalipquota\": $defaultexternalipquota, \"defaultrxquota\": $defaultrxquota, \"defaulttxquota\": $defaulttxquota, ";
260
    $jsontext .= qq|"engine": | . to_json(\%engine_h);
261
    $jsontext .= "},  ";
262

    
263
    $jsontext .= "{\"id\": \"common\", \"privileges\": \"--\"," .
264
      "\"fullname\": \"--\", \"email\": \"--\"," .
265
      "\"storagequota\": 0, \"memoryquota\": 0, \"vcpuquota\": 0, \"externalipquota\": 0," .
266
      "\"rxquota\": 0, \"txquota\": 0}";
267

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

    
270
    foreach my $aid (keys %ahash) {
271
        my $privs = $ahash{$aid};
272
        $jsontext .= qq|, {"id": "$aid", "privileges": "$privs"}| unless ($aid eq $user || $aid eq $billto);
273
    }
274

    
275
    $jsontext .= "]}";
276
    # Create ui_update link in case we are logging in with a remotely generated ticket, i.e. not passing through login.cgi
277
    `/bin/ln -s ../ui_update.cgi ../cgi/ui_update/$user~ui_update.cgi` unless (-e "../cgi/ui_update/$user~ui_update.cgi");
278
    $postreply = to_json(from_json($jsontext), {pretty=>1});
279
    return $postreply;
280
}
281

    
282

    
283
sub do_listengines{
284
    my ($uuid, $action, $obj) = @_;
285
    if ($help) {
286
        return <<END
287
GET::
288
List other engines user has access to
289
END
290
    }
291
    if ($enginelinked) {
292
        require LWP::Simple;
293
        my $browser = LWP::UserAgent->new;
294
        $browser->agent('stabile/1.0b');
295
        $browser->protocols_allowed( [ 'http','https'] );
296

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

    
300
        $postreq->{'engineid'} = $engineid;
301
#        $postreq->{'user'} = $tktuser;
302
        $postreq->{'user'} = $user;
303
        $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
304

    
305
        my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=listengines", $postreq)->content();
306
        if ($content =~ /ERROR:(.+)"/) {
307
            $postreply = qq|{"identifier": "url", "label": "name", "items": [{"url": "# $1", "name": "$enginename"}]}|;
308
        } else {
309
            $postreply = qq|{"identifier": "url", "label": "name", "items": $content}|;
310
        }
311
    } else {
312
        $postreply = qq|{"identifier": "url", "label": "name", "items": [{"url": "#", "name": "$enginename"}]}|;
313
    }
314
    return $postreply;
315
}
316

    
317
sub do_billing {
318
    my ($uuid, $action, $obj) = @_;
319
    if ($help) {
320
        return <<END
321
GET:uuid,username,month,startmonth,endmonth,format:
322
List usage data, optionally for specific server/system [uuid] or user [username]. May be called as usage, usagestatus or usageavgstatus.
323
When called as "usage", format may be csv, in which case startmonth and endmonth may be specified.
324
END
325
    }
326
    my $buser = $params{'user'} || $params{'username'} || $user;
327
    my $bmonth = $params{'month'} || $month;
328
    $bmonth = substr("0$bmonth", -2);
329
    my $byear = $params{'year'} || $year;
330
    my $vcpu=0, $memory=0, $virtualsize=0, $nodevirtualsize=0, $backupsize=0, $externalip=0;
331
    my $rx = 0;
332
    my $tx = 0;
333
    my $vcpuavg = 0;
334
    my $externalipavg = 0;
335
    $uuid = '' if ($register{$uuid}); # check if $uuid was set to $user because no actual uuid passed
336

    
337
    if ($user eq $buser || index($privileges,"a")!=-1) {
338
         my %stats = collectBillingData( $uuid, $buser, $bmonth, $byear, $showcost );
339
         my $memoryquotagb = int(0.5 + 100*$memoryquota/1024)/100;
340
         my $storagequotagb = int(0.5 + 100*$storagequota/1024)/100;
341
         my $nodestoragequotagb = int(0.5 + 100*$nodestoragequota/1024)/100;
342
         my $irigo_cost = ($showcost?"showcost":"hidecost");
343

    
344
         if ($action eq "billing" || $action eq "usage") {
345
             if ($params{'format'} eq 'csv') {
346
                 $postreply = header("text/plain");
347
                 my $startmonth = $params{'startmonth'} || 1;
348
                 my $endmonth = $params{'endmonth'} || $bmonth;
349
                 my @vals;
350
                 for (my $i=$startmonth; $i<=$endmonth; $i++) {
351
                     my $m = substr("0$i", -2);
352
                     my %mstats = collectBillingData( $uuid, $buser, $m, $byear, $showcost );
353
                     push @vals, \%mstats;
354
                 }
355
                 csv(in => \@vals, out => \my $csvdata);
356
                 $postreply .= $csvdata;
357
             } else {
358
                 my $json_text = JSON::to_json(\%stats, {pretty => 1});
359
                 $postreply = "$json_text";
360
             }
361

    
362
         } elsif ($action eq "billingstatus" || $action eq "usagestatus") {
363
             my $virtualsizegb = $stats{'virtualsize'};
364
             my $backupsizegb = $stats{'backupsize'};
365
             my $externalip = $stats{'externalip'};
366
             my $memorygb = $stats{'memory'};
367
             my $nodevirtualsizegb = $stats{'nodevirtualsize'};
368
             $rx = $stats{'rx'};
369
             $tx = $stats{'tx'};
370
             $vcpu = $stats{'vcpu'};
371

    
372
             my $res;
373
             if ($params{'format'} eq 'html') {
374
                 $postreply .= header("text/html");
375
                 $res .= qq[<tr><th>Ressource</th><th>Quantity</th><th class="$irigo_cost">Cost/month</th><th>Quota</th></tr>];
376
                 $res .= qq[<tr><td>vCPU's:</td><td align="right">$vcpu</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$vcpu*$vcpuprice) . qq[</td><td align="right">$vcpuquota</td></tr>];
377
                 $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>];
378
                 $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>];
379
                 $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>];
380
                 $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>];
381
                 $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>];
382
                 if (!$uuid) {
383
                     $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>];
384
                     $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>];
385
                 }
386

    
387
                 $res =~ s/-1/&infin;/g;
388
                 $res =~ s/>0 .B<\/td><\/tr>/>&infin;<\/td><\/tr>/g;
389
                 $postreply .= qq[<table cellspacing="0" noframe="void" norules="rows" class="systemTables">$res</table>];
390
             } else {
391
                 my $bill = {
392
                     vcpus => {quantity => $vcpu, quota => $vcpuquota},
393
                     memory => {quantity => $memorygb, unit => 'GB', quota => $memoryquotagb},
394
                     shared_storage => {quantity => $virtualsizegb, unit => 'GB', quota => $storagequotagb},
395
                     node_storage => {quantity => $nodevirtualsizegb, unit => 'GB', quota => $nodestoragequotagb},
396
                     backup_storage => {quantity => $backupsizegb, unit => 'GB'},
397
                     external_ips => {quantity => $externalip, quota => $externalipquota},
398
                     network_traffic_out => {quantity => $rx, unit => 'GB', quota => int(0.5 + $rxquota/1024/1024)},
399
                     network_traffic_in => {quantity => $tx, unit => 'GB', quota => int(0.5 + $txquota/1024/1024)}
400
                 };
401
                 if ($showcost) {
402
                     $bill->{vcpus}->{cost} = int(0.5+$vcpu*$vcpuprice);
403
                     $bill->{memory}->{cost} = int(0.5+$memorygb*$memoryprice);
404
                     $bill->{shared_storage}->{cost} = int(0.5+$virtualsizegb*$storageprice);
405
                     $bill->{node_storage}->{cost} = int(0.5+$nodevirtualsizegb*$nodestorageprice);
406
                     $bill->{backup_storage}->{cost} = int(0.5+$backupsizegb*$storageprice);
407
                     $bill->{external_ips}->{cost} = int(0.5+$externalip*$externalipprice);
408
                     $bill->{currency} = $cur;
409
                     $bill->{username} = $buser;
410
                 }
411
                 $postreply .= to_json($bill, {pretty=>1});
412
             }
413
         } elsif ($action eq "billingavgstatus" || $action eq "usageavgstatus") {
414
             my $virtualsizeavggb = $stats{'virtualsizeavg'};
415
             my $backupsizeavggb = $stats{'backupsizeavg'};
416
             my $memoryavggb = $stats{'memoryavg'};
417
             my $nodevirtualsizeavggb = $stats{'nodevirtualsizeavg'};
418
             $vcpuavg = $stats{'vcpuavg'};
419
             $externalipavg = $stats{'externalipavg'};
420
             $rx = $stats{'rx'};
421
             $tx = $stats{'tx'};
422
             if ($params{'format'} eq 'html') {
423
                 $postreply .= header("text/html");
424
                 my $res;
425
                 $res .= qq[<tr><th>Ressource</th><th>Quantity</th><th class="$irigo_cost">Cost/month</th><th>Quota</th></tr>];
426
                 $res .= qq[<tr><td>vCPU's:</td><td align="right">] . int(0.5+100*$vcpuavg)/100 . qq[</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$vcpuavg*$vcpuprice) . qq[</td><td align="right">$vcpuquota</td></tr>];
427
                 $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>];
428
                 $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>];
429
                 $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>];
430
                 $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>];
431
                 $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>];
432
                 $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>];
433
                 $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>];
434

    
435
                 $res =~ s/-1/&infin;/g;
436
                 $res =~ s/>0 .B<\/td><\/tr>/>&infin;<\/td><\/tr>/g;
437
                 $postreply .= qq[<table cellspacing="0" noframe="void" norules="rows" class="systemTables">$res</table>];
438
             } else {
439
                 my $bill = {
440
                     vcpus => {quantity => $vcpuavg, quota => $vcpuquota},
441
                     memory => {quantity => $memoryavggb, unit => 'GB', quota => $memoryquotagb},
442
                     shared_storage => {quantity => $virtualsizeavggb, unit => 'GB', quota => $storagequotagb},
443
                     node_storage => {quantity => $nodevirtualsizeavggb, unit => 'GB', quota => $nodestoragequotagb},
444
                     backup_storage => {quantity => $backupsizeavggb, unit => 'GB'},
445
                     external_ips => {quantity => $externalipavg, quota => $externalipquota},
446
                     network_traffic_out => {quantity => int(0.5 + $rx), unit => 'GB', quota => int(0.5 + $rxquota/1024/1024)},
447
                     network_traffic_in => {quantity => int(0.5 + $tx), unit => 'GB', quota => int(0.5 + $txquota/1024/1024)}
448
                 };
449
                 if ($showcost) {
450
                     $bill->{vcpus}->{cost} = int(0.5+$vcpuavg*$vcpuprice);
451
                     $bill->{memory}->{cost} = int(0.5+$memoryavggb*$memoryprice);
452
                     $bill->{shared_storage}->{cost} = int(0.5+$virtualsizeavggb*$storageprice);
453
                     $bill->{node_storage}->{cost} = int(0.5+$nodevirtualsizeavggb*$nodestorageprice);
454
                     $bill->{backup_storage}->{cost} = int(0.5+$backupsizeavggb*$storageprice);
455
                     $bill->{external_ips}->{cost} = int(0.5+$externalipavg*$externalipprice);
456
                     $bill->{currency} = $cur;
457
                     $bill->{username} = $buser;
458
                 }
459
                 $postreply .= to_json($bill, {pretty=>1});
460
             }
461
        }
462
    } else {
463
        $postreply .= "Status=ERROR no privileges!!\n";
464
    }
465
    return $postreply;
466
}
467

    
468
sub do_listenginebackups {
469
    my ($uuid, $action, $obj) = @_;
470
    if ($help) {
471
        return <<END
472
GET::
473
List the backups of this engine's configuration in the registry.
474
END
475
    }
476
    if ($enginelinked) {
477
        require LWP::Simple;
478
        my $browser = LWP::UserAgent->new;
479
        $browser->agent('stabile/1.0b');
480
        $browser->protocols_allowed( [ 'http','https'] );
481

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

    
485
        $postreq->{'engineid'} = $engineid;
486
        $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
487

    
488
        my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=listbackups", $postreq)->content();
489
        if ($content =~ /\[\]/) {
490
            $postreply = qq|{"identifier": "path", "label": "name", "items": [{"path": "#", "name": "No backups"}]}|;
491
        } else {
492
            $postreply = qq|{"identifier": "path", "label": "name", "items": $content}|;
493
        }
494
    } else {
495
        $postreply = qq|{"identifier": "path", "label": "name", "items": [{"path": "#", "name": "Engine not linked"}]}|;
496
    }
497
    return $postreply;
498
}
499

    
500
sub Backupengine {
501
    my ($uuid, $action, $obj) = @_;
502
    if ($help) {
503
        return <<END
504
GET::
505
Backup this engine's configuration to the registry.
506
END
507
    }
508
    my $backupname = "$enginename.$engineid.$pretty_time";
509
    $backupname =~ tr/:/-/; # tar has a problem with colons in filenames
510
    if (-e "/tmp/$backupname.tgz") {
511
        $postreply .= "Status=ERROR Engine is already being backed up";
512
    } else {
513
        $res .= `mysqldump --ignore-table=steamregister.nodeidentities steamregister > /etc/stabile/steamregister.sql`;
514
        $res .= `cp /etc/apache2/conf-available/auth_tkt_cgi.conf /etc/stabile`;
515
        $res .= `cp /etc/apache2/ssl/*.crt /etc/stabile`;
516
        $res .= `cp /etc/apache2/ssl/*.pem /etc/stabile`;
517
        $res .= `cp /etc/apache2/ssl/*.key /etc/stabile`;
518
        $res .= `cp /etc/hosts.allow /etc/stabile`;
519
        $res .= `cp /etc/mon/mon.cf /etc/stabile`;
520

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

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

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

    
531
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
532
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
533
        my $enginetkthash = Digest::SHA::sha512_hex($tktkey);
534

    
535
        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`;
536
        if ($res =~ /OK: $backupname.tgz received/) {
537
            $postreply .= "Status=OK Engine configuration saved to the registry";
538
            $main::syslogit->($user, "info", "Engine configuration saved to the registry");
539
            unlink("/tmp/$backupname.tgz");
540
        } else {
541
            $postreply .= "Status=ERROR Problem backing configuration up to the registry\n$res\n";
542
        }
543
    }
544
    return $postreply;
545
}
546

    
547
sub Upgradeengine {
548
    my ($uuid, $action, $obj) = @_;
549
    if ($help) {
550
        return <<END
551
GET::
552
Try to upgrade this engine to latest release from the registry
553
END
554
    }
555
    $postreply = "Status=OK Requesting upgrade of Steamgine\n";
556
    `echo "UPGRADE=1" >> /etc/stabile/config.cfg` unless ( `grep ^UPGRADE=1 /etc/stabile/config.cfg`);
557
    `/usr/bin/pkill pressurecontrol`;
558
    #`service pressurecontrol stop`, "\n";
559
    #print `service pressurecontrol start`, "\n";
560
}
561

    
562
sub do_billengine {
563
    my ($uuid, $action, $obj) = @_;
564
    if ($help) {
565
        return <<END
566
GET::
567
Submit billing data for this engine to the registry.
568
END
569
    }
570
    require LWP::Simple;
571
    my $browser = LWP::UserAgent->new;
572
    $browser->agent('stabile/1.0b');
573
    $browser->protocols_allowed( [ 'http','https'] );
574

    
575
    my $bmonth = $params{'month'} || $month;
576
    $bmonth = substr("0$bmonth", -2);
577
    my $byear = $params{'year'} || $year;
578
    $showcost = 1;
579

    
580
    my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
581
    my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
582
    my $tkthash = Digest::SHA::sha512_hex($tktkey);
583

    
584
    my $postreq = ();
585
    my %bill;
586
    my @regvalues = values %register; # Sort by id
587
    foreach my $valref (@regvalues) {
588
        my $cuser = $valref->{'username'};
589
        my %stats = collectBillingData( '', $cuser, $bmonth, $byear, $showcost );
590
        $bill{"$cuser-$byear-$bmonth"} = \%stats;
591
    }
592
    $postreq->{'engineid'} = $engineid;
593
    $postreq->{'enginetkthash'} = $tkthash;
594
    $postreq->{'keywords'} = JSON::to_json(\%bill, {pretty=>1});
595
    my $url = "https://www.stabile.io/irigo/engine.cgi";
596
    $content = $browser->post($url, $postreq)->content();
597
    $postreply = "Status=OK Billed this engine ($engineid)\n";
598
    $postreply .= "$postreq->{'keywords'}\n$content";
599
    return $postreply;
600
}
601

    
602
sub Linkengine {
603
    my ($uuid, $action, $obj) = @_;
604
    if ($help) {
605
        return <<END
606
PUT:username,password,engineid,enginename,engineurl:
607
Links engine to the registry
608
END
609
    }
610
    return "Status=Error Not allowed\n" unless ($isadmin || ($user eq $engineuser));
611
    my $linkaction = 'update';
612
    $linkaction = 'link' if ($action eq 'linkengine');
613
    $linkaction = 'unlink' if ($action eq 'unlinkengine');
614
    $linkaction = 'update' if ($action eq 'updateengine');
615
    $linkaction = 'update' if ($action eq 'syncusers');
616

    
617
    require LWP::Simple;
618
    my $browser = LWP::UserAgent->new;
619
    $browser->agent('stabile/1.0b');
620
    $browser->protocols_allowed( [ 'http','https'] );
621

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

    
625
    my $postreq = ();
626
    $postreq->{'user'} = $user || $obj->{'username'};
627
    $postreq->{'engineid'} = $obj->{'engineid'} || $engineid;
628
    $postreq->{'pwd'} = $obj->{'pwd'} if ($obj->{'pwd'});
629
    $postreq->{'enginename'} = $obj->{'enginename'} if ($obj->{'enginename'});
630
    $postreq->{'engineurl'} = $obj->{'engineurl'} if ($obj->{'engineurl'});
631
    if ($tktkey) {
632
        if ($action eq 'linkengine') {
633
            $main::syslogit->($user, "info", "Linking engine with the registry");
634
            $postreq->{'enginetktkey'} = $tktkey;
635
        } else {
636
            $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
637
        }
638
    }
639
    if ($action eq "saveengine") { # Save request from the registry - don't post back
640
        # Pressurecontrol reads new configuration data from the registry, simply reload it
641
        my $pressureon = !(`systemctl is-active pressurecontrol` =~ /inactive/);
642
        $postreply = ($pressureon)? "Status=OK Engine updating...\n":"Status=OK Engine not updating because pressurecontrol not active\n";
643
        $postreply .= `systemctl restart pressurecontrol` if ($pressureon);
644
    } else {
645
        my $res;
646
        my $cfg = new Config::Simple("/etc/stabile/config.cfg");
647
        if ($action eq 'linkengine' || $action eq 'syncusers') {
648
            # Send engine users to the registry
649
            my @vals = values %register;
650
            my $json = JSON::to_json(\@vals);
651
            $json =~ s/null/""/g;
652
            $json = URI::Escape::uri_escape($json);
653
            $postreq->{'POSTDATA'} = $json;
654
        }
655
        if ($action eq 'linkengine' || $action eq 'updateengine') {
656
            # Update name in config file
657
            if ($postreq->{'enginename'} && $cfg->param("ENGINENAME") ne $postreq->{'enginename'}) {
658
                $cfg->param("ENGINENAME", $postreq->{'enginename'});
659
                $cfg->save();
660
            }
661
            # Send entire engine config file to the registry
662
            my %cfghash = $cfg->vars();
663
            foreach my $param (keys %cfghash) {
664
                $param =~ /default\.(.+)/; # Get rid of default. prefix
665
                if ($1) {
666
                    my $k = $1;
667
                    my @cvals = $cfg->param($param);
668
                    my $cval = join(", ", @cvals);
669
                    $postreq->{$k} = URI::Escape::uri_escape($cval);
670
                }
671
            }
672
            # Send entire engine piston config file to the registry
673
            my $nodeconfigfile = "/mnt/stabile/tftp/bionic/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
674
            if (-e $nodeconfigfile) {
675
                my $pistoncfg = new Config::Simple($nodeconfigfile);
676
                %cfghash = $pistoncfg->vars();
677
                foreach my $param (keys %cfghash) {
678
                    $param =~ /default\.(.+)/; # Get rid of default. prefix
679
                    if ($1) {
680
                        my $k = $1;
681
                        my @cvals = $pistoncfg->param($param);
682
                        my $cval = join(", ", @cvals);
683
                        $postreq->{$k} = URI::Escape::uri_escape($cval);
684
                    }
685
                }
686
            }
687
        }
688
        if ($linkaction eq 'link' || $enginelinked) {
689
            my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=$linkaction", $postreq)->content();
690
            if ($content =~ /(Engine linked|Engine not linked|Engine unlinked|Engine updated|Unknown engine|Invalid credentials .+\.)/i) {
691
                $res = "Status=OK $1";
692
                my $linked = 1;
693
                $linked = 0 unless ($content =~ /Engine linked/i || $content =~ /Engine updated/i);
694
                $cfg->param("ENGINE_LINKED", $linked);
695
                $cfg->save();
696
            } elsif ($action eq 'syncusers' || $action eq 'linkengine') { # If we send user list to the registry we get merged list back
697
                if ($content =~ /^\[/) { # Sanity check to see if we got json back
698
                    $res .= "Status=OK Engine linked\n" if ($action eq 'linkengine');
699
                    # Update engine users with users from the registry
700
                    $res .= updateEngineUsers($content);
701
                    $res .= "Status=OK Users synced with registry\n";
702
                    $main::updateUI->({ tab => 'users', type=>'update', user=>$user});
703
                }
704
                $res .= "$content" unless ($res =~ /Status=OK/); # Only add if there are problems
705
            }
706
            $postreply = $res;
707
            $content =~ s/\n/ - /;
708
            $res =~ s/\n/ - /;
709
        #    $main::syslogit->($user, "info", "$content");
710
            $main::syslogit->($user, "info", "Synced users");
711
        } else {
712
            $postreply .= "Status=OK Engine not linked, saving name\n";
713
        }
714
    }
715
    return $postreply;
716
}
717

    
718
sub Releasepressure {
719
    my ($uuid, $action, $obj) = @_;
720
    if ($help) {
721
        return <<END
722
GET::
723
Restarts pressurecontrol.
724
END
725
    }
726
    my $res;
727
    unless (`systemctl is-active pressurecontrol` =~ /inactive/) {
728
        my $daemon = Proc::Daemon->new(
729
            work_dir => '/usr/local/bin',
730
            exec_command => "systemctl restart pressurecontrol"
731
        ) or do {$postreply .= "Status=ERROR $@\n";};
732
        my $pid = $daemon->Init();
733
#        $res = `systemctl restart pressurecontrol`;
734
        return "Status=OK Venting...\n";
735
    } else {
736
        return "Status=OK Not venting\n";
737
    }
738
}
739

    
740
sub do_enable {
741
    my ($uuid, $action, $obj) = @_;
742
    if ($help) {
743
        return <<END
744
GET:username:
745
Enable a user.
746
END
747
    }
748
    my $username = $obj->{'username'};
749
    return unless ($username);
750
    if ($isadmin || ($user eq $engineuser)) {
751
        # Create user on this engine if not yet created
752
        do_save($username, 'save', $obj);
753
        my $uprivileges = $register{$username}->{'privileges'};
754
        $uprivileges =~ s/d//;
755
        $uprivileges .= 'n' unless ($uprivileges =~ /n/);# These are constant sources of problems - enable by default when enabling users to alleviate situation
756
        $register{$username}->{'privileges'} = $uprivileges;
757
        $register{$username}->{'allowinternalapi'} = 1;
758
        $postreply .= "Status=OK User $username enabled\n";
759
    } else {
760
        $postreply .= "Status=ERROR Not allowed\n";
761
    }
762
    $uiuuid = $username;
763
    return $postreply;
764
}
765

    
766
sub do_disable {
767
    my ($uuid, $action, $obj) = @_;
768
    if ($help) {
769
        return <<END
770
GET:username:
771
Disable a user.
772
END
773
    }
774
    my $username = $obj->{'username'};
775
    if ($isadmin || ($user eq $engineuser)) {
776
        my $uprivileges = $register{$username}->{'privileges'};
777
        $uprivileges .= 'd' unless ($uprivileges =~ /d/);
778
        $register{$username}->{'privileges'} = $uprivileges;
779
        $postreply .= "Stream=OK User $username disabled, halting servers...\n";
780
        require "$Stabile::basedir/cgi/servers.cgi";
781
        $Stabile::Servers::console = 1;
782
        $postreply .= Stabile::Servers::destroyUserServers($username,1);
783
        `/bin/rm /tmp/$username~*.tasks`;
784
    } else {
785
        $postreply .= "Status=ERROR Not allowed\n";
786
    }
787
    $uiuuid = $username;
788
    return $postreply;
789
}
790

    
791
sub Updateui {
792
    my ($uuid, $action, $obj) = @_;
793
    if ($help) {
794
        return <<END
795
GET:username,message,tab:
796
Update the UI for given user if logged into UI.
797
END
798
    }
799
    my $username = $obj->{'username'} || $user;
800
    my $message = $obj->{'message'};
801
    my $tab = $obj->{'tab'} || 'home';
802
    if ($isadmin || ($username eq $user) || ($user eq $engineuser)) {
803
        $postreply = $main::updateUI->({ tab => $tab, user => $username, message =>$message, type=>'update'});
804
    } else {
805
        $postreply = "Status=ERROR Not allowed\n";
806
    }
807
}
808

    
809
sub do_updateclientui {
810
    my ($uuid, $action, $obj) = @_;
811
    if ($help) {
812
        return <<END
813
GET:username,message,tab,type:
814
Update the UI for given user if logged into UI.
815
END
816
    }
817
    my $username = $obj->{'username'} || $user;
818
    my $message = $obj->{'message'};
819
    my $tab = $obj->{'tab'} || 'home';
820
    my $type= $obj->{'type'} || 'update';
821
    if ($isadmin || ($username eq $user) || ($user eq $engineuser)) {
822
        $postreply = $main::updateUI->({ tab => $tab, user => $username, message =>$message, type=>$type});
823
    } else {
824
        $postreply = "Status=ERROR Not allowed\n";
825
    }
826
}
827

    
828
sub Vent {
829
    my ($uuid, $action, $obj) = @_;
830
    if ($help) {
831
        return <<END
832
GET::
833
Restart pressurecontrol.
834
END
835
    }
836
    `systemctl restart pressurecontrol`;
837
    $postreply = "Status=OK Restarting pressurecontrol\n";
838
    return $postreply;
839
}
840

    
841
sub Deleteentirely {
842
    my ($uuid, $action, $obj) = @_;
843
    if ($help) {
844
        return <<END
845
GET:username:
846
Deletes a user and all the user's servers, images, networks etc. Warning: This destroys data
847
END
848
    }
849
    my $username = $obj->{'username'};
850
    my $reply = "Status=OK Removed $username\n";
851
    if (($isadmin || ($user eq $engineuser)) && $register{$username} && !($register{$username}->{'privileges'} =~ /a/) && !($username eq $engineuser)) {
852
        #Never delete admins
853
        my @dusers = ($username);
854
        # Add list of subusers - does not look like a good idea
855
        # foreach my $u (values %register) {
856
        #     push @dusers, $u->{'username'} if ($u->{'billto'} && $u->{'billto'} eq $username);
857
        # };
858

    
859
        foreach my $uname (@dusers) {
860
            next if ($register{$uname}->{privileges} =~ /a/); #Never delete admins
861
            $main::updateUI->({ tab => 'users', type=>'update', user=>$user, username=>$username, status=>'deleting'});
862

    
863
            $postreply .= "Stream=OK Deleting user $uname and all associated data!!!\n";
864

    
865
            require "$Stabile::basedir/cgi/servers.cgi";
866
            $Stabile::Servers::console = 1;
867

    
868
            require "$Stabile::basedir/cgi/systems.cgi";
869
            $Stabile::Systems::console = 1;
870
            Stabile::Systems::removeusersystems($uname);
871
            Stabile::Servers::removeUserServers($uname);
872

    
873
            require "$Stabile::basedir/cgi/images.cgi";
874
            $Stabile::Images::console = 1;
875
            $postreply .= Stabile::Images::removeUserImages($uname);
876

    
877
            require "$Stabile::basedir/cgi/networks.cgi";
878
            $Stabile::Networks::console = 1;
879
            Stabile::Networks::Removeusernetworks($uname);
880

    
881
            remove($uname);
882
            $reply = "$reply\n$postreply";
883

    
884
            # Also remove billing data, so next user with same username does not get old billing data
885
            `echo "delete from billing_domains where usernodetime like '$uname-%';" | mysql steamregister`;
886
            `echo "delete from billing_images where userstoragepooltime like '$uname-%';" | mysql steamregister`;
887
            `echo "delete from billing_networks where useridtime like '$uname-%';" | mysql steamregister`;
888
        }
889
        $main::updateUI->({tab => 'users', type=>'update', user=>$user});
890

    
891
    } else {
892
        $postreply .= "Stream=ERROR Cannot delete user $username - you cannot delete administrators!\n";
893
        $reply = $postreply;
894
    }
895
    return $reply;
896
}
897

    
898
sub do_save {
899
    my ($username, $action, $obj) = @_;
900
    if ($help) {
901
        return <<END
902
POST:username, password, privileges, fullname, email, opemail, alertemail, phone, opphone, opfullname, allowfrom, allowinternalapi, accounts, accountsprivileges, storagepools, memoryquota, storagequota, nodestoragequota, vcpuquota, externalipquota, rxquota, txquota:
903
Saves a user. If [username] does not exist, it is created if privileges allow this. [password] can be plaintext or a SHA256 hash.
904
END
905
    }
906
    $username = $username || $obj->{"username"};
907
    unless ($username && (($user eq $username) || $isadmin || ($user eq $engineuser))) {
908
        $postreply = "Status=ERROR Please provide a valid username\n";
909
        return $postreply;
910
    }
911
    my $password = '';
912
    my $reguser = $register{$username};
913
    if ($obj->{"password"} && $obj->{"password"} ne '--') {
914
        if (length $obj->{'password'} == 86) {
915
            $password = $obj->{"password"}; # This is already encoded
916
        } else {
917
            $password = $obj->{"password"};
918
            $MAXLEN = 20;
919
            my $msg = IsBadPassword($password);
920
            if ($msg) {
921
                $postreply = "Status=Error $msg - please choose a stronger password\n";
922
                $postmsg = "$msg - please choose a stronger password";
923
                return $postreply;
924
            } else {
925
                $password = Digest::SHA::sha512_base64($password);
926
            }
927
        }
928
    } else {
929
        $password = $reguser->{'password'};
930
    }
931
    my $fullname = $obj->{"fullname"} || $reguser->{'fullname'};
932
    my $email = $obj->{"email"} || $reguser->{'email'};
933
    my $opemail = $obj->{"opemail"} || $reguser->{'opemail'};
934
    my $alertemail = $obj->{"alertemail"} || $reguser->{'alertemail'};
935
    my $phone = $obj->{"phone"} || $reguser->{'phone'};
936
    my $opphone = $obj->{"opphone"} || $reguser->{'opphone'};
937
    my $opfullname = $obj->{"opfullname"} || $reguser->{'opfullname'};
938
    my $allowfrom = $obj->{"allowfrom"} || $reguser->{'allowfrom'};
939
    my $totpsecret = $reguser->{'totpsecret'};
940
    $totpsecret = $obj->{"totpsecret"} if (defined $obj->{"totpsecret"});
941
    my $allowinternalapi = $obj->{"allowinternalapi"} || $reguser->{'allowinternalapi'};
942

    
943
    if ($allowfrom) {
944
        my @allows = split(/(,\s*|\s+)/, $allowfrom);
945
        $allowfrom = '';
946
        foreach my $ip (@allows) {
947
            $allowfrom  .= "$1$2, " if ($ip =~ /(\d+\.\d+\.\d+\.\d+)(\/\d+)?/);
948
        }
949
        $allowfrom = substr($allowfrom,0,-2);
950
    }
951

    
952
    my $uprivileges = $reguser->{'privileges'};
953
    my $uaccounts = $reguser->{'accounts'};
954
    my $uaccountsprivileges = $reguser->{'accountsprivileges'};
955
    my $storagepools = $reguser->{'storagepools'};
956
    my $memoryquota = $reguser->{'memoryquota'};
957
    my $storagequota = $reguser->{'storagequota'};
958
    my $nodestoragequota = $reguser->{'nodestoragequota'};
959
    my $vcpuquota = $reguser->{'vcpuquota'};
960
    my $externalipquota = $reguser->{'externalipquota'};
961
    my $rxquota = $reguser->{'rxquota'};
962
    my $txquota = $reguser->{'txquota'};
963
    my $tasks = $reguser->{'tasks'};
964
    my $ubillto = $reguser->{'billto'};
965
    my $udnsdomains = $reguser->{'dnsdomains'};
966
    my $uappstoreurl = $reguser->{'appstoreurl'}; $uappstoreurl = '' if ($uappstoreurl eq '--');
967
    my $created = $reguser->{'created'} || $current_time; # set created timestamp for new users
968

    
969
    # Only allow admins to change user privileges and quotas
970
    if ($isadmin || $user eq $engineuser) {
971
        $uprivileges = $obj->{"privileges"} || $reguser->{'privileges'};
972
        $uprivileges = '' if ($uprivileges eq '--');
973
        $uprivileges = 'n' if (!$reguser->{'username'} && !$uprivileges); # Allow new users to use node storage unless explicitly disallowed
974
        $uprivileges =~ tr/adnrpu//cd; # filter out non-valid privileges
975
        $uprivileges =~ s/(.)(?=.*?\1)//g; # filter out duplicates using positive lookahead
976
        $storagepools = ($obj->{"storagepools"} || $obj->{"storagepools"} eq '0')?$obj->{"storagepools"} : $reguser->{'storagepools'};
977
        $memoryquota = (defined $obj->{"memoryquota"}) ? $obj->{"memoryquota"} : $reguser->{'memoryquota'};
978
        $storagequota = (defined $obj->{"storagequota"}) ? $obj->{"storagequota"} : $reguser->{'storagequota'};
979
        $nodestoragequota = (defined $obj->{"nodestoragequota"}) ? $obj->{"nodestoragequota"} : $reguser->{'nodestoragequota'};
980
        $vcpuquota = (defined $obj->{"vcpuquota"}) ? $obj->{"vcpuquota"} : $reguser->{'vcpuquota'};
981
        $externalipquota = (defined $obj->{"externalipquota"}) ? $obj->{"externalipquota"} : $reguser->{'externalipquota'};
982
        $rxquota = (defined $obj->{"rxquota"}) ? $obj->{"rxquota"} : $reguser->{'rxquota'};
983
        $txquota = (defined $obj->{"txquota"}) ? $obj->{"txquota"} : $reguser->{'txquota'};
984
        $tasks = $obj->{"tasks"} || $reguser->{'tasks'};
985
        $ubillto = $obj->{"billto"} || $reguser->{'billto'};
986
        $udnsdomains = $obj->{"dnsdomains"} || $udnsdomains; $udnsdomains = '' if ($udnsdomains eq '--');
987
        $uappstoreurl = $obj->{"appstoreurl"} || $uappstoreurl;
988
        $uaccounts = $obj->{"accounts"} || $reguser->{'accounts'};
989
        $uaccountsprivileges = $obj->{"accountsprivileges"} || $reguser->{'accountsprivileges'};
990
        my @ua = split(/, ?/, $uaccounts);
991
        my @up = split(/, ?/, $uaccountsprivileges);
992
        my @ua2 = ();
993
        my @up2 = ();
994
        my $i = 0;
995
        foreach my $u (@ua) {
996
            if ($register{$u} && ($u ne $username)) {
997
                push @ua2, $u;
998
                my $uprivs = $up[$i] || 'u';
999
                $uprivs =~ tr/adnrpu//cd; # filter out non-valid privileges
1000
                $uprivs =~ s/(.)(?=.*?\1)//g; # filter out duplicates using positive lookahead
1001
                push @up2, $uprivs;
1002
            }
1003
            $i++;
1004
        }
1005
        $uaccounts = join(", ", @ua2);
1006
        $uaccountsprivileges = join(", ", @up2);
1007
    }
1008

    
1009
    # Sanity checks
1010
    if (
1011
        ($fullname && length $fullname > 255)
1012
            || ($password && length $password > 255)
1013
    ) {
1014
        $postreply .= "Status=ERROR Bad data: $username\n";
1015
        return  $postreply;
1016
    }
1017
    # Only allow new users to be created by admins, i.e. no auto-registration
1018
    if ($reguser->{'username'} || $isadmin) {
1019
        $register{$username} = {
1020
            password           => $password,
1021
            fullname           => $fullname,
1022
            email              => $email,
1023
            opemail            => $opemail,
1024
            alertemail         => $alertemail,
1025
            phone              => $phone,
1026
            opphone            => $opphone,
1027
            opfullname         => $opfullname,
1028
            allowfrom          => $allowfrom,
1029
            totpsecret         => $totpsecret,
1030
            privileges         => $uprivileges,
1031
            accounts           => $uaccounts,
1032
            accountsprivileges => $uaccountsprivileges,
1033
            storagepools       => $storagepools,
1034
            memoryquota        => $memoryquota+0,
1035
            storagequota       => $storagequota+0,
1036
            nodestoragequota   => $nodestoragequota+0,
1037
            vcpuquota          => $vcpuquota+0,
1038
            externalipquota    => $externalipquota+0,
1039
            rxquota            => $rxquota+0,
1040
            txquota            => $txquota+0,
1041
            tasks              => $tasks,
1042
            allowinternalapi   => $allowinternalapi || 1, # specify '--' to explicitly disallow
1043
            billto             => $ubillto,
1044
            dnsdomains         => $udnsdomains,
1045
            appstoreurl        => $uappstoreurl,
1046
            created            => $created,
1047
            modified           => $current_time,
1048
            action             => ""
1049
        };
1050
        my %uref = %{$register{$username}};
1051
        $uref{result} = "OK";
1052
        $uref{password} = "";
1053
        $uref{status} = ($uprivileges =~ /d/)?'disabled':'enabled';
1054
        $postreply = JSON::to_json(\%uref, { pretty => 1 });
1055
#        $postreply =~ s/""/"--"/g;
1056
        $postreply =~ s/null/""/g;
1057
#        $postreply =~ s/\x/ /g;
1058
    }
1059
    return $postreply;
1060
}
1061

    
1062
sub do_list {
1063
    my ($uuid, $action, $obj) = @_;
1064
    if ($help) {
1065
        return <<END
1066
GET::
1067
List users registered on this engine.
1068
END
1069
    }
1070
    my $userfilter;
1071
    my $usermatch;
1072
    my $propmatch;
1073
    if ($uripath =~ /users(\.cgi)?\/(\?|)(me|this)/) {
1074
        $usermatch = $user;
1075
        $propmatch = $4 if ($uripath =~ /users(\.cgi)?\/(\?|)(me|this)\/(.+)/);
1076
    } elsif ($uripath =~ /users(\.cgi)?\/(\?|)(username)/) {
1077
        $userfilter = $3 if ($uripath =~ /users(\.cgi)?\/\??username(:|=)(.+)/);
1078
        $userfilter = $1 if ($userfilter =~ /(.*)\*/);
1079
    } elsif ($uripath =~ /users(\.cgi)?\/(\S+)/) {
1080
        $usermatch = $2;
1081
        $propmatch = $4 if ($uripath =~ /users(\.cgi)?\/(\S+)\/(.+)/);
1082
    }
1083

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

    
1087
    foreach my $valref (@regvalues) {
1088
        my $reguser = $valref->{'username'};
1089
        if ($user eq $reguser || $isadmin) {
1090
            next if ($reguser eq 'irigo' || $reguser eq 'guest');
1091
            my %val = %{$valref}; # Deference and assign to new ass array, effectively cloning object
1092
            $val{'password'} = '';
1093
            $val{'status'} = ($val{'privileges'} =~ /d/)?'disabled':'enabled';
1094
            if ((!$userfilter && !$usermatch) || ($userfilter && $reguser =~ /$userfilter/) || $reguser eq $usermatch) {
1095
                push @curregvalues,\%val;
1096
            }
1097
        }
1098
    }
1099
    if ($action eq 'tablelist') {
1100
        my $t2 = Text::SimpleTable->new(14,32,24,10);
1101

    
1102
        $t2->row('username', 'fullname', 'lastlogin', 'privileges');
1103
        $t2->hr;
1104
        my $pattern = $options{m};
1105
        foreach $rowref (@curregvalues){
1106
            if ($pattern) {
1107
                my $rowtext = $rowref->{'username'} . " " . $rowref->{'fullname'} . " " . $rowref->{'lastlogin'}
1108
                               . " " .  $rowref->{'privileges'};
1109
                $rowtext .= " " . $rowref->{'mac'} if ($isadmin);
1110
                next unless ($rowtext =~ /$pattern/i);
1111
            }
1112
            $t2->row($rowref->{'username'}, $rowref->{'fullname'}||'--', localtime($rowref->{'lastlogin'})||'--',
1113
            $rowref->{'privileges'}||'--');
1114
        }
1115
        #$t2->row('common', '--', '--', '--');
1116
        #$t2->row('all', '--', '--', '--') if (index($privileges,"a")!=-1);
1117
        $postreply .= $t2->draw;
1118
    } elsif ($console) {
1119
        $postreply = Dumper(\@curregvalues);
1120
    } else {
1121
        my $json_text;
1122
        if ($propmatch) {
1123
            $json_text = JSON::to_json($curregvalues[0]->{$propmatch}, {allow_nonref=>1});
1124
        } else {
1125
            $json_text = JSON::to_json(\@curregvalues, {pretty=>1});
1126
        }
1127
        $json_text =~ s/"--"/""/g;
1128
        $json_text =~ s/null/""/g;
1129
#        $json_text =~ s/\x/ /g;
1130
        $postreply = qq|{"identifier": "username", "label": "username", "items": | unless ($usermatch || $action ne 'listusers');
1131
        $postreply .= $json_text;
1132
        $postreply .= "}\n" unless ($usermatch || $action ne 'listusers');
1133
    }
1134
    return $postreply;
1135
}
1136

    
1137
sub do_uuidlookup {
1138
    if ($help) {
1139
        return <<END
1140
GET:uuid:
1141
Simple action for looking up a username (uuid) or part of a username and returning the complete username.
1142
END
1143
    }
1144
    my $u = $options{u};
1145
    $u = $params{'uuid'} unless ($u || $u eq '0');
1146
    if ($u || $u eq '0') {
1147
        foreach my $uuid (keys %register) {
1148
            if ($uuid =~ /^$u/) {
1149
                return "$uuid\n" if ($uuid eq $user || index($privileges,"a")!=-1);
1150
            }
1151
        }
1152
    }
1153
}
1154

    
1155
sub do_uuidshow {
1156
    if ($help) {
1157
        return <<END
1158
GET:uuid:
1159
Simple action for showing a single user. Pass username as uuid.
1160
END
1161
    }
1162
    my $u = $options{u};
1163
    $u = $params{'uuid'} unless ($u || $u eq '0');
1164
    if ($u eq $user || index($privileges,"a")!=-1) {
1165
        foreach my $uuid (keys %register) {
1166
            if ($uuid =~ /^$u/) {
1167
                my %hash = %{$register{$uuid}};
1168
                delete $hash{'action'};
1169
                my $dump = to_json(\%hash, {pretty=>1});
1170
                $dump =~ s/undef/"--"/g;
1171
                return $dump;
1172
            }
1173
        }
1174
    }
1175
}
1176

    
1177
sub Restoreengine {
1178
    my ($uuid, $action, $obj) = @_;
1179
    if ($help) {
1180
        return <<END
1181
GET:restorefile:
1182
Restores this engine's configuration from "restorefile", which must be one of the paths listed in listenginebackups
1183
END
1184
    }
1185
    if (!$isadmin) {
1186
        $postreply = "Status=ERROR You must be an administrator in order to restore this engine";
1187
    } else {
1188
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
1189
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
1190
        my $enginetkthash = Digest::SHA::sha512_hex($tktkey);
1191

    
1192
        my $restoredir = "/etc";
1193
        my $dbname = "steamregister";
1194
        my $restorefile = $obj->{'restorefile'};
1195

    
1196
        if ($restorefile && !($restorefile =~ /\//)) {
1197
            my $urifile = URI::Escape::uri_escape($restorefile);
1198
            my $uri = "https://www.stabile.io/irigo/engine.cgi";
1199
            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"|;
1200
            my $res = `$cmd`;
1201
            if (-s "/tmp/$restorefile") {
1202
                $res .= `(mkdir $restoredir/stabile; cd $restoredir/stabile; /bin/tar -zxf "/tmp/$restorefile")`;
1203
                $res .= `/usr/bin/mysql -e "create database $dbname;"`;
1204
                $res .= `/usr/bin/mysql $dbname < $restoredir/stabile/steamregister.sql`;
1205
                $res .= `cp -b $restoredir/stabile/hosts.allow /etc/hosts.allow`;
1206
                $res .= `cp -b $restoredir/stabile/auth_tkt_cgi.conf /etc/apache2/conf.d/`;
1207
                $res .= `cp -b $restoredir/stabile/*.crt /etc/apache2/ssl/`;
1208
                $res .= `cp -b $restoredir/stabile/*.key /etc/apache2/ssl/`;
1209
                $res .= `cp -b $restoredir/stabile/mon.cf /etc/mon/`;
1210
                $res .= `service apache2 reload`;
1211

    
1212
                # Restore default node configuration
1213
                unless ( tie(%idreg,'Tie::DBI', Hash::Merge::merge({table=>'nodeidentities', key=>'identity'}, $Stabile::dbopts)) ) {return "Unable to access identity register"};
1214
                my $defaultpath = $idreg{'default'}->{'path'} . "/casper/filesystem.dir/etc/stabile/nodeconfig.cfg";
1215
                untie %idreg;
1216
                $res .=  `cp $restoredir/stabile/nodeconfig.cfg $defaultpath`;
1217
                $main::syslogit->($user, "info", "Engine configuration $restorefile restored from the registry");
1218
                $postreply .= "Status=OK Engine configuration $restorefile restored from the registry - reloading UI\n";
1219
            } else {
1220
                $postreply .= "Status=ERROR Restore failed, $restorefile not found...\n";
1221
            }
1222
        } else {
1223
            $postreply .= "Status=ERROR You must select a restore file\n";
1224
        }
1225
    }
1226
    return $postreply;
1227
}
1228

    
1229
# Print list of available actions on objects
1230
sub do_plainhelp {
1231
    my $res;
1232
    $res .= header('text/plain') unless $console;
1233
    $res .= <<END
1234
new [username="name", password="password"]
1235
* enable: Enables a disabled user
1236
* disable: Disables a user, disallowing login
1237
* remove: Deletes a user, leaving servers, images, networks etc. untouched
1238
* deleteentirely: Deletes a user and all the user's servers, images, networks etc. Warning: This destroys data
1239

    
1240
END
1241
;
1242
}
1243

    
1244
sub do_cleanbillingdata {
1245
    my ($uuid, $action, $obj) = @_;
1246
    if ($help) {
1247
        return <<END
1248
GET:year,dryrun,cleanup:
1249
Deletes billing from [year]. Default is current year-2. Set dryrun to do a test run. Set cleanup to remove invalid entries.
1250
END
1251
    }
1252
    return "Status=Error Not allowed\n" unless ($isadmin);
1253

    
1254
    my $y = $params{'year'} || ($year-2);
1255
    my $dryrun = $params{'dryrun'};
1256
    my $cleanup = $params{'cleanup'};
1257
    my $pattern = qq|like '%-$y-__'|;
1258
    if ($cleanup) {
1259
        $pattern = qq|not like '%-____-__'|;
1260
        $y = '';
1261
    }
1262

    
1263
    unless ( tie(%bnetworksreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_networks', key=>'useridtime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1264
    my @bkeys = (tied %bnetworksreg)->select_where("useridtime $pattern");
1265
    $postreply .= "Status=OK -- this is only a test run ---\n" if ($dryrun);
1266
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y network rows\n";
1267
    foreach my $bkey (@bkeys) {
1268
        $postreply .= "Status=OK removing $bnetworksreg{$bkey}->{useridtime}\n";
1269
        delete($bnetworksreg{$bkey}) unless ($dryrun);
1270
    }
1271
    untie(%bnetworksreg);
1272

    
1273
    unless ( tie(%bimagesreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_images', key=>'userstoragepooltime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1274
    my @bkeys = (tied %bimagesreg)->select_where("userstoragepooltime $pattern");
1275
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y image rows\n";
1276
    foreach my $bkey (@bkeys) {
1277
        $postreply .= "Status=OK removing $bimagesreg{$bkey}->{userstoragepooltime}\n";
1278
        delete($bimagesreg{$bkey}) unless ($dryrun);
1279
    }
1280
    untie(%bimagesreg);
1281

    
1282
    unless ( tie(%bserversreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_domains', key=>'usernodetime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1283
    my @bkeys = (tied %bserversreg)->select_where("usernodetime $pattern");
1284
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y server rows\n";
1285
    foreach my $bkey (@bkeys) {
1286
        $postreply .= "Status=OK removing $bserversreg{$bkey}->{usernodetime}\n";
1287
        delete($bserversreg{$bkey}) unless ($dryrun);
1288
    }
1289
    untie(%bserversreg);
1290

    
1291
    return $postreply;
1292

    
1293
}
1294

    
1295
sub collectBillingData {
1296
    my ( $curuuid, $buser, $bmonth, $byear, $showcost ) = @_;
1297

    
1298
    my $vcpu=0;
1299
    my $rx = 0;
1300
    my $tx = 0;
1301
    my $vcpuavg = 0;
1302
    my $memory = 0;
1303
    my $memoryavg = 0;
1304
    my $backupsize = 0;
1305
    my $backupsizeavg = 0;
1306
    my $nodevirtualsize = 0;
1307
    my $nodevirtualsizeavg = 0;
1308
    my $virtualsize = 0;
1309
    my $virtualsizeavg = 0;
1310
    my $externalip = 0;
1311
    my $externalipavg = 0;
1312

    
1313
    my $prevmonth = $bmonth-1;
1314
    my $prevyear = $byear;
1315
    if ($prevmonth == 0) {$prevmonth=12; $prevyear--;};
1316
    $prevmonth = substr("0" . $prevmonth, -2);
1317
    my $prev_rx = 0;
1318
    my $prev_tx = 0;
1319
    # List pricing for a single system/server
1320
    if ($curuuid) {
1321
        unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domains register"};
1322
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images',key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1323
        unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
1324

    
1325
        my @domains;
1326
        my $isserver = 1 if ($domreg{$curuuid});
1327
        if ($isserver) {
1328
            @domains = $domreg{$curuuid};
1329
        } else {
1330
            @domains = values %domreg;
1331
        }
1332
        foreach my $valref (@domains) {
1333
            if ($valref->{'system'} eq $curuuid || $isserver) {
1334
                $memory += $valref->{'memory'};
1335
                $vcpu += $valref->{'vcpu'};
1336
                my $image = $valref->{'image'};
1337
                my $storagepool;
1338
                if ($imagereg{$image}) {
1339
                    $storagepool = $imagereg{$image}->{'storagepool'};
1340
                    if ($storagepool == -1) {
1341
                        $nodevirtualsize += $imagereg{$image}->{'virtualsize'};
1342
                    } else {
1343
                        $virtualsize += $imagereg{$image}->{'virtualsize'};
1344
                    }
1345
                    $backupsize += $imagereg{$image}->{'backupsize'};
1346
                }
1347
                $image = $valref->{'image2'};
1348
                if ($imagereg{$image}) {
1349
                    $storagepool = $imagereg{$image}->{'storagepool'};
1350
                    if ($storagepool == -1) {
1351
                        $nodevirtualsize += $imagereg{$image}->{'virtualsize'};
1352
                    } else {
1353
                        $virtualsize += $imagereg{$image}->{'virtualsize'};
1354
                    }
1355
                    $backupsize += $imagereg{$image}->{'backupsize'};
1356
                }
1357
                my $networkuuid = $valref->{'networkuuid1'};
1358
                my $networktype = $networkreg{$networkuuid}->{'type'};
1359
                $externalip++ if ($networktype eq 'externalip'|| $networktype eq 'ipmapping');
1360
                $networkuuid = $valref->{'networkuuid2'};
1361
                if ($networkreg{$networkuuid}) {
1362
                    $networktype = $networkreg{$networkuuid}->{'type'};
1363
                    $externalip++ if ($networktype eq 'externalip'|| $networktype eq 'ipmapping');
1364
                }
1365
            }
1366
        }
1367
        untie %domreg;
1368
        untie %imagereg;
1369
        untie %networkreg;
1370

    
1371
    # List pricing for all servers
1372
    } else {
1373
        # Network billing
1374
        unless ( tie(%bnetworksreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_networks', key=>'useridtime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
1375
        unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
1376

    
1377
        # Build list of the user's network id's
1378
        my %usernetworks;
1379
        my @nkeys = (tied %networkreg)->select_where("user = '$buser'");
1380
        foreach $network (@nkeys) {
1381
            my $id = $networkreg{$network}->{'id'};
1382
            $usernetworks{$id} = $id unless ($usernetworks{$id} || $id==0 || $id==1);
1383
        }
1384
        untie %networkreg;
1385

    
1386
        foreach $id (keys %usernetworks) {
1387
            my $networkobj = $bnetworksreg{"$buser-$id-$byear-$bmonth"};
1388
            my $prevnetworkobj = $bnetworksreg{"$buser-$id-$prevyear-$prevmonth"};
1389
            $externalip += $networkobj->{'externalip'};
1390
            $externalipavg += $networkobj->{'externalipavg'};
1391
            $rx += $networkobj->{'rx'};
1392
            $tx += $networkobj->{'tx'};
1393
            $prev_rx += $prevnetworkobj->{'rx'};
1394
            $prev_tx += $prevnetworkobj->{'tx'};
1395
        }
1396
        untie %bnetworksreg;
1397

    
1398
    # Image billing
1399

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

    
1402
        # Build list of the users storage pools
1403
        my $storagepools = $Stabile::config->get('STORAGE_POOLS_DEFAULTS') || "0";
1404
        my $upools = $register{$buser}->{'storagepools'}; # Prioritized list of users storage pools as numbers, e.g. "0,2,1"
1405
        $storagepools = $upools if ($upools && $upools ne '--');
1406
        my @spl = split(/,\s*/, $storagepools);
1407
        my $bimageobj = $bimagesreg{"$buser--1-$byear-$bmonth"};
1408
        $backupsize = $bimageobj->{'backupsize'}+0;
1409
        $nodevirtualsize = $bimageobj->{'virtualsize'}+0;
1410
        $backupsizeavg = $bimageobj->{'backupsizeavg'}+0;
1411
        $nodevirtualsizeavg = $bimageobj->{'virtualsizeavg'}+0;
1412
        foreach $pool (@spl) {
1413
            $bimageobj = $bimagesreg{"$buser-$pool-$byear-$bmonth"};
1414
            $virtualsize += $bimageobj->{'virtualsize'};
1415
            $backupsize += $bimageobj->{'backupsize'};
1416
            $virtualsizeavg += $bimageobj->{'virtualsizeavg'};
1417
            $backupsizeavg += $bimageobj->{'backupsizeavg'};
1418
        }
1419
        untie %bimagesreg;
1420

    
1421
    # Server billing
1422

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

    
1426
        my @usernodes = keys %nodereg;
1427
        untie %nodereg;
1428

    
1429
        my @nodebills;
1430
        foreach $mac (@usernodes) {
1431
            my $bserverobj = $bserversreg{"$buser-$mac-$byear-$bmonth"};
1432
            $vcpu += $bserverobj->{'vcpu'};
1433
            $memory += $bserverobj->{'memory'};
1434
            $vcpuavg += $bserverobj->{'vcpuavg'};
1435
            $memoryavg += $bserverobj->{'memoryavg'};
1436
        }
1437
        untie %bserversreg;
1438
    }
1439

    
1440
    my $uservcpuprice = 0+ $register{$user}->{'vcpuprice'};
1441
    my $usermemoryprice = 0+ $register{$user}->{'memoryprice'};
1442
    my $userstorageprice = 0+ $register{$user}->{'storageprice'};
1443
    my $usernodestorageprice = 0+ $register{$user}->{'nodestorageprice'};
1444
    my $userexternalipprice = 0+ $register{$user}->{'externalipprice'};
1445

    
1446
    $vcpuprice = $uservcpuprice || $Stabile::config->get('VCPU_PRICE') + 0;
1447
    $memoryprice = $usermemoryprice || $Stabile::config->get('MEMORY_PRICE') + 0;
1448
    $storageprice = $userstorageprice || $Stabile::config->get('STORAGE_PRICE') + 0;
1449
    $nodestorageprice = $usernodestorageprice || $Stabile::config->get('NODESTORAGE_PRICE') + 0;
1450
    $externalipprice = $userexternalipprice || $Stabile::config->get('EXTERNALIP_PRICE') + 0;
1451

    
1452
    my $memorygb = int(0.5 + 100*$memory/1024)/100;
1453
    my $virtualsizegb = int(0.5 + 100*$virtualsize/1024/1024/1024)/100;
1454
    my $nodevirtualsizegb = int(0.5 + 100*$nodevirtualsize/1024/1024/1024)/100;
1455
    my $backupsizegb = int(0.5 + 100*$backupsize/1024/1024/1024)/100;
1456

    
1457
    my $totalprice = int(0.5 + 100*($vcpu*$vcpuprice + $memorygb*$memoryprice + $virtualsizegb*$storageprice
1458
     + $nodevirtualsizegb*$nodestorageprice + $backupsizegb*$storageprice + $externalip*$externalipprice)) /100;
1459

    
1460
    my $memoryavggb = int(0.5 + 100*$memoryavg/1024)/100;
1461
    my $virtualsizeavggb = int(0.5 + 100*$virtualsizeavg/1024/1024/1024)/100;
1462
    my $nodevirtualsizeavggb = int(0.5 + 100*$nodevirtualsizeavg/1024/1024/1024)/100;
1463
    my $backupsizeavggb = int(0.5 + 100*$backupsizeavg/1024/1024/1024)/100;
1464

    
1465
    my $monfac = 1;
1466
    if ($bmonth == $month) {
1467
        # Find 00:00 of first day of month - http://www.perlmonks.org/?node_id=97120
1468
        my $fstamp = POSIX::mktime(0,0,0,1,$mon,$year-1900,0,0,-1);
1469
        my $lstamp = POSIX::mktime(0,0,0,1,$mon+1,$year-1900,0,0,-1);
1470
        $monfac = ($current_time-$fstamp)/($lstamp-$fstamp);
1471
    }
1472

    
1473
    my $totalpriceavg = int(0.5 + 100*$monfac * ($vcpuavg*$vcpuprice + $memoryavggb*$memoryprice + $virtualsizeavggb*$storageprice
1474
     + $nodevirtualsizeavggb*$nodestorageprice + $backupsizeavggb*$storageprice + $externalipavg*$externalipprice)) /100;
1475

    
1476
    $prev_rx = 0 if ($prev_rx>$rx); # Something is fishy
1477
    $prev_tx = 0 if ($prev_tx>$tx);
1478
    my $rxgb = int(0.5 + 100*($rx-$prev_rx)/1024**3)/100;
1479
    my $txgb = int(0.5 + 100*($tx-$prev_tx)/1024**3)/100;
1480

    
1481
    my %stats;
1482
    $stats{'virtualsize'} = $virtualsizegb;
1483
    $stats{'backupsize'} = $backupsizegb;
1484
    $stats{'externalip'} = $externalip;
1485
    $stats{'memory'} = $memorygb;
1486
    $stats{'month'} = $bmonth;
1487
    $stats{'nodevirtualsize'} = $nodevirtualsizegb;
1488
    $stats{'rx'} = $rxgb;
1489
    $stats{'tx'} = $txgb;
1490
    $stats{'username'} = $buser;
1491
    $stats{'vcpu'} = $vcpu;
1492
    $stats{'year'} = $byear;
1493
    $stats{'totalcost'} = "$cur $totalprice" if ($showcost);
1494
    $stats{'curtotal'} = $totalprice if ($showcost);
1495

    
1496
    if (!$curuuid) {
1497
        $stats{'virtualsizeavg'} = $virtualsizeavggb;
1498
        $stats{'backupsizeavg'} = $backupsizeavggb;
1499
        $stats{'memoryavg'} = $memoryavggb;
1500
        $stats{'nodevirtualsizeavg'} = $nodevirtualsizeavggb;
1501
        $stats{'vcpuavg'} = int(0.5 + 100*$vcpuavg)/100;
1502
        $stats{'externalipavg'} = int(0.5 + 100*$externalipavg)/100;
1503
        $stats{'totalcostavg'} = "$cur $totalpriceavg" if ($showcost);
1504
    }
1505
    return %stats;
1506
}
1507

    
1508
sub do_resetpassword {
1509
    my ($uuid, $action, $obj) = @_;
1510
    if ($help) {
1511
        return <<END
1512
GET:username:
1513
Sends an email to a user with a link to reset his password. The user must have a valid email address.
1514
END
1515
    }
1516
    my $username = $obj->{'username'} || $user;
1517
    if ($register{$username} && ($username eq $user || $isadmin)) {
1518
        my $mailaddrs = $register{$username}->{'email'};
1519
        $mailaddrs = $username if (!$mailaddrs && $username =~ /\@/);
1520
        if ($mailaddrs) {
1521
            require (dirname(__FILE__)) . "/../auth/Apache/AuthTkt.pm";
1522
            my $tktname = 'auth_' . substr($engineid, 0, 8);
1523
            my $at = Apache::AuthTkt->new(conf => $ENV{MOD_AUTH_TKT_CONF});
1524
            my $tkt = $at->ticket(uid => $username, digest_type => 'SHA512', tokens => '', debug => 0);
1525
#            my $valid = $at->valid_ticket($tkt);
1526

    
1527
            my $mailhtml = <<END;
1528
<!DOCTYPE html
1529
	PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
1530
	 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1531
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
1532
	<head>
1533
		<title>Password reset</title>
1534
		<meta http-equiv="Pragma" content="no-cache" />
1535
		<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" />
1536
		<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
1537
	</head>
1538
	<body class="tundra">
1539
		<div>
1540
			<div class="well" style="margin:20px;">
1541
				<h3 style="color: #e74c3c!important; margin-bottom:30px;">You requested a password reset at $enginename</h3>
1542
					To log in and set a new password, please click <a href="$baseurl/auth/autologin?$tktname=$tkt\&back=#chpwd">here</a>.<br>
1543
    				<div>Thanks,<br>your friendly infrastructure services</div>
1544
				</div>
1545
			</div>
1546
		</div>
1547
	</body>
1548
</html>
1549
END
1550
            ;
1551
            my $msg = MIME::Lite->new(
1552
                From     => "$enginename",
1553
                To       => $mailaddrs,
1554
                Type     => 'multipart/alternative',
1555
                Subject  => "Password reset on $enginename",
1556
            );
1557
            # my $att_text = MIME::Lite->new(
1558
            #     Type     => 'text',
1559
            #     Data     => $mailtext,
1560
            #     Encoding => 'quoted-printable',
1561
            # );
1562
            # $att_text->attr('content-type' => 'text/plain; charset=UTF-8');
1563
            # $msg->attach($att_text);
1564
            my $att_html = MIME::Lite->new(
1565
                Type     => 'text',
1566
                Data     => $mailhtml,
1567
                Encoding => 'quoted-printable',
1568
            );
1569
            $att_html->attr('content-type' => 'text/html; charset=UTF-8');
1570
            $msg->attach($att_html);
1571
            my $res = $msg->send;
1572
            $postreply = "Status=OK Password reset email sent to $mailaddrs\n";
1573
        } else {
1574
            $postreply = "Status=Error user does not have a registered email address\n";
1575
        }
1576
    } else {
1577
        $postreply = "Status=Error invalid data submitted\n";
1578
    }
1579
    return $postreply;
1580
}
1581

    
1582
sub do_changepassword {
1583
    my ($uuid, $action, $obj) = @_;
1584
    if ($help) {
1585
        return <<END
1586
GET:username,password:
1587
Changes the password for a user.
1588
END
1589
    }
1590
    my $username = $obj->{'username'} || $user;
1591
    my $password = $obj->{'password'};
1592
    if ($password && $register{$username} && ($username eq $user || $isadmin)) {
1593
        $MAXLEN = 20;
1594
        var $msg = IsBadPassword($password);
1595
        if ($msg) {
1596
            $postreply = "Status=Error $msg - please choose a stronger password\n";
1597
        } else {
1598
            $password = Digest::SHA::sha512_base64($password);
1599
            $register{$username}->{'password'} = $password;
1600
            $postreply = "Status=OK Password changed for $username\n";
1601
        }
1602
    } else {
1603
        $postreply = "Status=Error invalid data submitted\n";
1604
    }
1605
    return $postreply;
1606
}
1607

    
1608
sub do_remove {
1609
    my ($uuid, $action, $obj) = @_;
1610
    if ($help) {
1611
        return <<END
1612
GET:username:
1613
Removes a user.
1614
END
1615
    }
1616
    my $username = $obj->{'username'};
1617
    $postreply = remove($username);
1618
    return $postreply;
1619
}
1620

    
1621
sub remove {
1622
    my $username = shift;
1623
    if (!$isadmin && ($user ne $engineuser)) {
1624
        $postreply .= "Status=ERROR You are not allowed to remove user $username\n";
1625
    } elsif ($register{$username}) {
1626
        delete $register{$username};
1627
        tied(%register)->commit;
1628
        `/bin/rm /tmp/$username~*.tasks`;
1629
        unlink "../cgi/ui_update/$username~ui_update.cgi" if (-e "../cgi/ui_update/$username~ui_update.cgi");
1630
        $main::syslogit->($user, "info", "Deleted user $username from db");
1631
        if ($console) {
1632
            $postreply .= "Status=OK Deleted user $username\n";
1633
        } else {
1634
#            $main::updateUI->({ tab => 'users', type=>'update', user=>$user});
1635
            return "{}";
1636
        }
1637
        return $postreply;
1638
    } else {
1639
        $postreply .= "Status=ERROR No such user: $username\n";
1640
    }
1641
}
1642

    
1643
# Update engine users with users received from the registry
1644
sub updateEngineUsers {
1645
    my ($json_text) = @_;
1646
    return unless ($isadmin || ($user eq $engineuser));
1647
    my $res;
1648
    my $json = JSON->new;
1649
    $json->utf8([1]);
1650
    my $json_obj = $json->decode($json_text);
1651
    my @ulist = @$json_obj;
1652
    my @efields = qw(password
1653
    	address city company country email fullname phone
1654
        state zip alertemail opemail opfullname opphone billto
1655
        memoryquota storagequota vcpuquota externalipquota rxquota txquota nodestoragequota
1656
        accounts accountsprivileges privileges modified dnsdomains appstoreurl totpsecret
1657
    );
1658
    my $ures;
1659
    my $ucount = 0;
1660
    foreach my $u (@ulist) {
1661
        my $username = $u->{'username'};
1662
        if (!$register{$username} && $u->{'password'}) {
1663
            $register{$username} = {
1664
                username => $username,
1665
                password => $u->{'password'},
1666
                allowinternalapi => 1
1667
            };
1668
            $ures .= " *";
1669
        }
1670
        next unless ($register{$username});
1671
        next if ($register{$username}->{'modified'} && $register{$username}->{'modified'} > $u->{'modified'});
1672
        foreach my $efield (@efields) {
1673
            if ($efield eq 'privileges') {
1674
                $u->{$efield} =~ tr/adnrpu//cd; # filter out non-valid privileges
1675
            }
1676
            if (defined $u->{$efield}) {
1677
                $u->{$efield} += 0 if ($efield =~ /(quota|price)$/);
1678
                $register{$username}->{$efield} = $u->{$efield};
1679
            }
1680
            delete $u->{$efield} if (defined $u->{$efield} && $u->{$efield} eq '' && $efield ne 'password')
1681
        }
1682
        $ures .= "$username ($u->{'fullname'}), ";
1683
        $ucount++;
1684
        my $uid = `id -u irigo-$username`; chomp $uid;
1685
        if (!$uid) { # Check user has system account for disk quotas
1686
            $main::syslogit->($user, "info", "Adding system user $username");
1687
            `/usr/sbin/useradd -m "irigo-$username"`;
1688
            `echo "[User]\nSystemAccount=true" > /var/lib/AccountsService/users/irigo-$username`; # Don't show in login screen
1689
        }
1690

    
1691
    }
1692
    $ures = substr($res, 0, -2) . "\n";
1693
    $res .= "Status=OK Received $ucount updates on " .(scalar(@ulist)). " registry users\n";
1694
    return $res;
1695
}
1696

    
1697
sub sendEngineUser {
1698
    my ($username) = @_;
1699
    if ($enginelinked) {
1700
    # Send engine user to the registry
1701
        require LWP::Simple;
1702
        my $browser = LWP::UserAgent->new;
1703
        $browser->agent('stabile/1.0b');
1704
        $browser->protocols_allowed( [ 'http','https'] );
1705

    
1706
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
1707
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
1708
        my $tkthash = Digest::SHA::sha512_hex($tktkey);
1709
        my $json = '[' . JSON::to_json(\%{$register{$username}}) . ']';
1710
        $json =~ s/null/""/g;
1711
#        $json = uri_escape_utf8($json);
1712
        $json = URI::Escape::uri_escape($json);
1713
        my $posturl = "https://www.stabile.io/irigo/engine.cgi?action=update";
1714
        my $postreq = ();
1715
        $postreq->{'POSTDATA'} = $json;
1716
        $postreq->{'engineid'} = $engineid;
1717
        $postreq->{'enginetkthash'} = $tkthash;
1718

    
1719
#        my $req = HTTP::Request->new(POST => $posturl);
1720
#        $req->content_type("application/json; charset='utf8'");
1721
#        $req->content($postreq);
1722

    
1723
        $content = $browser->post($posturl, $postreq)->content();
1724
#        $content = $browser->post($posturl, 'Content-type' => 'text/plain;charset=utf-8', Content => $postreq)->content();
1725
#        $content = $browser->request($req)->content();
1726
        my $fullname = $register{$username}->{'fullname'};
1727
        $fullname = Encode::decode('utf8', $fullname);
1728
        return "Updated $fullname in registry\n";
1729
    }
1730
}
(9-9/9)