Project

General

Profile

Download (77.1 KB) Statistics
| Branch: | Revision:
1 95b003ff Origo
#!/usr/bin/perl
2
3
# All rights reserved and Copyright (c) 2020 Origo Systems ApS.
4
# This file is provided with no warranty, and is subject to the terms and conditions defined in the license file LICENSE.md.
5
# The license file is part of this source code package and its content is also available at:
6 48fcda6b Origo
# https://www.stabile.io/info/stabiledocs/licensing/stabile-open-source-license
7 95b003ff Origo
8
package Stabile::Users;
9
10
use Error qw(:try);
11
use Time::Local;
12
# use Time::HiRes qw( time );
13
use Config::Simple;
14
use Text::CSV_XS qw( csv );
15
use Proc::Daemon;
16
use MIME::Lite;
17
use File::Basename;
18
use Data::Password qw(:all);
19 4aef7ef6 hq
use Geo::IP;
20 95b003ff Origo
use lib dirname (__FILE__);
21
use Stabile;
22
23
$engineid = $Stabile::config->get('ENGINEID') || "";
24
$enginename = $Stabile::config->get('ENGINENAME') || "";
25
#$enginelinked = $Stabile::config->get('ENGINE_LINKED') || "";
26
$showcost = $Stabile::config->get('SHOW_COST') || "";
27
$cur = $Stabile::config->get('CURRENCY') || "USD";
28
$engineuser = $Stabile::config->get('ENGINEUSER') || "";
29
$externaliprangestart = $Stabile::config->get('EXTERNAL_IP_RANGE_START') || "";
30
$externaliprangeend = $Stabile::config->get('EXTERNAL_IP_RANGE_END') || "";
31
$proxyiprangestart = $Stabile::config->get('PROXY_IP_RANGE_START') || "";
32
$proxyiprangeend = $Stabile::config->get('PROXY_IP_RANGE_END') || "";
33
$proxygw = $Stabile::config->get('PROXY_GW') || "";
34
35
$uiuuid;
36
$uistatus;
37
$help = 0; # If this is set, functions output help
38
39
#our %options=();
40
# -a action -h help -u uuid -m match pattern -f full list, i.e. all users
41
# -v verbose, include HTTP headers -s impersonate subaccount -t target [uuid or image]
42
# -g args to gearman task
43
#Getopt::Std::getopts("a:hfu:g:m:vs:t:", \%options);
44
45
try {
46
    Init(); # Perform various initalization tasks
47
    process() if ($package);
48
49
} catch Error with {
50
    my $ex = shift;
51
    print header('text/html', '500 Internal Server Error') unless ($console);
52
    if ($ex->{-text}) {
53
        print "Got error: ", $ex->{-text}, " on line ", $ex->{-line}, "\n";
54
    } else {
55
        print "Status=ERROR\n";
56
    }
57
} finally {
58
};
59
60
1;
61
62
sub getObj {
63
    my %h = %{@_[0]};
64
    $console = 1 if $h{"console"};
65
    $api = 1 if $h{"api"};
66
    my $username = $h{"username"} || $h{"uuid"};
67
    my $obj;
68
    $action = $action || $h{'action'};
69
    if ($action=~ /engine$|updateclientui$|updateui$/) {
70
        $obj = \%h;
71
        $obj->{pwd} = $obj->{password} if ($obj->{password});
72
    } else {
73
        $obj = $register{$username};
74
        my %hobj = %{$register{$username}};
75
        $obj = \%hobj; # We do this to get around a weird problem with freeze...
76
        my @props = qw ( restorefile engineid enginename engineurl username user password pwd fullname email
77
            opemail alertemail phone opphone opfullname allowfrom allowinternalapi privileges accounts accountsprivileges
78 54401133 hq
            storagepools memoryquota storagequota nodestoragequota vcpuquota externalipquota rxquota txquota billto dnsdomains appstoreurl totpsecret );
79 95b003ff Origo
        foreach my $prop (@props) {
80
            if (defined $h{$prop}) {
81
                $obj->{$prop} = $h{$prop};
82
            }
83
        }
84
    }
85
    return $obj;
86
}
87
88
sub Init {
89
    # Tie database tables to hashes
90
    unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username'}, $Stabile::dbopts)) ) {return "Unable to access users register"};
91
92
    # simplify globals initialized in Stabile.pm
93
    $tktuser = $tktuser || $Stabile::tktuser;
94
    $user = $user || $Stabile::user;
95
96
    $fullname = $register{$user}->{'fullname'};
97
    $email = $register{$user}->{'email'};
98
    $opemail = $register{$user}->{'opemail'};
99
    $alertemail = $register{$user}->{'alertemail'};
100
    $phone = $register{$user}->{'phone'};
101
    $opphone = $register{$user}->{'opphone'};
102
    $opfullname = $register{$user}->{'opfullname'};
103
    $allowfrom = $register{$user}->{'allowfrom'};
104
    $allowinternalapi = $register{$user}->{'allowinternalapi'};
105
    $lastlogin = $register{$user}->{'lastlogin'};
106
    $lastloginfrom = $register{$user}->{'lastloginfrom'};
107
108
#    if ($register{$user}->{'lastlogin'} ne $tkt) {
109
#        $register{$user}->{'lastlogin'} = time;
110
#        $register{$user}->{'lastloginfrom'} = $ENV{'REMOTE_ADDR'};
111
#        $register{$user}->{'lasttkt'} = $tkt;
112
#    }
113
114
    $Stabile::userstoragequota = 0+ $register{$user}->{'storagequota'};
115
    $Stabile::usernodestoragequota = 0+ $register{$user}->{'nodestoragequota'};
116
    $usermemoryquota = 0+ $register{$user}->{'memoryquota'};
117
    $uservcpuquota = 0+ $register{$user}->{'vcpuquota'};
118
    $userexternalipquota = 0+ $register{$user}->{'externalipquota'};
119
    $userrxquota = 0+ $register{$user}->{'rxquota'};
120
    $usertxquota = 0+ $register{$user}->{'txquota'};
121
122
    $storagequota = $Stabile::userstoragequota || $defaultstoragequota;
123
    $nodestoragequota = $Stabile::usernodestoragequota || $defaultnodestoragequota;
124
    $memoryquota = $usermemoryquota || $defaultmemoryquota;
125
    $vcpuquota = $uservcpuquota || $defaultvcpuquota;
126
    $externalipquota = $userexternalipquota || $defaultexternalipquota;
127
    $rxquota = $userrxquota || $defaultrxquota;
128
    $txquota = $usertxquota || $defaulttxquota;
129
130
    # Create aliases of functions
131
    *header = \&CGI::header;
132
133
    *Unlinkengine = \&Linkengine;
134
    *Updateengine = \&Linkengine;
135
    *Saveengine = \&Linkengine;
136
    *Syncusers = \&Linkengine;
137
138
    *do_help = \&action;
139
    *do_show = \&do_uuidshow;
140
    *do_delete = \&do_remove;
141
    *do_tablelist = \&do_list;
142
    *do_billingstatus = \&do_billing;
143
    *do_usage = \&do_billing;
144
    *do_usagestatus = \&do_billing;
145
    *do_billingavgstatus = \&do_billing;
146
    *do_usageavgstatus = \&do_billing;
147
    *do_upgradeengine = \&privileged_action;
148
    *do_gear_upgradeengine = \&do_gear_action;
149
    *do_backupengine = \&privileged_action;
150
    *do_gear_backupengine = \&do_gear_action;
151
    *do_restoreengine = \&privileged_action;
152
    *do_gear_restoreengine = \&do_gear_action;
153
    *do_releasepressure = \&privileged_action_async;
154
    *do_gear_releasepressure = \&do_gear_action;
155
156
    *do_linkengine = \&privileged_action;
157
    *do_gear_linkengine = \&do_gear_action;
158
    *do_saveengine = \&privileged_action_async;
159
    *do_gear_saveengine = \&do_gear_action;
160
    *do_unlinkengine = \&privileged_action;
161
    *do_gear_unlinkengine = \&do_gear_action;
162
    *do_updateengine = \&privileged_action;
163
    *do_syncusers = \&privileged_action;
164
    *do_gear_updateengine = \&do_gear_action;
165
    *do_gear_syncusers = \&do_gear_action;
166
    *do_deleteentirely = \&privileged_action;
167
    *do_gear_deleteentirely = \&do_gear_action;
168
    *do_vent = \&privileged_action;
169
    *do_gear_vent = \&do_gear_action;
170
    *do_updateui = \&privileged_action;
171
    *do_gear_updateui = \&do_gear_action;
172
}
173
174
sub do_listaccounts {
175
    my ($uuid, $action, $obj) = @_;
176
    if ($help) {
177
        return <<END
178
GET:common:
179
List other user accounts current user has access to use and switch to. This is an internal method which includes html
180
specifically for use with Dojo.
181
END
182
    }
183
    my $common = $params{'common'};
184
    my %bhash;
185
    my @accounts = split(/,\s*/, $register{$tktuser}->{'accounts'});
186
    my @accountsprivs = split(/,\s*/, $register{$tktuser}->{'accountsprivileges'});
187
    for my $i (0 .. $#accounts) {
188
        $bhash{$accounts[$i]} = {
189
            id=>$accounts[$i],
190
            privileges=>$accountsprivs[$i] || 'r'
191
        } if ($register{$accounts[$i]}); # Only include accounts that exist on this engine
192
    };
193
    $bhash{$tktuser} = {id=>$tktuser, privileges=>$privileges};
194
    delete $bhash{$user};
195
    $bhash{'common'} = {id=>'common', privileges=>'--'} if ($common);
196
    my @bvalues = values %bhash;
197
    unshift(@bvalues, {id=>$user, privileges=>$privileges});
198
    my $logout = {privileges=>'', id=>'<span class="glyphicon glyphicon-log-out" aria-hidden="true" style="font-size:15px;color:#3c3c3c; vertical-align:top; margin-top:8px;"></span> Log out '};
199
    push(@bvalues, $logout) unless ($common);
200
    $postreply = "{\"identifier\": \"id\",\"label\": \"id\", \"items\":" . JSON::to_json(\@bvalues, {pretty=>1}) . "}";
201
    return $postreply;
202
}
203
204
sub do_listids {
205
    my ($uuid, $action, $obj) = @_;
206
    if ($help) {
207
        return <<END
208
GET::
209
List other user accounts current user has read access to. Call with flat=1 if you want a flat array.
210
END
211
    }
212
    require "$Stabile::basedir/cgi/images.cgi";
213
    my $backupdevice = Stabile::Images::Getbackupdevice('', 'getbackupdevice');
214
    my $imagesdevice = Stabile::Images::Getimagesdevice('', 'getimagesdevice');
215
    my $mounts = `cat /proc/mounts | grep zfs`;
216
    my %engine_h;
217
    my $zbackupavailable = ( (($mounts =~ /$backupdevice\/backup (\S+) zfs/) && ($mounts =~ /$imagesdevice\/images (\S+) zfs/) )?1:'');
218
    my $jsontext = qq|{"identifier": "id","label": "id", "items":[| .
219
              qq|{"id": "$user", "privileges": "$privileges", "userprivileges": "$dbprivileges", "tktuser": "$tktuser", |.
220
              qq|"storagequota": $storagequota, "nodestoragequota": $nodestoragequota, "memoryquota": $memoryquota, "vcpuquota": $vcpuquota, |.
221
              qq|"fullname": "$fullname", "email": "$email", "opemail": "$opemail", "alertemail": "$alertemail", |.
222
              qq|"phone": "$phone", "opphone": "$opphone", "opfullname": "$opfullname", "appstoreurl": "$appstoreurl", |.
223 71b897d3 hq
              qq|"allowfrom": "$allowfrom", "lastlogin": "$lastlogin", "lastloginfrom": "$lastloginfrom", "allowinternalapi": "$allowinternalapi", "billto": "$billto", |.
224 45cc3024 hq
              qq|"dnsdomain": "$dnsdomain", "appstoreurl": "$appstoreurl", |;
225 95b003ff Origo
226
    if ($isadmin && $engineid) {
227
        $engine_h{"engineid"} = $engineid;
228
        $engine_h{"engineuser"} = $engineuser;
229
        $engine_h{"externaliprangestart"} = $externaliprangestart;
230
        $engine_h{"externaliprangeend"} = $externaliprangeend;
231
        $engine_h{"proxyiprangestart"} = $proxyiprangestart;
232
        $engine_h{"proxyiprangeend"} = $proxyiprangeend;
233
        $engine_h{"proxygw"} = $proxygw;
234
235
        $engine_h{"disablesnat"} = $disablesnat;
236
        $engine_h{"imagesdevice"} = $imagesdevice;
237
        $engine_h{"backupdevice"} = $backupdevice;
238
239
        my $nodecfg = new Config::Simple("/etc/stabile/nodeconfig.cfg");
240
        my $readlimit = $nodecfg->param('VM_READ_LIMIT'); # e.g. 125829120 = 120 * 1024 * 1024 = 120 MB / s
241
        my $writelimit = $nodecfg->param('VM_WRITE_LIMIT');
242
        my $iopsreadlimit = $nodecfg->param('VM_IOPS_READ_LIMIT'); # e.g. 1000 IOPS
243
        my $iopswritelimit = $nodecfg->param('VM_IOPS_WRITE_LIMIT');
244
        $engine_h{"vmreadlimit"} = $readlimit;
245
        $engine_h{"vmwritelimit"} = $writelimit;
246
        $engine_h{"vmiopsreadlimit"} = $iopsreadlimit;
247
        $engine_h{"vmiopswritelimit"} = $iopswritelimit;
248
249
        $engine_h{"zfsavailable"} = $zbackupavailable;
250
        $engine_h{"downloadmasters"} = $downloadmasters;
251
    }
252 6fdc8676 hq
    if (-e "/var/www/stabile/static/img/logo-icon-" . $ENV{HTTP_HOST} . ".png") {
253
        $jsontext .= qq|"favicon": "/stabile/static/img/logo-icon-$ENV{HTTP_HOST}.png", |;
254
    }
255 c899e439 Origo
    $engine_h{"enginename"} = $enginename;
256
    $engine_h{"enginelinked"} = $enginelinked;
257 95b003ff Origo
    $jsontext .= "\"showcost\": \"$showcost\", ";
258
    $jsontext .= "\"externalipquota\": $externalipquota, \"rxquota\": $rxquota, \"txquota\": $txquota, ";
259
    $jsontext .= qq|"defaultstoragequota": $defaultstoragequota, "defaultnodestoragequota": $defaultnodestoragequota, "defaultmemoryquota": $defaultmemoryquota, "defaultvcpuquota": $defaultvcpuquota, |;
260
    $jsontext .= "\"defaultexternalipquota\": $defaultexternalipquota, \"defaultrxquota\": $defaultrxquota, \"defaulttxquota\": $defaulttxquota, ";
261
    $jsontext .= qq|"engine": | . to_json(\%engine_h);
262
    $jsontext .= "},  ";
263
264
    $jsontext .= "{\"id\": \"common\", \"privileges\": \"--\"," .
265
      "\"fullname\": \"--\", \"email\": \"--\"," .
266
      "\"storagequota\": 0, \"memoryquota\": 0, \"vcpuquota\": 0, \"externalipquota\": 0," .
267
      "\"rxquota\": 0, \"txquota\": 0}";
268
269
    $jsontext .= ", {\"id\": \"$billto\"}" if ($billto && $billto ne '--');
270
271
    foreach my $aid (keys %ahash) {
272
        my $privs = $ahash{$aid};
273
        $jsontext .= qq|, {"id": "$aid", "privileges": "$privs"}| unless ($aid eq $user || $aid eq $billto);
274
    }
275
276
    $jsontext .= "]}";
277
    # Create ui_update link in case we are logging in with a remotely generated ticket, i.e. not passing through login.cgi
278
    `/bin/ln -s ../ui_update.cgi ../cgi/ui_update/$user~ui_update.cgi` unless (-e "../cgi/ui_update/$user~ui_update.cgi");
279
    $postreply = to_json(from_json($jsontext), {pretty=>1});
280
    return $postreply;
281
}
282
283
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 2a63870a Christian Orellana
#        $postreq->{'user'} = $tktuser;
303
        $postreq->{'user'} = $user;
304 95b003ff Origo
        $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
305
306 48fcda6b Origo
        my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=listengines", $postreq)->content();
307 95b003ff Origo
        if ($content =~ /ERROR:(.+)"/) {
308
            $postreply = qq|{"identifier": "url", "label": "name", "items": [{"url": "# $1", "name": "$enginename"}]}|;
309
        } else {
310
            $postreply = qq|{"identifier": "url", "label": "name", "items": $content}|;
311
        }
312
    } else {
313
        $postreply = qq|{"identifier": "url", "label": "name", "items": [{"url": "#", "name": "$enginename"}]}|;
314
    }
315
    return $postreply;
316
}
317
318
sub do_billing {
319
    my ($uuid, $action, $obj) = @_;
320
    if ($help) {
321
        return <<END
322
GET:uuid,username,month,startmonth,endmonth,format:
323 d24d9a01 hq
List usage data, optionally for specific server/system [uuid] or user [username]. May be called as usage, usagestatus or usageavgstatus.
324 95b003ff Origo
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 2a63870a Christian Orellana
             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 95b003ff Origo
             $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 8d7785ff Origo
                 $res .= qq[<tr><td>Backup storage (est.):</td><td align="right">$backupsizegb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$backupsizegb*$storageprice) . qq[</td><td align="right">&infin;</td></tr>];
382 95b003ff Origo
                 $res .= qq[<tr><td>External IP addresses:</td><td align="right">$externalip</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$externalip*$externalipprice) . qq[</td><td align="right">$externalipquota</td></tr>];
383
                 if (!$uuid) {
384 8d7785ff Origo
                     $res .= qq[<tr><td>Network traffic out:</td><td align="right">] . $rx . qq[ GB</td><td align="right" class="$irigo_cost">$cur 0</td><td align="right">] . int(0.5 + $rxquota/1024/1024) . qq[ GB</td></tr>];
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 95b003ff Origo
                 }
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 8d7785ff Origo
                     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 95b003ff Origo
                 };
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 2a63870a Christian Orellana
             my $virtualsizeavggb = $stats{'virtualsizeavg'};
416
             my $backupsizeavggb = $stats{'backupsizeavg'};
417
             my $memoryavggb = $stats{'memoryavg'};
418
             my $nodevirtualsizeavggb = $stats{'nodevirtualsizeavg'};
419 95b003ff Origo
             $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 8d7785ff Origo
                 $res .= qq[<tr><td>Backup storage (est.):</td><td align="right">$backupsizeavggb GB</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$backupsizeavggb*$storageprice) . qq[</td><td align="right">&infin;</td></tr>];
432 95b003ff Origo
                 $res .= qq[<tr><td>External IP addresses:</td><td align="right">] . int(0.5+100*$externalipavg)/100 . qq[</td><td align="right" class="$irigo_cost">$cur ] . int(0.5+$externalipavg*$externalipprice) . qq[</td><td align="right">$externalipquota</td></tr>];
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 48fcda6b Origo
List the backups of this engine's configuration in the registry.
475 95b003ff Origo
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 48fcda6b Origo
        my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=listbackups", $postreq)->content();
490 95b003ff Origo
        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 48fcda6b Origo
Backup this engine's configuration to the registry.
507 95b003ff Origo
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 48fcda6b Origo
        my $res = `/usr/bin/curl -k -F engineid=$engineid -F enginetkthash=$enginetkthash -F filedata=@"/tmp/$backupname.tgz" https://www.stabile.io/irigo/engine.cgi?action=backup`;
537 95b003ff Origo
        if ($res =~ /OK: $backupname.tgz received/) {
538 48fcda6b Origo
            $postreply .= "Status=OK Engine configuration saved to the registry";
539
            $main::syslogit->($user, "info", "Engine configuration saved to the registry");
540 95b003ff Origo
            unlink("/tmp/$backupname.tgz");
541
        } else {
542 48fcda6b Origo
            $postreply .= "Status=ERROR Problem backing configuration up to the registry\n$res\n";
543 95b003ff Origo
        }
544
    }
545
    return $postreply;
546
}
547
548
sub Upgradeengine {
549
    my ($uuid, $action, $obj) = @_;
550
    if ($help) {
551
        return <<END
552
GET::
553 48fcda6b Origo
Try to upgrade this engine to latest release from the registry
554 95b003ff Origo
END
555
    }
556 4aef7ef6 hq
    $postreply = "Status=OK Requesting upgrade of Stabile\n";
557
    print header("text/plain"), $postreply;
558 95b003ff Origo
    `echo "UPGRADE=1" >> /etc/stabile/config.cfg` unless ( `grep ^UPGRADE=1 /etc/stabile/config.cfg`);
559 4aef7ef6 hq
    my $cmd = "echo 'sleep 5 ; /usr/bin/pkill pressurecontrol' | at now";
560
    system($cmd);
561
    exit 0;
562 95b003ff Origo
}
563
564
sub do_billengine {
565
    my ($uuid, $action, $obj) = @_;
566
    if ($help) {
567
        return <<END
568
GET::
569 48fcda6b Origo
Submit billing data for this engine to the registry.
570 95b003ff Origo
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 d24d9a01 hq
        my $cuser = $valref->{'username'};
591
        my %stats = collectBillingData( '', $cuser, $bmonth, $byear, $showcost );
592
        $bill{"$cuser-$byear-$bmonth"} = \%stats;
593 95b003ff Origo
    }
594
    $postreq->{'engineid'} = $engineid;
595
    $postreq->{'enginetkthash'} = $tkthash;
596
    $postreq->{'keywords'} = JSON::to_json(\%bill, {pretty=>1});
597 48fcda6b Origo
    my $url = "https://www.stabile.io/irigo/engine.cgi";
598 95b003ff Origo
    $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 48fcda6b Origo
Links engine to the registry
610 95b003ff Origo
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 48fcda6b Origo
            $main::syslogit->($user, "info", "Linking engine with the registry");
636 95b003ff Origo
            $postreq->{'enginetktkey'} = $tktkey;
637
        } else {
638
            $postreq->{'enginetkthash'} = Digest::SHA::sha512_hex($tktkey);
639
        }
