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