Project

General

Profile

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