Project

General

Profile

Download (77.1 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
    $userexternalipquota = 0+ $register{$user}->{'externalipquota'};
119
    $userrxquota = 0+ $register{$user}->{'rxquota'};
120
    $usertxquota = 0+ $register{$user}->{'txquota'};
121

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
283

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

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

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

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

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

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

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

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

    
373
             my $res;
374
             if ($params{'format'} eq 'html') {
375
                 $postreply .= header("text/html");
376
                 $res .= qq[<tr><th>Ressource</th><th>Quantity</th><th class="$irigo_cost">Cost/month</th><th>Quota</th></tr>];
377
                 $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>];
378
                 $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>];
379
                 $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>];
380
                 $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>];
381
                 $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>];
382
                 $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>];
383
                 if (!$uuid) {
384
                     $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>];
385
                     $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>];
386
                 }
387

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

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

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

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

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

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

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

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

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

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

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

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

    
548
sub Upgradeengine {
549
    my ($uuid, $action, $obj) = @_;
550
    if ($help) {
551
        return <<END
552
GET::
553
Try to upgrade this engine to latest release from the registry
554
END
555
    }
556
    $postreply = "Status=OK Requesting upgrade of Stabile\n";
557
    print header("text/plain"), $postreply;
558
    `echo "UPGRADE=1" >> /etc/stabile/config.cfg` unless ( `grep ^UPGRADE=1 /etc/stabile/config.cfg`);
559
    my $cmd = "echo 'sleep 5 ; /usr/bin/pkill pressurecontrol' | at now";
560
    system($cmd);
561
    exit 0;
562
}
563

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
883
            remove($uname);
884
            $reply = "$reply\n$postreply";
885

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

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

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

    
945
    if (defined $obj->{"allowfrom"}) {
946
        my @allows = split(/(,\s*|\s+)/, $allowfrom);
947
        $allowfrom = '';
948
        my %allowshash;
949
        foreach my $ip (@allows) {
950
            $allowshash{"$1$2"} = 1 if ($ip =~ /(\d+\.\d+\.\d+\.\d+)(\/\d+)?/);
951
            if ($ip =~ /\w\w/) { # Check if we are dealing with a country code
952
                $ip = uc $ip;
953
                my $geoip = Geo::IP->new(GEOIP_MEMORY_CACHE);
954
                my $tz = $geoip->time_zone($ip, '');
955
                $allowshash{$ip} = 1 if ($tz); # We have a valid country code
956
            }
957
        }
958
        $allowfrom = join(", ", sort(keys %allowshash));
959
    }
960

    
961
    my $uprivileges = $reguser->{'privileges'};
962
    my $uaccounts = $reguser->{'accounts'};
963
    my $uaccountsprivileges = $reguser->{'accountsprivileges'};
964
    my $storagepools = $reguser->{'storagepools'};
965
    my $memoryquota = $reguser->{'memoryquota'};
966
    my $storagequota = $reguser->{'storagequota'};
967
    my $nodestoragequota = $reguser->{'nodestoragequota'};
968
    my $vcpuquota = $reguser->{'vcpuquota'};
969
    my $externalipquota = $reguser->{'externalipquota'};
970
    my $rxquota = $reguser->{'rxquota'};
971
    my $txquota = $reguser->{'txquota'};
972
    my $tasks = $reguser->{'tasks'};
973
    my $ubillto = $reguser->{'billto'};
974
    my $udnsdomains = $reguser->{'dnsdomains'};
975
    my $uappstoreurl = $reguser->{'appstoreurl'}; $uappstoreurl = '' if ($uappstoreurl eq '--');
976
    my $created = $reguser->{'created'} || $current_time; # set created timestamp for new users