640
    }
641 48fcda6b Origo
    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 95b003ff Origo
        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 48fcda6b Origo
            # Send engine users to the registry
651 95b003ff Origo
            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 48fcda6b Origo
            # Send entire engine config file to the registry
664 95b003ff Origo
            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 48fcda6b Origo
            # Send entire engine piston config file to the registry
675 95b003ff Origo
            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 48fcda6b Origo
            my $content = $browser->post("https://www.stabile.io/irigo/engine.cgi?action=$linkaction", $postreq)->content();
692 95b003ff Origo
            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 48fcda6b Origo
            } elsif ($action eq 'syncusers' || $action eq 'linkengine') { # If we send user list to the registry we get merged list back
699 95b003ff Origo
                if ($content =~ /^\[/) { # Sanity check to see if we got json back
700
                    $res .= "Status=OK Engine linked\n" if ($action eq 'linkengine');
701 48fcda6b Origo
                    # Update engine users with users from the registry
702
                    $res .= updateEngineUsers($content);
703
                    $res .= "Status=OK Users synced with registry\n";
704 95b003ff Origo
                    $main::updateUI->({ tab => 'users', type=>'update', user=>$user});
705
                }
706 48fcda6b Origo
                $res .= "$content" unless ($res =~ /Status=OK/); # Only add if there are problems
707 95b003ff Origo
            }
708
            $postreply = $res;
709
            $content =~ s/\n/ - /;
710
            $res =~ s/\n/ - /;
711 64c667ea hq
        #    $main::syslogit->($user, "info", "$content");
712
            $main::syslogit->($user, "info", "Synced users");
713 95b003ff Origo
        } 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 71b897d3 hq
    return unless ($username);