977

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

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

    
1071
sub do_list {
1072
    my ($uuid, $action, $obj) = @_;
1073
    if ($help) {
1074
        return <<END
1075
GET::
1076
List users registered on this engine.
1077
END
1078
    }
1079
    my $userfilter;
1080
    my $usermatch;
1081
    my $propmatch;
1082
    if ($uripath =~ /users(\.cgi)?\/(\?|)(me|this)/) {
1083
        $usermatch = $user;
1084
        $propmatch = $4 if ($uripath =~ /users(\.cgi)?\/(\?|)(me|this)\/(.+)/);
1085
    } elsif ($uripath =~ /users(\.cgi)?\/(\?|)(username)/) {
1086
        $userfilter = $3 if ($uripath =~ /users(\.cgi)?\/\??username(:|=)(.+)/);
1087
        $userfilter = $1 if ($userfilter =~ /(.*)\*/);
1088
    } elsif ($uripath =~ /users(\.cgi)?\/(\S+)/) {
1089
        $usermatch = $2;
1090
        $propmatch = $4 if ($uripath =~ /users(\.cgi)?\/(\S+)\/(.+)/);
1091
    }
1092

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

    
1096
    foreach my $valref (@regvalues) {
1097
        my $reguser = $valref->{'username'};
1098
        if ($user eq $reguser || $isadmin) {
1099
            next if ($reguser eq 'irigo' || $reguser eq 'guest');
1100
            my %val = %{$valref}; # Deference and assign to new ass array, effectively cloning object
1101
            $val{'password'} = '';
1102
            $val{'status'} = ($val{'privileges'} =~ /d/)?'disabled':'enabled';
1103
            if ((!$userfilter && !$usermatch) || ($userfilter && $reguser =~ /$userfilter/) || $reguser eq $usermatch) {
1104
                push @curregvalues,\%val;
1105
            }
1106
        }
1107
    }
1108
    if ($action eq 'tablelist') {
1109
        my $t2 = Text::SimpleTable->new(14,32,24,10);
1110

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

    
1146
sub do_uuidlookup {
1147
    if ($help) {
1148
        return <<END
1149
GET:uuid:
1150
Simple action for looking up a username (uuid) or part of a username and returning the complete username.
1151
END
1152
    }
1153
    my $u = $options{u};
1154
    $u = $params{'uuid'} unless ($u || $u eq '0');
1155
    if ($u || $u eq '0') {
1156
        foreach my $uuid (keys %register) {
1157
            if ($uuid =~ /^$u/) {
1158
                return "$uuid\n" if ($uuid eq $user || index($privileges,"a")!=-1);
1159
            }
1160
        }
1161
    }
1162
}
1163

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

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

    
1201
        my $restoredir = "/etc";
1202
        my $dbname = "steamregister";
1203
        my $restorefile = $obj->{'restorefile'};
1204

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

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

    
1238
# Print list of available actions on objects
1239
sub do_plainhelp {
1240
    my $res;
1241
    $res .= header('text/plain') unless $console;
1242
    $res .= <<END
1243
new [username="name", password="password"]
1244
* enable: Enables a disabled user
1245
* disable: Disables a user, disallowing login
1246
* remove: Deletes a user, leaving servers, images, networks etc. untouched
1247
* deleteentirely: Deletes a user and all the user's servers, images, networks etc. Warning: This destroys data
1248

    
1249
END
1250
;
1251
}
1252

    
1253
sub do_cleanbillingdata {
1254
    my ($uuid, $action, $obj) = @_;
1255
    if ($help) {
1256
        return <<END
1257
GET:year,dryrun,cleanup:
1258
Deletes billing from [year]. Default is current year-2. Set dryrun to do a test run. Set cleanup to remove invalid entries.
1259
END
1260
    }
1261
    return "Status=Error Not allowed\n" unless ($isadmin);
1262

    
1263
    my $y = $params{'year'} || ($year-2);
1264
    my $dryrun = $params{'dryrun'};
1265
    my $cleanup = $params{'cleanup'};
1266
    my $pattern = qq|like '%-$y-__'|;
1267
    if ($cleanup) {
1268
        $pattern = qq|not like '%-____-__'|;
1269
        $y = '';
1270
    }
1271

    
1272
    unless ( tie(%bnetworksreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_networks', key=>'useridtime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1273
    my @bkeys = (tied %bnetworksreg)->select_where("useridtime $pattern");
1274
    $postreply .= "Status=OK -- this is only a test run ---\n" if ($dryrun);
1275
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y network rows\n";
1276
    foreach my $bkey (@bkeys) {
1277
        $postreply .= "Status=OK removing $bnetworksreg{$bkey}->{useridtime}\n";
1278
        delete($bnetworksreg{$bkey}) unless ($dryrun);
1279
    }
1280
    untie(%bnetworksreg);
1281

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

    
1291
    unless ( tie(%bserversreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_domains', key=>'usernodetime'}, $Stabile::dbopts)) ) {return "Status=Error Unable to access billing register"};
1292
    my @bkeys = (tied %bserversreg)->select_where("usernodetime $pattern");
1293
    $postreply .= "Status=OK Cleaning " . scalar @bkeys . " $y server rows\n";
1294
    foreach my $bkey (@bkeys) {
1295
        $postreply .= "Status=OK removing $bserversreg{$bkey}->{usernodetime}\n";
1296
        delete($bserversreg{$bkey}) unless ($dryrun);
1297
    }
1298
    untie(%bserversreg);
1299

    
1300
    return $postreply;
1301

    
1302
}
1303

    
1304
sub collectBillingData {
1305
    my ( $curuuid, $buser, $bmonth, $byear, $showcost ) = @_;
1306

    
1307
    my $vcpu=0;
1308
    my $rx = 0;
1309
    my $tx = 0;
1310
    my $vcpuavg = 0;
1311
    my $memory = 0;
1312
    my $memoryavg = 0;
1313
    my $backupsize = 0;
1314
    my $backupsizeavg = 0;
1315
    my $nodevirtualsize = 0;
1316
    my $nodevirtualsizeavg = 0;
1317
    my $virtualsize = 0;
1318
    my $virtualsizeavg = 0;
1319
    my $externalip = 0;
1320
    my $externalipavg = 0;
1321

    
1322
    my $prevmonth = $bmonth-1;
1323
    my $prevyear = $byear;
1324
    if ($prevmonth == 0) {$prevmonth=12; $prevyear--;};
1325
    $prevmonth = substr("0" . $prevmonth, -2);
1326
    my $prev_rx = 0;
1327
    my $prev_tx = 0;
1328
    # List pricing for a single system/server
1329
    if ($curuuid) {
1330
        unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domains register"};
1331
        unless ( tie(%imagereg,'Tie::DBI', Hash::Merge::merge({table=>'images',key=>'path'}, $Stabile::dbopts)) ) {return "Unable to access images register"};
1332
        unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
1333

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

    
1380
    # List pricing for all servers
1381
    } else {
1382
        # Network billing
1383
        unless ( tie(%bnetworksreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_networks', key=>'useridtime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
1384
        unless ( tie(%networkreg,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access networks register"};
1385

    
1386
        # Build list of the user's network id's
1387
        my %usernetworks;
1388
        my @nkeys = (tied %networkreg)->select_where("user = '$buser'");
1389
        foreach $network (@nkeys) {
1390
            my $id = $networkreg{$network}->{'id'};
1391
            $usernetworks{$id} = $id unless ($usernetworks{$id} || $id==0 || $id==1);
1392
        }
1393
        untie %networkreg;
1394

    
1395
        foreach $id (keys %usernetworks) {
1396
            my $networkobj = $bnetworksreg{"$buser-$id-$byear-$bmonth"};
1397
            my $prevnetworkobj = $bnetworksreg{"$buser-$id-$prevyear-$prevmonth"};
1398
            $externalip += $networkobj->{'externalip'};
1399
            $externalipavg += $networkobj->{'externalipavg'};
1400
            $rx += $networkobj->{'rx'};
1401
            $tx += $networkobj->{'tx'};
1402
            $prev_rx += $prevnetworkobj->{'rx'};
1403
            $prev_tx += $prevnetworkobj->{'tx'};
1404
        }
1405
        untie %bnetworksreg;
1406

    
1407
    # Image billing
1408

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

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

    
1430
    # Server billing
1431

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

    
1435
        my @usernodes = keys %nodereg;
1436
        untie %nodereg;
1437

    
1438
        my @nodebills;
1439
        foreach $mac (@usernodes) {
1440
            my $bserverobj = $bserversreg{"$buser-$mac-$byear-$bmonth"};
1441
            $vcpu += $bserverobj->{'vcpu'};
1442
            $memory += $bserverobj->{'memory'};
1443
            $vcpuavg += $bserverobj->{'vcpuavg'};
1444
            $memoryavg += $bserverobj->{'memoryavg'};
1445
        }
1446
        untie %bserversreg;
1447
    }
1448

    
1449
    my $uservcpuprice = 0+ $register{$user}->{'vcpuprice'};
1450
    my $usermemoryprice = 0+ $register{$user}->{'memoryprice'};
1451
    my $userstorageprice = 0+ $register{$user}->{'storageprice'};
1452
    my $usernodestorageprice = 0+ $register{$user}->{'nodestorageprice'};
1453
    my $userexternalipprice = 0+ $register{$user}->{'externalipprice'};
1454

    
1455
    $vcpuprice = $uservcpuprice || $Stabile::config->get('VCPU_PRICE') + 0;
1456
    $memoryprice = $usermemoryprice || $Stabile::config->get('MEMORY_PRICE') + 0;
1457
    $storageprice = $userstorageprice || $Stabile::config->get('STORAGE_PRICE') + 0;
1458
    $nodestorageprice = $usernodestorageprice || $Stabile::config->get('NODESTORAGE_PRICE') + 0;
1459
    $externalipprice = $userexternalipprice || $Stabile::config->get('EXTERNALIP_PRICE') + 0;
1460

    
1461
    my $memorygb = int(0.5 + 100*$memory/1024)/100;
1462
    my $virtualsizegb = int(0.5 + 100*$virtualsize/1024/1024/1024)/100;
1463
    my $nodevirtualsizegb = int(0.5 + 100*$nodevirtualsize/1024/1024/1024)/100;
1464
    my $backupsizegb = int(0.5 + 100*$backupsize/1024/1024/1024)/100;
1465

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

    
1469
    my $memoryavggb = int(0.5 + 100*$memoryavg/1024)/100;
1470
    my $virtualsizeavggb = int(0.5 + 100*$virtualsizeavg/1024/1024/1024)/100;
1471
    my $nodevirtualsizeavggb = int(0.5 + 100*$nodevirtualsizeavg/1024/1024/1024)/100;
1472
    my $backupsizeavggb = int(0.5 + 100*$backupsizeavg/1024/1024/1024)/100;
1473

    
1474
    my $monfac = 1;
1475
    if ($bmonth == $month) {
1476
        # Find 00:00 of first day of month - http://www.perlmonks.org/?node_id=97120
1477
        my $fstamp = POSIX::mktime(0,0,0,1,$mon,$year-1900,0,0,-1);
1478
        my $lstamp = POSIX::mktime(0,0,0,1,$mon+1,$year-1900,0,0,-1);
1479
        $monfac = ($current_time-$fstamp)/($lstamp-$fstamp);
1480
    }
1481

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

    
1485
    $prev_rx = 0 if ($prev_rx>$rx); # Something is fishy
1486
    $prev_tx = 0 if ($prev_tx>$tx);
1487
    my $rxgb = int(0.5 + 100*($rx-$prev_rx)/1024**3)/100;
1488
    my $txgb = int(0.5 + 100*($tx-$prev_tx)/1024**3)/100;
1489

    
1490
    my %stats;
1491
    $stats{'virtualsize'} = $virtualsizegb;
1492
    $stats{'backupsize'} = $backupsizegb;
1493
    $stats{'externalip'} = $externalip;
1494
    $stats{'memory'} = $memorygb;
1495
    $stats{'month'} = $bmonth;
1496
    $stats{'nodevirtualsize'} = $nodevirtualsizegb;
1497
    $stats{'rx'} = $rxgb;
1498
    $stats{'tx'} = $txgb;
1499
    $stats{'username'} = $buser;
1500
    $stats{'vcpu'} = $vcpu;
1501
    $stats{'year'} = $byear;
1502
    $stats{'totalcost'} = "$cur $totalprice" if ($showcost);
1503
    $stats{'curtotal'} = $totalprice if ($showcost);
1504

    
1505
    if (!$curuuid) {
1506
        $stats{'virtualsizeavg'} = $virtualsizeavggb;
1507
        $stats{'backupsizeavg'} = $backupsizeavggb;
1508
        $stats{'memoryavg'} = $memoryavggb;
1509
        $stats{'nodevirtualsizeavg'} = $nodevirtualsizeavggb;
1510
        $stats{'vcpuavg'} = int(0.5 + 100*$vcpuavg)/100;
1511
        $stats{'externalipavg'} = int(0.5 + 100*$externalipavg)/100;
1512
        $stats{'totalcostavg'} = "$cur $totalpriceavg" if ($showcost);
1513
    }
1514
    return %stats;
1515
}
1516

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

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

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

    
1617
sub do_remove {
1618
    my ($uuid, $action, $obj) = @_;
1619
    if ($help) {
1620
        return <<END
1621
GET:username:
1622
Removes a user.
1623
END
1624
    }
1625
    my $username = $obj->{'username'};
1626
    $postreply = remove($username);
1627
    return $postreply;
1628
}
1629

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

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

    
1700
    }
1701
    $ures = substr($res, 0, -2) . "\n";
1702
    $res .= "Status=OK Received $ucount updates on " .(scalar(@ulist)). " registry users\n";
1703
    return $res;
1704
}
1705

    
1706
sub sendEngineUser {
1707
    my ($username) = @_;
1708
    if ($enginelinked) {
1709
    # Send engine user to the registry
1710
        require LWP::Simple;
1711
        my $browser = LWP::UserAgent->new;
1712
        $browser->agent('stabile/1.0b');
1713
        $browser->protocols_allowed( [ 'http','https'] );
1714

    
1715
        my $tktcfg = ConfigReader::Simple->new($Stabile::auth_tkt_conf, [qw(TKTAuthSecret)]);
1716
        my $tktkey = $tktcfg->get('TKTAuthSecret') || '';
1717
        my $tkthash = Digest::SHA::sha512_hex($tktkey);
1718
        my $json = '[' . JSON::to_json(\%{$register{$username}}) . ']';
1719
        $json =~ s/null/""/g;
1720
#        $json = uri_escape_utf8($json);
1721
        $json = URI::Escape::uri_escape($json);
1722
        my $posturl = "https://www.stabile.io/irigo/engine.cgi?action=update";
1723
        my $postreq = ();
1724
        $postreq->{'POSTDATA'} = $json;
1725
        $postreq->{'engineid'} = $engineid;
1726
        $postreq->{'enginetkthash'} = $tkthash;
1727

    
1728
#        my $req = HTTP::Request->new(POST => $posturl);
1729
#        $req->content_type("application/json; charset='utf8'");
1730
#        $req->content($postreq);
1731

    
1732
        $content = $browser->post($posturl, $postreq)->content();
1733
#        $content = $browser->post($posturl, 'Content-type' => 'text/plain;charset=utf-8', Content => $postreq)->content();
1734
#        $content = $browser->request($req)->content();
1735
        my $fullname = $register{$username}->{'fullname'};
1736
        $fullname = Encode::decode('utf8', $fullname);
1737
        return "Updated $fullname in registry\n";
1738
    }
1739
}
(9-9/9)