752 95b003ff Origo
    if ($isadmin || ($user eq $engineuser)) {
753 71b897d3 hq
        # Create user on this engine if not yet created
754
        do_save($username, 'save', $obj);
755 95b003ff Origo
        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 6fdc8676 hq
GET:username,message,tab,type:
816 95b003ff Origo
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 6fdc8676 hq
    my $type= $obj->{'type'} || 'update';
823 95b003ff Origo
    if ($isadmin || ($username eq $user) || ($user eq $engineuser)) {
824 6fdc8676 hq
        $postreply = $main::updateUI->({ tab => $tab, user => $username, message =>$message, type=>$type});
825 95b003ff Origo
    } 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 48fcda6b Origo
        $main::updateUI->({tab => 'users', type=>'update', user=>$user});
892 95b003ff Origo
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 71b897d3 hq
    my ($username, $action, $obj) = @_;
902 95b003ff Origo
    if ($help) {
903
        return <<END
904 a439a9c4 hq
POST:username, password, privileges, fullname, email, opemail, alertemail, phone, opphone, opfullname, allowfrom, allowinternalapi, accounts, accountsprivileges, storagepools, memoryquota, storagequota, nodestoragequota, vcpuquota, externalipquota, rxquota, txquota:
905 71b897d3 hq
Saves a user. If [username] does not exist, it is created if privileges allow this. [password] can be plaintext or a SHA256 hash.
906 95b003ff Origo
END
907
    }
908 71b897d3 hq
    $username = $username || $obj->{"username"};
909 95b003ff Origo
    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 4aef7ef6 hq
    my $allowfrom = $obj->{"allowfrom"};
941 705b5366 hq
    my $totpsecret = $reguser->{'totpsecret'};
942
    $totpsecret = $obj->{"totpsecret"} if (defined $obj->{"totpsecret"});
943 95b003ff Origo
    my $allowinternalapi = $obj->{"allowinternalapi"} || $reguser->{'allowinternalapi'};
944
945 4aef7ef6 hq
    if (defined $obj->{"allowfrom"}) {
946 95b003ff Origo
        my @allows = split(/(,\s*|\s+)/, $allowfrom);
947
        $allowfrom = '';
948 4aef7ef6 hq
        my %allowshash;
949 95b003ff Origo
        foreach my $ip (@allows) {
950 4aef7ef6 hq
            $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 95b003ff Origo
        }
958 4aef7ef6 hq
        $allowfrom = join(", ", sort(keys %allowshash));
959 95b003ff Origo
    }
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 45cc3024 hq
    my $udnsdomains = $reguser->{'dnsdomains'};
975
    my $uappstoreurl = $reguser->{'appstoreurl'}; $uappstoreurl = '' if ($uappstoreurl eq '--');
976 95b003ff Origo
    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 45cc3024 hq
        $udnsdomains = $obj->{"dnsdomains"} || $udnsdomains; $udnsdomains = '' if ($udnsdomains eq '--');
996
        $uappstoreurl = $obj->{"appstoreurl"} || $uappstoreurl;
997 95b003ff Origo
        $uaccounts = $obj->{"accounts"} || $reguser->{'accounts'};
998
        $uaccountsprivileges = $obj->{"accountsprivileges"} || $reguser->{'accountsprivileges'};
999 a439a9c4 hq
        my @ua = split(/, ?/, $uaccounts);
1000
        my @up = split(/, ?/, $uaccountsprivileges);
1001 95b003ff Origo
        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 54401133 hq
            totpsecret         => $totpsecret,
1039 95b003ff Origo
            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 45cc3024 hq
            dnsdomains         => $udnsdomains,
1054
            appstoreurl        => $uappstoreurl,
1055
            created            => $created,
1056 95b003ff Origo
            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 54401133 hq
            $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 95b003ff Origo
        }
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 48fcda6b Origo
            my $uri = "https://www.stabile.io/irigo/engine.cgi";
1208 95b003ff Origo
            my $cmd = qq|/usr/bin/curl -f --cookie -O -L -F action=getbackup -F restorefile=$urifile -F engineid=$engineid -F enginetkthash=$enginetkthash "$uri" > "/tmp/$restorefile"|;
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 48fcda6b Origo
                $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 95b003ff Origo
            } 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 8d7785ff Origo
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 95b003ff Origo
sub collectBillingData {
1305
    my ( $curuuid, $buser, $bmonth, $byear, $showcost ) = @_;
1306
1307 8d7785ff Origo
    my $vcpu=0;
1308 95b003ff Origo
    my $rx = 0;
1309
    my $tx = 0;
1310
    my $vcpuavg = 0;
1311 8d7785ff Origo
    my $memory = 0;
1312 95b003ff Origo
    my $memoryavg = 0;
1313 8d7785ff Origo
    my $backupsize = 0;
1314 95b003ff Origo
    my $backupsizeavg = 0;
1315 8d7785ff Origo
    my $nodevirtualsize = 0;
1316 95b003ff Origo
    my $nodevirtualsizeavg = 0;
1317 8d7785ff Origo
    my $virtualsize = 0;
1318 95b003ff Origo
    my $virtualsizeavg = 0;
1319 8d7785ff Origo
    my $externalip = 0;
1320 95b003ff Origo
    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 2a63870a Christian Orellana
                    $externalip++ if ($networktype eq 'externalip'|| $networktype eq 'ipmapping');
1373 95b003ff Origo
                }
1374
            }
1375
        }
1376
        untie %domreg;
1377
        untie %imagereg;
1378
        untie %networkreg;
1379
1380
    # List pricing for all servers
1381
    } else {
1382 d24d9a01 hq
        # Network billing
1383 95b003ff Origo
        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 d24d9a01 hq
        # Build list of the user's network id's
1387 95b003ff Origo
        my %usernetworks;
1388 8d7785ff Origo
        my @nkeys = (tied %networkreg)->select_where("user = '$buser'");
1389 95b003ff Origo
        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 8d7785ff Origo
        $storagepools = $upools if ($upools && $upools ne '--');
1415 95b003ff Origo
        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 c899e439 Origo
        my @usernodes = keys %nodereg;
1436 95b003ff Origo
        untie %nodereg;
1437
1438
        my @nodebills;
1439 c899e439 Origo
        foreach $mac (@usernodes) {
1440 95b003ff Origo
            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 48fcda6b Origo
# Update engine users with users received from the registry
1653 95b003ff Origo
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 eb31fb38 hq
        state zip alertemail opemail opfullname opphone billto
1664 95b003ff Origo
        memoryquota storagequota vcpuquota externalipquota rxquota txquota nodestoragequota
1665 54401133 hq
        accounts accountsprivileges privileges modified dnsdomains appstoreurl totpsecret
1666 95b003ff Origo
    );
1667 48fcda6b Origo
    my $ures;
1668
    my $ucount = 0;
1669 95b003ff Origo
    foreach my $u (@ulist) {
1670
        my $username = $u->{'username'};
1671
        if (!$register{$username} && $u->{'password'}) {
1672
            $register{$username} = {
1673
                username => $username,
1674 d24d9a01 hq
                password => $u->{'password'},
1675
                allowinternalapi => 1
1676 95b003ff Origo
            };
1677 48fcda6b Origo
            $ures .= " *";
1678 95b003ff Origo
        }
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 48fcda6b Origo
        $ures .= "$username ($u->{'fullname'}), ";
1692
        $ucount++;
1693 95b003ff Origo
        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 104449f5 Origo
            `echo "[User]\nSystemAccount=true" > /var/lib/AccountsService/users/irigo-$username`; # Don't show in login screen
1698 95b003ff Origo
        }
1699
1700
    }
1701 48fcda6b Origo
    $ures = substr($res, 0, -2) . "\n";
1702 705b5366 hq
    $res .= "Status=OK Received $ucount updates on " .(scalar(@ulist)). " registry users\n";
1703 95b003ff Origo
    return $res;
1704
}
1705
1706
sub sendEngineUser {
1707
    my ($username) = @_;
1708
    if ($enginelinked) {
1709 48fcda6b Origo
    # Send engine user to the registry
1710 95b003ff Origo
        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 48fcda6b Origo
        my $posturl = "https://www.stabile.io/irigo/engine.cgi?action=update";
1723 95b003ff Origo
        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 71b897d3 hq
        return "Updated $fullname in registry\n";
1738 95b003ff Origo
    }
1739
}