Project

General

Profile

Download (116 KB) Statistics
| Branch: | Revision:
1
#!/usr/bin/perl
2

    
3
# All rights reserved and Copyright (c) 2020 Origo Systems ApS.
4
# This file is provided with no warranty, and is subject to the terms and conditions defined in the license file LICENSE.md.
5
# The license file is part of this source code package and its content is also available at:
6
# https://www.origo.io/info/stabiledocs/licensing/stabile-open-source-license
7

    
8
package Stabile::Networks;
9

    
10
use Error qw(:try);
11
use Data::Dumper;
12
use Time::Local;
13
use Time::HiRes qw( time );
14
use Data::UUID;
15
use Net::Netmask;
16
use Net::Ping;
17
use Proc::Daemon;
18
use File::Basename;
19
use List::Util qw(shuffle);
20
use lib dirname (__FILE__);
21
use Stabile;
22

    
23
($datanic, $extnic) = $main::getNics->();
24
$extsubnet = $Stabile::config->get('EXTERNAL_SUBNET_SIZE');
25
$proxynic = $Stabile::config->get('PROXY_NIC') || $extnic;
26
$proxyip = $Stabile::config->get('PROXY_IP');
27
$proxygw = $Stabile::config->get('PROXY_GW') || $proxyip;
28
$proxysubnet = $Stabile::config->get('PROXY_SUBNET_SIZE');
29
my $engineid = $Stabile::config->get('ENGINEID') || "";
30
$dodns = $Stabile::config->get('DO_DNS') || "";
31
$enginelinked = $Stabile::config->get('ENGINE_LINKED') || "";
32

    
33
my $tenders = $Stabile::config->get('STORAGE_POOLS_ADDRESS_PATHS');
34
@tenderlist = split(/,\s*/, $tenders);
35
my $tenderpaths = $Stabile::config->get('STORAGE_POOLS_LOCAL_PATHS') || "/mnt/stabile/images";
36
@tenderpathslist = split(/,\s*/, $tenderpaths);
37
my $tendernames = $Stabile::config->get('STORAGE_POOLS_NAMES') || "Standard storage";
38
@tendernameslist = split(/,\s*/, $tendernames);
39
$storagepools = $Stabile::config->get('STORAGE_POOLS_DEFAULTS') || "0";
40

    
41
$uiuuid;
42
$uistatus;
43
$help = 0; # If this is set, functions output help
44

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

    
51
try {
52
    Init(); # Perform various initalization tasks
53
    process() if ($package);
54

    
55
} catch Error with {
56
	my $ex = shift;
57
    print header('text/html', '500 Internal Server Error') unless ($console);
58
	if ($ex->{-text}) {
59
        print "Got error: ", $ex->{-text}, " on line ", $ex->{-line}, "\n";
60
	} else {
61
	    print "Status=ERROR\n";	    
62
	}
63
} finally {
64
};
65

    
66
1;
67

    
68
sub getObj {
69
    my %h = %{@_[0]};
70
    $console = 1 if $h{"console"};
71
    $api = 1 if $h{"api"};
72
    my $uuid = $h{"uuid"};
73
    my $obj;
74
    $action = $action || $h{'action'};
75
    if (
76
        $action =~ /^dns/
77
    ) {
78
        $obj = \%h;
79
        return $obj;
80
    }
81
    $uuid = $curuuid if ($uuid eq 'this');
82
    if ($uuid =~ /(\d+\.\d+\.\d+\.\d+)/) { # ip addresses are unique across networks so we allow this
83
        foreach my $val (values %register) {
84
            if ($val->{'internalip'} eq $uuid || $val->{'externalip'} eq $uuid) {
85
                $uuid = $val->{'uuid'};
86
                last;
87
            }
88
        }
89
    }
90
    my $dbobj = $register{$uuid} || {};
91
    my $status = $dbobj->{'status'} || $h{"status"}; # Trust db status if it exists
92
    if ((!$uuid && $uuid ne '0') && (!$status || $status eq 'new') && ($action eq 'save')) {
93
        my $ug = new Data::UUID;
94
        $uuid = $ug->create_str();
95
        $status = 'new';
96
    };
97
    return 0 unless ($uuid && length $uuid == 36);
98

    
99
    $uiuuid = $uuid;
100
    $uistatus = $dbobj->{'status'};
101

    
102
    my $id = $h{"id"};
103
    my $dbid = 0+$dbobj->{'id'};
104
    if ($status eq 'new' || !$dbid) {
105
        $id = getNextId($id) ;
106
    } else {
107
        $id = $dbid;
108
    }
109

    
110
    if ($id > 4095 || $id < 0 || ($id==0 && $uuid!=0) || ($id==1 && $uuid!=1)) {
111
        $postreply .= "Status=ERROR Invalid new network id $id\n";
112
        return;
113
    }
114
    my $name = $h{"name"} || $dbobj->{'name'};
115
    my $internalip = $h{"internalip"} || $dbobj->{'internalip'};
116
    if (!($internalip =~ /\d+\.\d+\.\d+\.\d+/)) {$internalip = ""};
117
    my $externalip = $h{"externalip"} || $dbobj->{'externalip'};
118
    my $ports = $h{"ports"} || $dbobj->{'ports'};
119
    my $type = $h{"type"} || $dbobj->{'type'};
120
    my $systems = $h{"systems"} || $dbobj->{'systems'};
121
    my $force = $h{"force"};
122
    my $reguser = $dbobj->{'user'};
123
    # Sanity checks
124
    if (
125
        ($name && length $name > 255)
126
        || ($ports && length $ports > 255)
127
        || ($type && !($type =~ /gateway|ipmapping|internalip|externalip|remoteip/))
128
    ) {
129
         $postreply .= "Stroke=ERROR Bad network data: $name\n";
130
         return;
131
     }
132
     # Security check
133
     if (($user ne $reguser && index($privileges,"a")==-1 && $action ne 'save' ) ||
134
         ($reguser && $status eq "new"))
135
     {
136
         $postreply .= "Stroke=ERROR Bad user: $user, $action\n";
137
         return;
138
     }
139

    
140
    if (!$type ||($type ne 'gateway' && $type ne 'internalip' && $type ne 'ipmapping' && $type ne 'externalip' && $type ne 'remoteip')) {
141
         $type = "gateway";
142
         if ($internalip && $internalip ne "--" && $externalip && $externalip ne "--") {$type = "ipmapping";}
143
         elsif (($internalip && $internalip ne "--") || $status eq 'new') {$type = "internalip";}
144
         elsif (($externalip && $externalip ne "--") || $status eq 'new') {$type = "externalip";}
145
    }
146

    
147
    my $obj = {
148
        uuid => $uuid,
149
        id => $id,
150
        name => $name,
151
        status => $status,
152
        type => $type,
153
        internalip => $internalip,
154
        externalip => $externalip,
155
        ports => $ports,
156
        systems => $systems,
157
        force => $force,
158
        action => $h{"action"}
159
    };
160
    return $obj;
161
}
162

    
163
sub Init {
164

    
165
    # Tie database tables to hashes
166
    unless ( tie(%register,'Tie::DBI', Hash::Merge::merge({table=>'networks'}, $Stabile::dbopts)) ) {return "Unable to access network register"};
167
    unless ( tie(%domreg,'Tie::DBI', Hash::Merge::merge({table=>'domains'}, $Stabile::dbopts)) ) {return "Unable to access domain register"};
168

    
169
    # simplify globals initialized in Stabile.pm
170
    $tktuser = $tktuser || $Stabile::tktuser;
171
    $user = $user || $Stabile::user;
172

    
173
    # Create aliases of functions
174
    *header = \&CGI::header;
175

    
176
    *Natall = \&Deactivateall;
177
    *Stopall = \&Deactivateall;
178
    *Restoreall = \&Activateall;
179

    
180
    *do_save = \&Save;
181
    *do_tablelist = \&do_list;
182
    *do_jsonlist = \&do_list;
183
    *do_listnetworks = \&do_list;
184
    *do_this = \&do_list;
185
    *do_help = \&action;
186
    *do_remove = \&action;
187

    
188
    *do_restoreall = \&privileged_action;
189
    *do_activateall = \&privileged_action;
190
    *do_deactivateall = \&privileged_action;
191
    *do_natall = \&privileged_action;
192
    *do_stopall = \&privileged_action;
193
    *do_stop = \&privileged_action;
194
    *do_activate = \&privileged_action;
195
    *do_deactivate = \&privileged_action;
196

    
197
    *do_gear_activate = \&do_gear_action;
198
    *do_gear_deactivate = \&do_gear_action;
199
    *do_gear_stop = \&do_gear_action;
200
    *do_gear_activateall = \&do_gear_action;
201
    *do_gear_restoreall = \&do_gear_action;
202
    *do_gear_deactivateall = \&do_gear_action;
203
    *do_gear_stopall = \&do_gear_action;
204
    *do_gear_natall = \&do_gear_action;
205

    
206
    $rx; # Global rx count in bytes
207
    $tx; # Global tx count in bytes
208
    $etcpath = "/etc/stabile/networks";
209
}
210

    
211
sub do_list {
212
    my ($uuid, $action, $obj) = @_;
213
    if ($help) {
214
        return <<END
215
GET:uuid:
216
List networks current user has access to.
217
END
218
    }
219

    
220
    my $res;
221
    my $filter;
222
    my $statusfilter;
223
    my $uuidfilter;
224
    $uuid = $obj->{'uuid'} if ($obj->{'uuid'});
225

    
226
    if ($curuuid && ($isadmin || $register{$curuuid}->{'user'} eq $user) && $uripath =~ /networks(\.cgi)?\/(\?|)(this)/) {
227
        $uuidfilter = $curuuid;
228
    } elsif ($uripath =~ /networks(\.cgi)?\/(\?|)(name|status)/) {
229
        $filter = $3 if ($uripath =~ /networks(\.cgi)?\/.*name(:|=)(.+)/);
230
        $statusfilter = $3 if ($uripath =~ /networks(\.cgi)?\/.*status(:|=)(\w+)/);
231
    } elsif ($uripath =~ /networks(\.cgi)?\/(\w{8}-\w{4}-\w{4}-\w{4}-\w{12})/) {
232
        $uuidfilter = $2;
233
    } elsif ($uuid) {
234
        $uuidfilter = $uuid;
235
    }
236
    $uuidfilter = $options{u} unless $uuidfilter;
237
    $filter = $1 if ($filter =~ /(.*)\*/);
238
    $statusfilter = '' if ($statusfilter eq '*');
239

    
240
    my $curnetwork = URI::Escape::uri_unescape($params{'network'});
241
    my $curnetwork1 = URI::Escape::uri_unescape($params{'network1'});
242

    
243
    my $sysuuid;
244
    if ($params{'system'}) {
245
        $sysuuid = $params{'system'};
246
        $sysuuid = $cursysuuid || $curdomuuid if ($params{'system'} eq 'this');
247
    }
248

    
249
    $res .= header('application/json') unless ($console || $action eq 'tablelist');
250
    my @curregvalues;
251

    
252
    updateBilling();
253
    my @regkeys;
254
    if ($fulllist) {
255
        @regkeys = keys %register;
256
    } elsif ($uuidfilter && $isadmin) {
257
        @regkeys = (tied %register)->select_where("uuid = '$uuidfilter'");
258
    } else {
259
        @regkeys = (tied %register)->select_where("user = '$user' OR user = 'common'");
260
    }
261

    
262
    foreach my $k (@regkeys) {
263
        my $valref = $register{$k};
264
        my $uuid = $valref->{'uuid'};
265
        my $dbuser = $valref->{'user'};
266
        my $type = $valref->{'type'};
267
        my $id = $valref->{'id'};
268
    # Only list networks belonging to current user
269
        if ($dbuser eq "common" || $user eq $dbuser || $fulllist || ($uuidfilter && $isadmin)) {
270
            my $dom = $domreg{$valref->{'domains'}};
271
            next unless (!$sysuuid || $dom->{'system'} eq $sysuuid || $valref->{'domains'} eq $sysuuid);
272
            validateStatus($valref);
273
            my %val = %{$valref}; # Deference and assign to new ass array, effectively cloning object
274
            $val{'id'} += 0;
275
            $val{'rx'} = $rx;
276
            $val{'tx'} = $tx;
277
            $val{'domainnames'} = decode('utf8', $val{'domainnames'});
278
            if ($filter || $statusfilter || $uuidfilter) { # List filtered networks
279
                my $fmatch;
280
                my $smatch;
281
                my $umatch;
282
                $fmatch = 1 if (!$filter || $val{'name'}=~/$filter/i);
283
                $smatch = 1 if (!$statusfilter || $statusfilter eq 'all'
284
                        || $statusfilter eq $val{'status'}
285
                        );
286
                $umatch = 1 if ($val{'uuid'} eq $uuidfilter);
287
                if ($fmatch && $smatch && !$uuidfilter) {
288
                    push @curregvalues,\%val;
289
                } elsif ($umatch) {
290
                    push @curregvalues,\%val;
291
                    last;
292
                }
293

    
294
            } elsif ($action eq "listnetworks") { # List available networks
295
                if (($id>0 || index($privileges,"a")!=-1) && ((!$valref->{'domains'} && !$valref->{'systems'}) || $type eq 'gateway' || ($curnetwork eq $uuid && !$curnetwork1) || $curnetwork1 eq $uuid)) {
296
                    push @curregvalues,\%val;
297
                }
298
            } else {
299
                push @curregvalues,\%val if ($id>0 || index($privileges,"a")!=-1);
300
            }
301
        }
302
    }
303

    
304
    # Sort @curregvalues
305
    my $sort = 'status';
306
    $sort = $2 if ($uripath =~ /sort\((\+|\-)(\S+)\)/);
307
    my $reverse;
308
    $reverse = 1 if ($1 eq '-');
309
    if ($reverse) { # sort reverse
310
        if ($sort =~ /id/) {
311
            @curregvalues = (sort {$b->{$sort} <=> $a->{$sort}} @curregvalues); # Sort as number
312
        } else {
313
            @curregvalues = (sort {$b->{$sort} cmp $a->{$sort}} @curregvalues); # Sort as string
314
        }
315
    } else {
316
        if ($sort =~ /id/) {
317
            @curregvalues = (sort {$a->{$sort} <=> $b->{$sort}} @curregvalues); # Sort as number
318
        } else {
319
            @curregvalues = (sort {$a->{$sort} cmp $b->{$sort}} @curregvalues); # Sort as string
320
        }
321
    }
322

    
323
    my %val = ("uuid", "--", "name", "--");
324
    if ($curnetwork1) { # allow second network to be empty
325
        push @curregvalues, \%val;
326
    }
327
    if ($action eq 'tablelist') {
328
        $res .= header("text/plain") unless ($console);
329
        my $t2 = Text::SimpleTable->new(36,20,10,5,10,14,14,7);
330
        $t2->row('uuid', 'name', 'type', 'id', 'internalip', 'externalip', 'user', 'status');
331
        $t2->hr;
332
        my $pattern = $options{m};
333
        foreach $rowref (@curregvalues){
334
            if ($pattern) {
335
                my $rowtext = $rowref->{'uuid'} . " " . $rowref->{'name'} . " " . $rowref->{'type'} . " " . $rowref->{'id'}
336
                   . " " .  $rowref->{'internalip'} . " " . $rowref->{'externalip'} . " " . $rowref->{'user'} . " " . $rowref->{'status'};
337
                $rowtext .= " " . $rowref->{'mac'} if ($isadmin);
338
                next unless ($rowtext =~ /$pattern/i);
339
            }
340
            $t2->row($rowref->{'uuid'}, $rowref->{'name'}||'--', $rowref->{'type'}, $rowref->{'id'},
341
            $rowref->{'internalip'}||'--', $rowref->{'externalip'}||'--', $rowref->{'user'}, $rowref->{'status'});
342
        }
343
        $res .= $t2->draw;
344
    } elsif ($console && !$uuidfilter && $action ne 'jsonlist') {
345
        $res .= Dumper(\@curregvalues);
346
    } else {
347
        my $json_text;
348
        if ($uuidfilter) {
349
            $json_text = to_json($curregvalues[0], {pretty => 1}) if (@curregvalues);
350
        } else {
351
            $json_text = to_json(\@curregvalues, {pretty => 1}) if (@curregvalues);
352
        }
353
        $json_text = "[]" unless $json_text;
354
        $json_text =~ s/""/"--"/g;
355
        $json_text =~ s/null/"--"/g;
356
        $json_text =~ s/undef/"--"/g;
357
        $json_text =~ s/\x/ /g;
358
        $res .= qq|{"action": "$action", "identifier": "uuid", "label": "name", "items": | if ($action && $action ne 'jsonlist' && $action ne 'list' && !$uuidfilter);
359
        $res .= $json_text;
360
        $res .= qq|}| if ($action && $action ne 'jsonlist' && $action ne 'list'  && !$uuidfilter);
361
#        $res .= "JSON" if (action eq 'jsonlist');
362
    }
363
    return $res;
364
}
365

    
366
sub do_uuidlookup {
367
    if ($help) {
368
        return <<END
369
GET:uuid:
370
Simple action for looking up a uuid or part of a uuid and returning the complete uuid.
371
END
372
    }
373

    
374
    my $res;
375
    $res .= header('text/plain') unless $console;
376
    my $u = $options{u};
377
    $u = $curuuid unless ($u || $u eq '0');
378
    my $ruuid;
379
    if ($u || $u eq '0') {
380
        foreach my $uuid (keys %register) {
381
            if (($register{$uuid}->{'user'} eq $user || $register{$uuid}->{'user'} eq 'common' || $fulllist)
382
                && ($uuid =~ /^$u/ || $register{$uuid}->{'name'} =~ /^$u/)) {
383
                $ruuid = $uuid;
384
                last;
385
            }
386
        }
387
        if (!$ruuid && $isadmin) { # If no match and user is admin, do comprehensive lookup
388
            foreach $uuid (keys %register) {
389
                if ($uuid =~ /^$u/ || $register{$uuid}->{'name'} =~ /^$u/) {
390
                    $ruuid = $uuid;
391
                    last;
392
                }
393
            }
394
        }
395
    }
396
    $res .= "$ruuid\n" if ($ruuid);
397
    return $res;
398
}
399

    
400
sub do_uuidshow {
401
    if ($help) {
402
        return <<END
403
GET:uuid:
404
Simple action for showing a single network.
405
END
406
    }
407

    
408
    my $res;
409
    $res .= header('application/json') unless $console;
410
    my $u = $options{u};
411
    $u = $curuuid unless ($u || $u eq '0');
412
    if ($u || $u eq '0') {
413
        foreach my $uuid (keys %register) {
414
            if (($register{$uuid}->{'user'} eq $user || $register{$uuid}->{'user'} eq 'common' || index($privileges,"a")!=-1)
415
                && $uuid =~ /^$u/) {
416
                my %hash = %{$register{$uuid}};
417
                delete $hash{'action'};
418
                delete $hash{'nextid'};
419
#                my $dump = Dumper(\%hash);
420
                my $dump = to_json(\%hash, {pretty=>1});
421
                $dump =~ s/undef/"--"/g;
422
                $res .= $dump;
423
                last;
424
            }
425
        }
426
    }
427
    return $res;
428
}
429

    
430
sub do_updateui {
431
    my ($uuid, $action) = @_;
432
    if ($help) {
433
        return <<END
434
GET:uuid:
435
Update the web UI for the given uuid (if user has web UI loaded).
436
END
437
    }
438

    
439
    my $res;
440
    $res .= header('text/plain') unless $console;
441
    if ($register{$uuid}) {
442
        my $uistatus = $register{$uuid}->{'status'};
443
        $main::updateUI->({tab=>"networks", user=>$user, uuid=>$uuid, status=>$uistatus});
444
        $res .= "Status=OK Updated UI for $register{$uuid}->{'type'} $register{$uuid}->{'name'}: $uistatus";
445
    } else {
446
        $main::updateUI->({tab=>"networks", user=>$user});
447
        $res .= "Status=OK Updated networks UI for $user";
448
    }
449
    return $res;
450

    
451
}
452

    
453
sub do_dnslist {
454
    my ($uuid, $action) = @_;
455
    if ($help) {
456
        return <<END
457
GET:domain:
458
Lists entries in [domain] or if not specified, the default zone: $dnsdomain.
459
END
460
    }
461

    
462
    my $res = $main::dnsList->($engineid, $user, $params{'domain'});
463
    return $res;
464
}
465

    
466
sub do_envdump {
467
    my ($uuid, $action) = @_;
468
    if ($help) {
469
        return <<END
470
GET::
471
Dump environment variables
472
END
473
    }
474
    return to_json(\%ENV, {pretty=>1});
475
}
476

    
477

    
478
sub do_dnscreate {
479
    my ($uuid, $action) = @_;
480
    if ($help) {
481
        return <<END
482
GET:name, value, type:
483
Create a DNS record in the the subdomain belonging to the user's default DNS domain.
484
<b>name</b> is a domain name in the Engine's zone. <b>value</b> is either an IP address for A records or a domain name for other. <b>[type]</b> is A, CNAME, TXT or MX.
485
END
486
    }
487

    
488
    my $res = $main::dnsCreate->($engineid, $params{'name'}, $params{'value'}, $params{'type'}, $user);
489
    return $res;
490
}
491

    
492
sub do_dnsupdate {
493
    my ($uuid, $action, $obj) = @_;
494
    if ($help) {
495
        return <<END
496
GET:name,value,type,oldname,oldvalue:
497
Updates CNAME records pointing to a A record with value 'value', to point to the new 'name' in the the default DNS domain.
498
END
499
    }
500

    
501
    my $res = $main::dnsUpdate->($engineid, $obj->{'name'}, $obj->{'value'}, $obj->{'type'}, $obj->{'oldname'}, $obj->{'oldvalue'}, $user);
502
    return $res;
503
}
504

    
505
sub do_dnsclean {
506
    my ($uuid, $action) = @_;
507
    if ($help) {
508
        return <<END
509
GET::
510
Remove this engines entries in $dnsdomain zone.
511
END
512
    }
513

    
514
    my $res;
515
    $res .= header('text/plain') unless $console;
516
    $res .= $main::dnsClean->($engineid, $user);
517
    return $res;
518
}
519

    
520
sub do_dnscheck {
521
    my ($uuid, $action) = @_;
522
    if ($help) {
523
        return <<END
524
GET:name:
525
Checks if a domain name (name[.subdomain]) is available, i.e. not registered,
526
where subdomain is the subdomain belonging to the the registering engine.
527
END
528
    }
529

    
530
    my $res;
531
    $res .= header('text/plain') unless $console;
532
    my $name = $params{'name'};
533
    $name = $1 if ($name =~ /(.+)\.$dnsdomain$/);
534
    if (!$enginelinked) {
535
        $res .= "Status=ERROR You cannot create DNS records - your engine is not linked.\n";
536
    } elsif ($name =~ /^\S+$/ && !(`host $name.$dnsdomain authns1.cabocomm.dk` =~ /has address/)
537
        && $name ne 'www'
538
        && $name ne 'mail'
539
        && $name ne 'info'
540
        && $name ne 'admin'
541
        && $name ne 'work'
542
        && $name ne 'io'
543
        && $name ne 'cloud'
544
        && $name ne 'compute'
545
        && $name ne 'sso'
546
        && $name !~ /valve/
547
    ) {
548
        $res .= "Status=OK $name.$dnsdomain is available\n";
549
    } else {
550
        $res .= "Status=ERROR $name.$dnsdomain is not available\n";
551
    }
552
    return $res;
553
}
554

    
555
sub do_dnsdelete {
556
    my ($uuid, $action) = @_;
557
    if ($help) {
558
        return <<END
559
GET:name, value, type:
560
Delete a DNS record in the configured zone.
561
END
562
    }
563

    
564
    my $res = $main::dnsDelete->($engineid, $params{'name'}, $params{'value'}, $params{'type'}, $user);
565
    return $res;
566
}
567

    
568
sub do_getappstoreurl {
569
    my ($uuid, $action) = @_;
570
    if ($help) {
571
        return <<END
572
GET::
573
Get URL to the app store belonging to engine or user (uverrides engine default).
574
END
575
    }
576

    
577
    my $res;
578
    # $res .= header('application/json') unless $console;
579
    # $res .= qq|{"url": "$appstoreurl"}\n|;
580
    $res .= "$appstoreurl\n";
581
    return $res;
582
}
583

    
584
sub do_listdnsdomains {
585
    my ($uuid, $action) = @_;
586
    if ($help) {
587
        return <<END
588
GET::
589
Get the DNS domains current user has access to.
590
END
591
    }
592
    unless ( tie(%userreg,'Tie::DBI', Hash::Merge::merge({table=>'users', key=>'username', CLOBBER=>1}, $Stabile::dbopts)) ) {return 0};
593
    my $billto = $userreg{$user}->{'billto'};
594
    my $bdomains = ($userreg{$billto})?$userreg{$billto}->{'dnsdomains'}:'';
595
    my $domains = ($enginelinked)?($userreg{$user}->{'dnsdomains'} || $bdomains || $dnsdomain) :'';
596
    untie %userreg;
597
    my @doms = split(/, ?/, $domains);
598
    my $subdomain = ($enginelinked)?substr($engineid, 0, 8):'';
599
    my $linked = ($enginelinked)?'true':'false';
600
    my $res;
601
    $res .= header('application/json') unless $console;
602
    $res .= qq|{"domains": | . to_json(\@doms) . qq|, "subdomain": "$subdomain", "enginelinked": "$linked", "billto": "$billto", "user": "$user"}|;
603
    return $res;
604
}
605

    
606
sub do_getdnsdomain {
607
    my ($uuid, $action) = @_;
608
    if ($help) {
609
        return <<END
610
GET::
611
Get the default DNS domain and the subdomain this Engine registers entries in.
612
END
613
    }
614
    my $domain = ($enginelinked)?$dnsdomain:'';
615
    my $subdomain = ($enginelinked)?substr($engineid, 0, 8):'';
616
    my $linked = ($enginelinked)?'true':'false';
617
    my $res;
618
    $res .= header('application/json') unless $console;
619
    $res .= qq|{"domain": "$domain", "subdomain": "$subdomain", "enginelinked": "$linked"}|;
620
    return $res;
621
}
622

    
623
sub xmppsend {
624
    my ($uuid, $action) = @_;
625
    if ($help) {
626
        return <<END
627
GET:to, msg:
628
Send out an xmpp alert.
629
END
630
    }
631
    if ($help) {
632
        return <<END
633
Send out an xmpp alert
634
END
635
    }
636

    
637
    my $res;
638
    $res .= header('text/plain') unless $console;
639
    $res .= $main::xmppSend->($params{'to'}, $params{'msg'}, $engineid);
640
    return $res;
641
}
642

    
643
# List available network types. Possibly limited by exhausted IP ranges.
644
sub do_listnetworktypes {
645
    if ($help) {
646
        return <<END
647
GET::
648
List available network types. Possibly limited by exhausted IP ranges.
649
END
650
    }
651

    
652
    my $res;
653
    $res .= header('application/json') unless $console;
654
    # Check if we have exhausted our IP ranges
655
    my $intipavail = getNextInternalIP();
656
    my $extipavail = getNextExternalIP();
657
    my $arpipavail = getNextExternalIP('','',1);
658
    my $json_text;
659
    $json_text .= '{"type": "gateway", "name": "Gateway"}, ';
660
    $json_text .= '{"type": "internalip", "name": "Internal IP"}, ' if ($intipavail);
661
    unless (overQuotas()) {
662
        $json_text .= '{"type": "ipmapping", "name": "IP mapping"}, ' if ($intipavail && $extipavail);
663
        $json_text .= '{"type": "externalip", "name": "External IP"}, 'if ($arpipavail);
664
        $json_text .= '{"type": "remoteip", "name": "Remote IP"}, 'if ($Stabile::remoteipenabled);
665
    }
666
    $json_text = substr($json_text,0,-2);
667
    $res .= '{"identifier": "type", "label": "name", "items": [' . $json_text  . ']}';
668
    return $res;
669
}
670

    
671
# Simple action for removing all networks belonging to a user
672
sub do_removeusernetworks {
673
    my ($uuid, $action) = @_;
674
    if ($help) {
675
        return <<END
676
GET:username:
677
Remove all networks belonging to a user.
678
END
679
    }
680
    my $username = shift;
681
    return unless ($username && ($isadmin || $user eq $username) && !$isreadonly);
682
    $user = $username;
683
    my $res;
684
    $res .= header('text/plain') unless $console;
685
    if ($readonly) {
686
        $postreply .= "Status=ERROR Not allowed\n";
687
    } else {
688
        Removeusernetworks($user);
689
    }
690
    $res .= $postreply || "Status=OK Nothing to remove\n";
691
    return $res;
692
}
693

    
694
# Activate all networks. If restoreall (e.g. after reboot) is called, we only activate networks which have entries in /etc/stabile/network
695
sub Activateall {
696
    my ($nouuid, $action) = @_;
697
    if ($help) {
698
        return <<END
699
GET::
700
Tries to activate all networks. If called as restoreall by an admin, will try to restore all user's networks to saved state, e.g. after a reboot.
701
END
702
    }
703
    my @regkeys;
704
    if (($action eq "restoreall" || $fulllist) && index($privileges,"a")!=-1) { # Only an administrator is allowed to do this
705
        @regkeys = keys %register;
706
    } else {
707
        @regkeys = (tied %register)->select_where("user='$user'");
708
    }
709
    my $i = 0;
710
    if (!$isreadonly) {
711
    	foreach my $key (@regkeys) {
712
            my $valref = $register{$key};
713
    		my $uuid = $valref->{'uuid'};
714
    		my $type = $valref->{'type'};
715
    		my $id = $valref->{'id'};
716
    		my $name = $valref->{'name'};
717
    		my $internalip = $valref->{'internalip'};
718
    		my $externalip = $valref->{'externalip'};
719
    		if ($id!=0 && $id!=1 && $id<4095) {
720
                my $caction = "nat";
721
    			if (-e "$etcpath/dhcp-hosts-$id") {
722
    				if ($action eq "restoreall" && $isadmin) { # If restoring, only activate previously active networks
723
                        my $hosts;
724
                        $hosts = lc `/bin/cat $etcpath/dhcp-hosts-$id` if (-e "$etcpath/dhcp-hosts-$id");
725
                        $caction = "activate" if ($hosts =~ /($internalip|$externalip)/);
726
    			    } elsif ($action eq "activateall") {
727
    				    $caction = "activate";
728
        			}
729
                    # TODO: investigate why this is necessary - if we don't do it, networks are not activated
730
                    $user = $valref->{'user'};
731
                    do_list($uuid, 'list');
732

    
733
                    my $res = Activate($uuid, $caction);
734
                    if ($res =~ /\w+=(\w+) / ) {
735
                        $register{$uuid}->{'status'} = $1 unless (uc $1 eq 'ERROR');
736
                        $i ++ unless (uc $1 eq 'ERROR');
737
                    } else {
738
                        $postreply .= "Status=ERROR Cannot $caction $type $name $uuid: $res\n";
739
                    }
740
    		    }
741
            } else {
742
                $postreply .= "Status=ERROR Cannot $action $type $name\n" unless ($id==0 || $id==1);
743
        	}
744
        }
745
    } else {
746
        $postreply .= "Status=ERROR Problem activating all networks\n";
747
    }
748
    if ($postreply =~/Status=ERROR /) {
749
        $postreply = header('text/plain', '500 Internal Server Error') . $postreply unless $console;
750
    }
751
    $postreply .= "Status=OK activated $i networks\n";
752
    $main::updateUI->({tab=>"networks", user=>$user});
753
    updateBilling("$action $user");
754
    return $postreply;
755
}
756

    
757
# Deactivate all networks
758
sub Deactivateall {
759
    my ($nouuid, $action) = @_;
760
    if ($help) {
761
        return <<END
762
GET::
763
Tries to deactivate all networks. May also be called as natall or stopall.
764
END
765
    }
766

    
767
    my @regkeys;
768
    if ($fulllist && index($privileges,"a")!=-1) { # Only an administrator is allowed to do this
769
        @regkeys = keys %register;
770
    } else {
771
        @regkeys = (tied %register)->select_where("user='$user'");
772
    }
773
    if (!$isreadonly) {
774
		my %ids;
775
		foreach my $key (@regkeys) {
776
            my $valref = $register{$key};
777
			my $uuid = $valref->{'uuid'};
778
			my $type = $valref->{'type'};
779
			my $id = $valref->{'id'};
780
			my $name = $valref->{'name'};
781
			if ($id!=0 && $id!=1 && $id<4095) {
782
				if (-e "$etcpath/dhcp-hosts-$id") {
783
					my $caction = "deactivate";
784
					my $result;
785
					if ($action eq "stopall") {
786
						$caction = "stop";
787
						# Stop also deactivates all networks with same id, so only do this once for each id
788
						if ($ids{$id}) {
789
							$result = $valref->{'status'};
790
						} else {
791
							$result = Stop($id, $caction);
792
						}
793
						$ids{$id} = 1;
794
					} else {
795
                        my $res = Deactivate($uuid, $caction);
796
                        if ($res =~ /\w+=(\w+) /) {
797
                            $register{$uuid}->{'status'} = $1;
798
                        } else {
799
                            $postreply .= "Status=ERROR Cannot $caction $type $name $uuid: $res\n";
800
                        }
801
					}
802
					if ($result =~ /\w+=(.\w+) /) {
803
                        $register{$uuid}->{'status'} = $uistatus = $1;
804
						$uiuuid = $uuid;
805
						$postreply .= "Status=OK $caction $type $name $uuid\n";
806
						$main::syslogit->($user, "info", "$caction network $uuid ($id) ");
807
					}
808
				}
809
			} else {
810
				$postreply .= "Status=ERROR Cannot $action $type $name\n" unless ($id==0 || $id==1);
811
			}
812
		}
813
	} else {
814
		$postreply .= "Status=ERROR Problem deactivating all networks\n";
815
	}
816
    if ($postreply =~/Status=ERROR /) {
817
        $res = header('text/plain', '500 Internal Server Error') unless $console;
818
    } else {
819
        $res = header('text/plain') unless $console;
820
    }
821
	$main::updateUI->({tab=>"networks", user=>$user});
822
	updateBilling("$action $user");
823
	return $postreply;
824
}
825

    
826
sub do_updatebilling {
827
    my ($uuid, $action) = @_;
828
    if ($help) {
829
        return <<END
830
GET:uuid:
831
Update network billing for current user.
832
END
833
    }
834

    
835
    my $res;
836
    $res .= header('text/plain') unless $console;
837
    if ($isreadonly) {
838
        $res .= "Status=ERROR Not updating network billing for $user\n";
839
    } else {
840
        updateBilling("updatebilling $user");
841
        $res .= "Status=OK Updated network billing for $user\n";
842
    }
843
    return $res;
844
}
845

    
846
# Print list of available actions on objects
847
sub do_plainhelp {
848
    my $res;
849
    $res .= header('text/plain') unless $console;
850
    $res .= <<END
851
* new [type="ipmapping|internalip|externalip|gateway", name="name"]: Creates a new network
852
* activate: Activates a network. If gateway is down it is brought up.
853
* stop: Stops the gateway, effectively stopping network communcation with the outside.
854
* deactivate: Deactivates a network. Removes the associated internal IP address from the DHCP service.
855
* delete: Deletes a network. Use with care. Network can not be in use.
856

    
857
END
858
;
859
}
860

    
861
sub addDHCPAddress {
862
    my $id = shift;
863
    my $uuid = shift;
864
    my $dhcpip = shift;
865
    my $gateway = shift;
866
    my $mac = lc shift;
867
    my $isexternal = !($dhcpip =~ /^10\./);
868
    my $options;
869
    my $interface = "br$id"; #,$extnic.$id
870
    $options = "--strict-order --bind-interfaces --except-interface=lo --interface=$interface " .
871
    ($proxyip?"--dhcp-range=tag:external,$proxyip,static ":"") .
872
    "--pid-file=/var/run/stabile-$id.pid --dhcp-hostsfile=$etcpath/dhcp-hosts-$id --dhcp-range=tag:internal,$gateway,static " .
873
    "--dhcp-optsfile=$etcpath/dhcp-options-$id --port=0 --log-dhcp";
874

    
875
    my $running;
876
    my $error;
877
    my $psid;
878
    return "Status=ERROR Empty mac or ip when configuing dhcp for $name" unless ($mac && $dhcpip);
879

    
880
    eval {
881
        $psid = `/bin/cat /var/run/stabile-$id.pid` if (-e "/var/run/stabile-$id.pid");
882
        chomp $psid;
883
        $running = -e "/proc/$psid" if ($psid);
884
        # `/bin/ps p $psid` =~ /$psid/
885
        # `/bin/ps ax | /bin/grep stabile-$id.pid | /usr/bin/wc -l`; 1;} or do
886
        1;
887
    } or do {$error .= "Status=ERROR Problem configuring dhcp for $name $@\n";};
888

    
889
    if (-e "$etcpath/dhcp-hosts-$id") {
890
        open(TEMP1, "<$etcpath/dhcp-hosts-$id") || ($error .= "Status=ERROR Problem reading dhcp hosts\n");
891
        open(TEMP2, ">$etcpath/dhcp-hosts-$id.new") || ($error .= "Status=ERROR Problem writing dhcp hosts $etcpath/dhcp-hosts-$id.new\n");
892
        while (<TEMP1>) {
893
            my $line = $_;
894
            print TEMP2 $line unless (($mac && $line =~ /^$mac/i) || ($line & $line =~ /.+,$dhcpip/));
895
        }
896
        print TEMP2 "$mac," . (($isexternal)?"set:external,":"set:internal,") . "$dhcpip\n";
897
        close(TEMP1);
898
        close(TEMP2);
899
        rename("$etcpath/dhcp-hosts-$id", "$etcpath/dhcp-hosts-$id.old") || ($error .= "Status=ERROR Problem writing dhcp hosts\n");
900
        rename("$etcpath/dhcp-hosts-$id.new", "$etcpath/dhcp-hosts-$id") || ($error .= "Status=ERROR Problem writing dhcp hosts\n");
901
    } else {
902
        open(TEMP1, ">$etcpath/dhcp-hosts-$id") || ($error .= "Status=ERROR Problem writing dhcp options\n");
903
        print TEMP1 "$mac,$dhcpip\n";
904
        close (TEMP1);
905
    }
906

    
907
#    unless (-e "$etcpath/dhcp-options-$id") {
908
        my $block = new Net::Netmask("$proxygw/$proxysubnet");
909
        my $proxymask = $block->mask();
910
        open(TEMP1, ">$etcpath/dhcp-options-$id") || ($error .= "Status=ERROR Problem writing dhcp options\n");
911

    
912
# Turns out the VM's gateway has to be $proxyip and not $proxygw in our proxyarp setup
913
        print TEMP1 <<END;
914
tag:external,option:router,$proxyip
915
tag:external,option:netmask,$proxymask
916
tag:external,option:dns-server,$proxyip
917
tag:internal,option:router,$gateway
918
tag:internal,option:netmask,255.255.255.0
919
tag:internal,option:dns-server,$gateway
920
option:dns-server,1.1.1.1
921
END
922

    
923
        close (TEMP1);
924
#    }
925

    
926
    if ($running) {
927
        $main::syslogit->($user, 'info', "HUPing dnsmasq 1: $id");
928
        eval {`/usr/bin/pkill -HUP -f "stabile-$id.pid"`; 1;} or do {$error .= "Status=ERROR Problem configuring dhcp for $name $@\n";};
929
    } else {
930
        eval {`/usr/sbin/dnsmasq $options`;1;} or do {$error .= "Status=ERROR Problem configuring dhcp for $name $@\n";};
931
    }
932
    # Allow access to DHCP service
933
    `iptables -D INPUT -i br$id -p udp -m udp --dport 67 -j ACCEPT`;
934
    `iptables -I INPUT -i br$id -p udp -m udp --dport 67 -j ACCEPT`;
935
    # Allow access to DNS service
936
    `iptables -D INPUT -i br$id -p udp -m udp --dport 53 -j ACCEPT`;
937
    `iptables -I INPUT -i br$id -p udp -m udp --dport 53 -j ACCEPT`;
938
    `iptables -D INPUT -i br$id -p tcp -m tcp --dport 53 -j ACCEPT`;
939
    `iptables -I INPUT -i br$id -p tcp -m tcp --dport 53 -j ACCEPT`;
940

    
941
    return $error?$error:"OK";
942
}
943

    
944
sub removeDHCPAddress {
945
    my $id = shift;
946
    my $uuid = shift;
947
    my $dhcpip = shift;
948
    my $mac;
949
    $mac = lc $domreg{$uuid}->{'nicmac1'} if ($domreg{$uuid});
950
    my $isexternal = ($dhcpip =~ /^10\./);
951
    my $running;
952
    my $error;
953
    my $psid;
954
    return "Status=ERROR Empty mac or ip when configuring dhcp for $name" unless ($mac || $dhcpip);
955

    
956
    eval {
957
        $psid = `/bin/cat /var/run/stabile-$id.pid` if (-e "/var/run/stabile-$id.pid");
958
        chomp $psid;
959
        $running = -e "/proc/$psid" if ($psid);
960
        1;
961
    } or do {$error .= "Status=ERROR Problem deconfiguring dhcp for $name $@\n";};
962

    
963
    my $keepup;
964
    if (-e "$etcpath/dhcp-hosts-$id") {
965
        open(TEMP1, "<$etcpath/dhcp-hosts-$id") || ($error .= "Status=ERROR Problem reading dhcp hosts\n");
966
        open(TEMP2, ">$etcpath/dhcp-hosts-$id.new") || ($error .= "Status=ERROR Problem writing dhcp hosts\n");
967
        while (<TEMP1>) {
968
            my $line = $_; chomp $line;
969
            if ($line && $line =~ /(.+),.+,($dhcpip)/) { # Release and remove this mac/ip from lease file
970
                $main::syslogit->($user, 'info', "Releasing dhcp lease: br$id $dhcpip $1");
971
                `/usr/bin/dhcp_release br$id $dhcpip $1`;
972
            } elsif ($mac && $line =~ /^$mac/i) {
973
                # If we find a stale assigment to the mac we are removing, remove this also
974
                $main::syslogit->($user, 'info', "Releasing stale dhcp lease: br$id $dhcpip $mac");
975
                `/usr/bin/dhcp_release br$id $dhcpip $mac`;
976
            } else {
977
                # Keep all other leases, and keep up the daemon if any leases found
978
                print TEMP2 "$line\n";
979
                $keepup = 1 if $line;
980
            }
981
        }
982
        close(TEMP1);
983
        close(TEMP2);
984
        rename("$etcpath/dhcp-hosts-$id", "$etcpath/dhcp-hosts-$id.old") || ($error .= "Status=ERROR Problem writing dhcp hosts\n");
985
        rename("$etcpath/dhcp-hosts-$id.new", "$etcpath/dhcp-hosts-$id") || ($error .= "Status=ERROR Problem writing dhcp hosts\n");
986
    }
987

    
988
    if ($keepup) {
989
        if ($running) {
990
            $main::syslogit->($user, 'info', "HUPing dnsmasq 2: $id");
991
            eval {`/usr/bin/pkill -HUP -f "stabile-$id.pid"`; 1;} or do {$error .= "Status=ERROR Problem configuring dhcp for $name $@\n";};
992
        }
993
    } else {
994
        unlink "$etcpath/dhcp-options-$id" if (-e "$etcpath/dhcp-options-$id");
995
        if ($running) {
996
            # Disallow access to DHCP service
997
            `iptables -D INPUT -i br$id -p udp -m udp --dport 67 -j ACCEPT`;
998
            # Disallow access to DNS service
999
            `iptables -D INPUT -i br$id -p udp -m udp --dport 53 -j ACCEPT`;
1000
            `iptables -D INPUT -i br$id -p tcp -m tcp --dport 53 -j ACCEPT`;
1001
            # Take down dhcp server
1002
            $main::syslogit->($user, 'info', "Killing dnsmasq 3: $id");
1003
            eval {`/usr/bin/pkill -f "stabile-$id.pid"`; 1;} or do {$error .= "Status=ERROR Problem configuring dhcp for $name $@\n";};
1004
        }
1005
    }
1006

    
1007
    return $error?$error:"OK";
1008
}
1009

    
1010
# Helper function
1011
sub save {
1012
    my ($id, $uuid, $name, $status, $type, $internalip, $externalip, $ports, $buildsystem, $username) = @_;
1013
    my $obj = {
1014
        id => $id,
1015
        uuid => $uuid,
1016
        name => $name,
1017
        status => $status,
1018
        type => $type,
1019
        internalip => $internalip,
1020
        externalip => $externalip,
1021
        ports => $ports,
1022
        buildsystem => $buildsystem,
1023
        username => $username
1024
    };
1025
    return Save($uuid, 'save', $obj);
1026
}
1027

    
1028
sub Save {
1029
    my ($uuid, $action, $obj) = @_;
1030
    if ($help) {
1031
        return <<END
1032
POST:uuid, id, name, internalip, externalip, ports, type, systems, activate:
1033
To save a collection of networks you either PUT or POST a JSON array to the main endpoint with objects representing the networks with the changes you want.
1034
Depending on your privileges not all changes are permitted. If you save without specifying a uuid, a new network is created.
1035
For now, [activate] only has effect when creating a new connection with a linked system/server.
1036
END
1037
    }
1038
    $uuid = $obj->{'uuid'} if ($obj->{'uuid'});
1039
    my $regnet = $register{$uuid};
1040
    my $id = $obj->{id};
1041
    my $name = $obj->{name};
1042
    my $status = $obj->{status};
1043
    my $type = $obj->{type} || $regnet->{type};
1044
    my $internalip = $obj->{internalip};
1045
    my $externalip = $obj->{externalip};
1046
    my $ports = $obj->{ports};
1047
    my $buildsystem = $obj->{buildsystem};
1048
    my $username = $obj->{username};
1049
    my $systems = $obj->{systems}; # Optionally link this network to a system
1050

    
1051
    $postreply = "" if ($buildsystem);
1052
	$username = $user unless ($username);
1053

    
1054
    $status = $regnet->{'status'} || $status; # Trust db status if it exists
1055
    if ((!$uuid && $uuid ne '0') && $status eq 'new') {
1056
        my $ug = new Data::UUID;
1057
        $uuid = $ug->create_str();
1058
    };
1059
    if ($status eq 'new') {
1060
        $name  = 'New Connection' unless ($name);
1061
    }
1062
    unless ($uuid && length $uuid == 36) {
1063
        $postreply .= "Status=Error Invalid uuid $uuid\n";
1064
        return $postreply;
1065
    }
1066
    my $systemnames = $regnet->{'systemnames'};
1067

    
1068
    my $dbid = 0+$regnet->{'id'};
1069
    if ($status eq 'new' || !$dbid) {
1070
        $id = getNextId($id) ;
1071
    } else {
1072
        $id = $dbid;
1073
    }
1074
    if ($id > 4095 || $id < 0 || ($id==0 && $uuid!=0 && $isadmin) || ($id==1 && $uuid!=1 && $isadmin)) {
1075
        $postreply .= "Status=ERROR Invalid network id $id\n";
1076
        return $postreply;
1077
    }
1078
    $name = $name || $regnet->{'name'};
1079
    $internalip = $internalip || $regnet->{'internalip'};
1080
    if (!($internalip =~ /\d+\.\d+\.\d+\.\d+/)) {$internalip = ''};
1081
    $externalip = $externalip || $regnet->{'externalip'};
1082
    $ports = $ports || $regnet->{'ports'};
1083
    my $reguser = $regnet->{'user'};
1084
    # Sanity checks
1085
    if (
1086
        ($name && length $name > 255)
1087
        || ($ports && length $ports > 255)
1088
        || ($type && !($type =~ /gateway|ipmapping|internalip|externalip|remoteip/))
1089
    ) {
1090
        $postreply .= "Stroke=ERROR Bad data: $name, $ports, $type\n";
1091
        return $postreply;
1092
    }
1093
    # Security check
1094
    if (($reguser && $username ne $reguser && !$isadmin ) ||
1095
        ($reguser && $status eq "new"))
1096
    {
1097
        $postreply .= "Status=Error Bad user: $username ($status)\n";
1098
        return $postreply;
1099
    }
1100
    # Check if remoteip is enabled
1101
    if ($type eq 'remoteip' && !$Stabile::remoteipenabled) {
1102
        $postreply .= "Status=Error remoteip is not enabled on this engine\n";
1103
        return $postreply;
1104
    }
1105
    my $hit = 0;
1106
# Check if user is allowed to use network
1107
    my @regvalues = values %register;
1108
    foreach my $val (@regvalues) {
1109
        $dbid = $val->{"id"};
1110
        $dbuser = $val->{"user"};
1111
        if ($dbid == $id && $username ne $dbuser && $dbuser ne "common") {
1112
            $hit = 1;
1113
            last;
1114
        }
1115
    }
1116
    if ($hit && !$isadmin) { # Network is nogo (unless you are an admin)
1117
        $postreply .= "Status=ERROR Network id $id not available\n";
1118
        return $postreply;
1119
    } elsif (!$type) {
1120
        $postreply .= "Status=ERROR Network must have a type\n";
1121
        return $postreply;
1122
    } elsif ($status eq 'down' || $status eq 'new' || $status eq 'nat') {
1123
        # Check if network has been modified or is new
1124
        if ($regnet->{'id'} ne $id ||
1125
            $regnet->{'name'} ne $name ||
1126
            $regnet->{'type'} ne $type ||
1127
            $regnet->{'internalip'} ne $internalip ||
1128
            $regnet->{'externalip'} ne $externalip ||
1129
            $regnet->{'systems'} ne $systems ||
1130
            $regnet->{'ports'} ne $ports)
1131
        {
1132
            if ($type eq "externalip") {
1133
                $internalip = "--";
1134
                $externalip = getNextExternalIP($externalip, $uuid, 1);
1135
                if (!$externalip) {
1136
                    $postreply .= "Status=ERROR Unable to allocate external proxy IP for $name\n";
1137
                    $externalip = "--";
1138
                    $internalip = getNextInternalIP($internalip, $uuid, $id);
1139
                    $type = "internalip";
1140
                } else {
1141
                    $postreply .= "Status=OK Allocated external IP: $externalip UUID: $uuid\n" unless ($regnet->{'externalip'} eq $externalip);
1142
                    if ($dodns) {
1143
                        $main::dnsCreate->($engineid, $externalip, $externalip, 'A', $user);
1144
                    }
1145
                }
1146

    
1147
            } elsif ($type eq "ipmapping") {
1148
                $externalip = getNextExternalIP($externalip, $uuid);
1149
                if (!$externalip) {
1150
                    $postreply .= "Status=ERROR Unable to allocate external IP for $name\n";
1151
                    $externalip = "--";
1152
                    $type = "internalip";
1153
                } else {
1154
                    $postreply .= "Status=OK Allocated external IP: $externalip\n" unless ($regnet->{'externalip'} eq $externalip);
1155
                    if ($dodns) {
1156
                        $postreply .= "Status=OK Trying to register DNS ";
1157
                        $main::dnsCreate->($engineid, $externalip, $externalip, 'A', $user);
1158
                    }
1159
                }
1160
                $internalip = getNextInternalIP($internalip, $uuid, $id);
1161
                if (!$internalip) {
1162
                    $postreply .= "Status=ERROR Unable to allocate internal IP for $name\n";
1163
                    $internalip = "--";
1164
                    $type = "gateway";
1165
                } else {
1166
                    $postreply .= "Status=OK Allocated internal IP: $internalip for $name\n" unless ($regnet->{'internalip'} eq $internalip);
1167
                }
1168

    
1169
            } elsif ($type eq "remoteip") {
1170
                # Check if engine user has been created
1171
                my $uid = `id -u irigo-$Stabile::engineuser`; chomp $uid;
1172
                if (!$uid) {
1173
                    $postreply .= "Status=ERROR Local engine user irigo-$Stabile::engineuser has not been created.\n";
1174
                    $postmsg = "ERROR Local engine user irigo-$Stabile::engineuser has not been created";
1175
                } else {
1176
                    if (!(-e "/home/irigo-$Stabile::engineuser/.ssh/id_rsa.pub")) { # Generate ssh keys if they don't exist
1177
                        `sudo -u irigo-$Stabile::engineuser ssh-keygen -t rsa -b 4096 -N '' -f "/home/irigo-$Stabile::engineuser/.ssh/id_rsa" -C $Stabile::engineuser`;
1178
                        my $pubkey = `cat "/home/irigo-$Stabile::engineuser/.ssh/id_rsa.pub"`;
1179
                        chomp $pubkey;
1180
                        # Upload public key to origo registry
1181
                        $postreply .= $main::postToOrigo->($engineid, 'uploadpubkey', $pubkey, 'pubkey');
1182
                    }
1183
                    $internalip = getNextInternalIP($internalip, $uuid, $id);
1184
                    if (!$internalip) {
1185
                        $postreply .= "Status=ERROR Unable to allocate internal IP for $name\n";
1186
                        $internalip = "--";
1187
                        $type = "gateway";
1188
                    } else {
1189
                        $postreply .= "Status=OK Allocated internal IP: $internalip for $name\n" unless ($regnet->{'internalip'} eq $internalip);
1190
                    }
1191
                    $externalip = getNextRemoteIP($internalip) unless ($externalip && $externalip ne '--' && $regnet->{'externalip'} eq $externalip);
1192
                    if (!$externalip) {
1193
                        $postreply .= "Status=ERROR Unable to allocate remote IP $externalip for $name\n";
1194
                        $postmsg = "Unable to allocate remote IP $externalip for $name";
1195
                        $externalip = "--";
1196
                        $type = "internalip";
1197
                    } else {
1198
                        $postreply .= "Status=OK Acquired remote IP: $externalip\n" unless ($regnet->{'externalip'} eq $externalip);
1199
                        if ($dodns) {
1200
                            $postreply .= "Status=OK Trying to register DNS ";
1201
                            $main::dnsCreate->($engineid, $externalip, $externalip, 'A', $user);
1202
                        }
1203
                    }
1204
                }
1205
                $ports = "80,443,10001" if ($ports eq '--' || $ports eq '');
1206

    
1207
            } elsif ($type eq "internalip") {
1208
                $externalip = "--";
1209
                $ports = "--";
1210
                my $ointip = $internalip;
1211
                $internalip = getNextInternalIP($internalip, $uuid, $id);
1212
                if (!$internalip) {
1213
                    $postreply .= "Status=ERROR Unable to allocate internal IP $internalip ($id, $uuid, $ointip) for $name\n";
1214
                    $internalip = "--";
1215
                    $type = "gateway";
1216
                } else {
1217
                    $postreply .= "Status=OK Allocated internal IP: $internalip for $name\n" unless ($regnet->{'internalip'} eq $internalip);
1218
                }
1219

    
1220
            } elsif ($type eq "gateway") {
1221
            #    $internalip = "--";
1222
            #    $externalip = "--";
1223
            #    $ports = "--";
1224
            } else {
1225
                $postreply .= "Status=ERROR Network must have a valid type\n";
1226
                return $postreply;
1227
            }
1228
            # Validate ports
1229
            my @portslist = split(/, ?| /, $ports);
1230
            if ($ports ne "--") {
1231
                foreach my $port (@portslist) {
1232
                    my $p = $port; # Make a copy of var
1233
                    if ($p =~ /(\d+\.\d+\.\d+\.\d+):(\d+)/) {
1234
                        $p = $2;
1235
                    };
1236
                    $p = 0 unless ($p =~ /\d+/);
1237
                    if ($p<1 || $p>65535) {
1238
                        $postreply .= "Status=ERROR Invalid port mapping for $name\n";
1239
                        $postmsg = "Invalid port mapping";
1240
                        if ($type eq "remoteip") {
1241
                            @portslist = (80,443,10001);
1242
                        } else {
1243
                            $ports = "--";
1244
                        }
1245
                        last;
1246
                    }
1247
                }
1248
            }
1249
            if ($ports ne "--") {
1250
                $ports = join(',', @portslist);
1251
            }
1252
            if ($systems ne $regnet->{'systems'}) {
1253
                my $regsystems = $regnet->{'systems'};
1254
                unless (tie(%sysreg,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access systems register"}|; return $res;};
1255

    
1256
                # Remove existing link to system
1257
                if ($sysreg{$regsystems}) {
1258
                    $sysreg{$regsystems}->{'networkuuids'} =~ s/$uuid,? ?//;
1259
                    $sysreg{$regsystems}->{'networknames'} = s/$regnet->{'name'},? ?//;
1260
                } elsif ($domreg{$regsystems}) {
1261
                    $domreg{$regsystems}->{'networkuuids'} =~ s/$uuid,? ?//;
1262
                    $domreg{$regsystems}->{'networknames'} = s/$regnet->{'name'},? ?//;
1263
                }
1264
                if ($systems) {
1265
                    if ($sysreg{$systems}) { # Add new link to system
1266
                        $sysreg{$systems}->{'networkuuids'} .= (($sysreg{$systems}->{'networkuuids'}) ? ',' : '') . $uuid;
1267
                        $sysreg{$systems}->{'networknames'} .= (($sysreg{$systems}->{'networknames'}) ? ',' : '') . $name;
1268
                        $systemnames = $sysreg{$systems}->{'name'};
1269
                    } elsif ($domreg{$systems}) {
1270
                        $domreg{$systems}->{'networkuuids'} .= (($domreg{$systems}->{'networkuuids'}) ? ',' : '') . $uuid;
1271
                        $domreg{$systems}->{'networknames'} .= (($domreg{$systems}->{'networknames'}) ? ',' : '') . $name;
1272
                        $systemnames = $domreg{$systems}->{'name'};
1273
                    } else {
1274
                        $systems = '';
1275
                    }
1276
                }
1277
                tied(%sysreg)->commit;
1278
                untie(%sysreg);
1279
            }
1280

    
1281
            $register{$uuid} = {
1282
                uuid=>$uuid,
1283
                user=>$username,
1284
                id=>$id,
1285
                name=>$name,
1286
                internalip=>$internalip,
1287
                externalip=>$externalip,
1288
                ports=>$ports,
1289
                type=>$type,
1290
                systems=>$systems,
1291
                systemnames=>$systemnames,
1292
                action=>""
1293
            };
1294
            my $res = tied(%register)->commit;
1295
            my $obj = $register{$uuid};
1296
            $postreply .= "Status=OK Network $register{$uuid}->{'name'} saved: $uuid\n";
1297
            $postreply .= "Status=OK uuid: $uuid\n" if ($console && $status eq 'new');
1298
            if ($status eq 'new') {
1299
                validateStatus($register{$uuid});
1300
                $postmsg = "Created connection $name";
1301
                $uiupdatetype = "update";
1302
            }
1303
            updateBilling("allocate $externalip") if (($type eq "ipmapping" || $type eq "externalip" || $type eq "remoteip") && $externalip && $externalip ne "--");
1304

    
1305
        } else {
1306
        	$postreply = "Status=OK Network $uuid ($id) unchanged\n";
1307
        }
1308

    
1309
        if ($params{'PUTDATA'}) {
1310
            my %jitem = %{$register{$uuid}};
1311
            my $json_text = to_json(\%jitem);
1312
            $json_text =~ s/null/"--"/g;
1313
            $json_text =~ s/""/"--"/g;
1314
            $postreply = $json_text;
1315
            $postmsg = $postmsg || "OK, updated network $name";
1316
        }
1317
        return $postreply;
1318

    
1319
    } else {
1320
        $internalip = '--' unless ($internalip);
1321
        $externalip = '--' unless ($externalip);
1322
        if ($id ne $regnet->{'id'} ||
1323
        $internalip ne $regnet->{'internalip'} || $externalip ne $regnet->{'externalip'}) {
1324
            return "Status=ERROR Cannot modify active network: $uuid\n";
1325
        } elsif ($name ne $regnet->{'name'}) {
1326
            $register{$uuid}->{'name'} = $name;
1327
            $postreply .= "Status=OK Network \"$register{$uuid}->{'name'}\" saved: $uuid\n";
1328
            if ($params{'PUTDATA'}) {
1329
                my %jitem = %{$register{$uuid}};
1330
                my $json_text = to_json(\%jitem);
1331
                $json_text =~ s/null/"--"/g;
1332
                $postreply = $json_text;
1333
                $postmsg = "OK, updated network $name";
1334
            }
1335
        } else {
1336
            $postreply .= "Status=OK Nothing to save\n";
1337
            if ($params{'PUTDATA'}) {
1338
                my %jitem = %{$register{$uuid}};
1339
                my $json_text = to_json(\%jitem);
1340
                $json_text =~ s/null/"--"/g;
1341
                $postreply = $json_text;
1342
            }
1343
        }
1344
    }
1345
    return $postreply;
1346
}
1347

    
1348
sub Activate {
1349
    my ($uuid, $action, $obj) = @_;
1350
    if ($help) {
1351
        return <<END
1352
GET:uuid:
1353
Activate a network which must be in status down or nat.
1354
END
1355
    }
1356
    $uuid = $obj->{'uuid'} if ($obj->{'uuid'});
1357
    $action = 'activate' || $action;
1358
    my $regnet = $register{$uuid};
1359
    my $id = $regnet->{'id'};
1360
    my $name = $regnet->{'name'};
1361
    my $type = $regnet->{'type'};
1362
    my $status = $regnet->{'status'};
1363
    my $domains = $regnet->{'domains'};
1364
    my $systems = $regnet->{'systems'};
1365
    my $internalip = $regnet->{'internalip'};
1366
    my $externalip = $regnet->{'externalip'};
1367
    my $ports = $regnet->{'ports'};
1368
    my $idleft = ($id>99)?(substr $id,0,-2)+0 : 0;
1369
    my $idright = (substr $id,-2) + 0;
1370
    my $interfaces = `/sbin/ifconfig`;
1371
    my $dom = $domreg{$domains};
1372
    my $nicindex = ($dom->{'networkuuid1'} eq $uuid)?1:
1373
            ($dom->{'networkuuid2'} eq $uuid)?2:
1374
            ($dom->{'networkuuid3'} eq $uuid)?3:
1375
            0;
1376
    my $nicmac = $dom->{"nicmac$nicindex"};
1377
    my $e;
1378

    
1379
    if (!$id || $id==0 || $id==1 || $id>4095) {
1380
        $postreply .= "Status=ERROR Invalid ID activating $type\n";
1381
	    return $postreply;
1382
	} elsif (overQuotas()) { # Enforce quotas
1383
        $postreply .= "Status=ERROR Over quota activating $type " . overQuotas() . "\n";
1384
        return $postreply;
1385
    } elsif (($status ne 'down' && $status ne 'nat')) {
1386
        $postreply .= "Status=ERROR Cannot activate $type $name (current status is: $status)\n";
1387
        return $postreply;
1388
    }
1389

    
1390
    # Check if vlan with $id is created and doing nat, if not create it and create the gateway
1391
    unless (-e "/proc/net/vlan/$datanic.$id") {
1392
        eval {`/sbin/vconfig add $datanic $id`;}; # or do {$e=1; $postreply .= "Status=ERROR Problem adding vlan $datanic.$id $@\n"; return $postreply;};
1393
        eval {`/sbin/ifconfig $datanic.$id up`;}; # or do {$e=1; $postreply .= "Status=ERROR Problem activating vlan $datanic.$id $@\n"; return $postreply;};
1394
    }
1395
#    if (!($interfaces =~ m/$datanic\.$id /)) {
1396
    if (!($interfaces =~ m/br$id /)) {
1397
        # check if gw is created locally
1398
        unless (`arping -C1 -c2 -D -I $datanic.$id 10.$idleft.$idright.1` =~ /reply from/) { # check if gw is created on another engine
1399
            # Create gw
1400
#            eval {`/sbin/ifconfig $datanic.$id 10.$idleft.$idright.1 netmask 255.255.255.0 broadcast 10.$idleft.$idright.255 up`; 1;} or do {
1401
#                $e=1; $postreply .= "Status=ERROR $@\n"; return $postreply;
1402
            #            };
1403
            # To support local instances on valve, gw is now created as a bridge
1404
            eval {`/sbin/brctl addbr br$id`; 1;} or do {$e=1; $postreply .= "Status=ERROR $@\n"; return $postreply; };
1405
            eval {`/sbin/brctl addif br$id $datanic.$id`; 1;} or do {$e=1; $postreply .= "Status=ERROR $@\n"; return $postreply; };
1406
            eval {`/sbin/ifconfig br$id 10.$idleft.$idright.1/24 up`; 1;} or do {
1407
                $e=1; $postreply .= "Status=ERROR $@\n"; return $postreply; }
1408
        } else {
1409
            $postreply .= "Status=OK GW is active on another Engine, assuming this is OK\n";
1410
        }
1411
    }
1412
    my $astatus = "nat" unless ($e);
1413
    `/usr/bin/touch $etcpath/dhcp-hosts-$id` unless (-e "$etcpath/dhcp-hosts-$id");
1414
    if ($action eq "activate") { #} && $domains) {
1415
        if ($type eq "internalip" || $type eq "ipmapping" || $type eq "remoteip") {
1416
            # Configure internal dhcp server
1417
            if ($domains) {
1418
                my $result = addDHCPAddress($id, $domains, $internalip, "10.$idleft.$idright.1", $nicmac);
1419
                if ($result eq "OK") {
1420
                    $astatus = "up" if ($type eq "internalip");
1421
                } else {
1422
                    $e = 1;
1423
                    $postreply .= "$result\n";
1424
                }
1425
            }
1426

    
1427
            # Also export storage pools to user's network
1428
            my @spl = split(/,\s*/, $storagepools);
1429
            my $reloadnfs;
1430
            my $uid = `id -u irigo-$user`; chomp $uid;
1431
            $uid = `id -u nobody` unless ($uid =~ /\d+/); chomp $uid;
1432
            my $gid = `id -g irigo-$user`; chomp $gid;
1433
            $gid = `id -g nobody` unless ($gid =~ /\d+/); chomp $gid;
1434

    
1435
            # We are dealing with multiple upstream routes - configure local routing
1436
            if ($proxynic && $proxynic ne $extnic) {
1437
                if (-e "/etc/iproute2/rt_tables" && !grep(/1 proxyarp/, `cat /etc/iproute2/rt_tables`)) {
1438
                    `/bin/echo "1 proxyarp" >> /etc/iproute2/rt_tables`;
1439
                }
1440
                if (!grep(/$datanic\.$id/, `/sbin/ip route show table proxyarp`)) {
1441
                    `/sbin/ip route add "10.$idleft.$idright.0/24" dev $datanic.$id table proxyarp`;
1442
                }
1443
            }
1444

    
1445
            # Manuipulate NFS exports and related disk quotas.
1446
            # Not needed for externalip's since they dont have access to the internal 10.x.x.x address space
1447
            foreach my $p (@spl) {
1448
                if ($tenderlist[$p] && $tenderpathslist[$p]) {
1449
                    my $fuelpath = $tenderpathslist[$p] . "/$user/fuel";
1450
                    unless (-e $fuelpath) {
1451
                        if ($tenderlist[$p] eq 'local') { # We only support fuel on local tender for now
1452
                            `mkdir "$fuelpath"`;
1453
                            `chmod 777 "$fuelpath"`;
1454
                        }
1455
                    }
1456
                    if ($tenderlist[$p] eq "local") {
1457
                        `chown irigo-$user:irigo-$user "$fuelpath"`;
1458
                        my $mpoint = `df -P "$fuelpath" | tail -1 | cut -d' ' -f 1`;
1459
                        chomp $mpoint;
1460
                        my $storagequota = $Stabile::userstoragequota;
1461
                        if (!$storagequota) {
1462
                            $storagequota = $Stabile::config->get('STORAGE_QUOTA');
1463
                        }
1464
                        my $nfsquota = $storagequota * 1024 ; # quota is in MB
1465
                        $nfsquota = 0 if ($nfsquota < 0); # quota of -1 means no limit
1466
                        `setquota -u irigo-$user $nfsquota $nfsquota 0 0 "$mpoint"` if (-e "$mntpoint");
1467
                        if (!(`grep "$fuelpath 10\.$idleft\.$idright" /etc/exports`) && -e $fuelpath) {
1468
                            `echo "$fuelpath 10.$idleft.$idright.0/255.255.255.0(sync,no_subtree_check,all_squash,rw,anonuid=$uid,anongid=$gid)" >> /etc/exports`;
1469
                            $reloadnfs = 1;
1470
                        }
1471
                    }
1472
                }
1473
            }
1474
            `/usr/sbin/exportfs -r` if ($reloadnfs); #Reexport nfs shares
1475

    
1476
        } elsif ($type eq "externalip") {
1477
            # A proxy is needed to route traffic, don't go any further if not configured
1478
            if ($proxyip) {
1479
                # Set up proxy
1480
                if (!($interfaces =~ m/$proxyip/ && $interfaces =~ m/br$id:proxy/)) {
1481
                    eval {`/sbin/ifconfig br$id:proxy $proxyip/$proxysubnet up`; 1;}
1482
                        or do {$e=1; $postreply .= "Status=ERROR Problem setting up proxy arp gw $proxyip on br$id:proxy $@\n";};
1483
                    eval {`/sbin/ifconfig $proxynic:proxy $proxyip/$proxysubnet up`; 1;}
1484
                        or do {$e=1; $postreply .= "Status=ERROR Problem setting up proxy arp gw $proxynic $@\n";};
1485
                }
1486
                my $result = "OK";
1487
                # Configure dhcp server
1488
                if ($domains) {
1489
                    $result = addDHCPAddress($id, $domains, $externalip, "10.$idleft.$idright.1", $nicmac) if ($domains);
1490
                    if ($result eq "OK") {
1491
                        ;
1492
                    } else {
1493
                        $e = 1;
1494
                        $postreply .= "$result\n";
1495
                    }
1496
                }
1497
            } else {
1498
                $postreply .= "Status=ERROR Cannot set up external IP without Proxy ARP gateway\n";
1499
            }
1500
        }
1501

    
1502
        # Handle routing with Iptables
1503
        if ($type eq "ipmapping" || $type eq "internalip" || $type eq "remoteip") {
1504
            `iptables -I FORWARD -d $internalip -m state --state ESTABLISHED,RELATED -j RETURN`;
1505
        }
1506
        # Check if external ip exists and routing configured, if not create and configure it
1507
        if ($type eq "ipmapping") {
1508
            if ($internalip && $internalip ne "--" && $externalip && $externalip ne "--" && !($interfaces =~ m/$externalip /g)) { # the space is important
1509
                $externalip =~ /\d+\.\d+\.(\d+)\.(\d+)/;
1510
                my $ipend = "$1$2"; # Linux NIC names are limited to 15 chars - we will have to find a way to support long NIC names and bigger than /24 subnets
1511
                $ipend = $2 if (length("$extnic:$id-$ipend")>15);
1512
                eval {`/sbin/ifconfig $extnic:$id-$ipend $externalip/$extsubnet up`; 1;}
1513
                    or do {$e=1; $postreply .= "Status=ERROR Problem adding interface $extnic:$id-$ipend $@\n";};
1514
                unless (`ip addr show dev $extnic` =~ /$externalip/) {
1515
                    $e=10;
1516
                    $postreply .= "Status=ERROR Problem adding interface $extnic:$id-$ipend\n";
1517
                }
1518
                # `/sbin/iptables -A POSTROUTING -t nat -s $internalip -j LOG --log-prefix "SNAT-POST"`;
1519
                # `/sbin/iptables -A INPUT -t nat -s $internalip -j LOG --log-prefix "SNAT-INPUT"`;
1520
                # `/sbin/iptables -A OUTPUT -t nat -s $internalip -j LOG --log-prefix "SNAT-OUTPUT"`;
1521
                # `/sbin/iptables -A PREROUTING -t nat -s $internalip -j LOG --log-prefix "SNAT-PRE"`;
1522
                if ($ports && $ports ne "--") { # Port mapping is defined
1523
                    my @portslist = split(/, ?| /, $ports);
1524
                    foreach my $port (@portslist) {
1525
                        my $ipfilter;
1526
                        if ($port =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)(\/\d+)?:(\d+)/) {
1527
                            my $portip = "$1.$2.$3.$4$5";
1528
                            $port = $6;
1529
                            $ipfilter = "-s $portip";
1530
                        } else {
1531
                            $port = 0 unless ($port =~ /\d+/);
1532
                        }
1533
                        if ($port<1 || $port>65535) {
1534
                            $postreply .= "Status=ERROR Invalid port mapping for $name\n";
1535
                            $ports = "--";
1536
                            last;
1537
                        }
1538
                        if ($port>1 || $port<65535) {
1539
                            # DNAT externalip -> internalip
1540
                            eval {`/sbin/iptables -A PREROUTING -t nat -p tcp $ipfilter -d $externalip --dport $port -j DNAT --to $internalip`; 1;}
1541
                               or do {$e=2; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1542
                            eval {`/sbin/iptables -A PREROUTING -t nat -p udp $ipfilter -d $externalip --dport $port -j DNAT --to $internalip`; 1;}
1543
                               or do {$e=3; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1544
                            # PREROUTING is not parsed for packets coming from local host...
1545
                            eval {`/sbin/iptables -A OUTPUT -t nat -p tcp $ipfilter -d $externalip --dport $port -j DNAT --to $internalip`; 1;}
1546
                                or do {$e=2; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1547
                            eval {`/sbin/iptables -A OUTPUT -t nat -p udp $ipfilter -d $externalip --dport $port -j DNAT --to $internalip`; 1;}
1548
                                or do {$e=3; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1549
                            # Allow access to ipmapped internal ip on $port
1550
                            `iptables -I FORWARD -d $internalip -p tcp --dport $port -j RETURN`;
1551
                            `iptables -I FORWARD -d $internalip -p udp --dport $port -j RETURN`;
1552
                        }
1553
                    }
1554
                    eval {`/sbin/iptables -D INPUT -d $externalip -j DROP`; 1;} # Drop traffic to all other ports
1555
                        or do {$e=5; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1556
                    eval {`/sbin/iptables -A INPUT -d $externalip -j DROP`; 1;} # Drop traffic to all other ports
1557
                        or do {$e=6; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1558
                } else {
1559
                    # DNAT externalip -> internalip coming from outside , --in-interface $extnic
1560
                    eval {`/sbin/iptables -A PREROUTING -t nat -d $externalip -j DNAT --to $internalip`; 1;}
1561
                        or do {$e=7; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1562
                    # PREROUTING is not parsed for packets coming from local host...
1563
                    eval {`/sbin/iptables -A OUTPUT -t nat -d $externalip -j DNAT --to $internalip`; 1;}
1564
                        or do {$e=7; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1565
                    # Allow blanket access to ipmapped internal ip
1566
                    `iptables -I FORWARD -d $internalip -j RETURN`;
1567
                }
1568
                # We masquerade packets going to internalip from externalip to avoid confusion
1569
                #eval {`/sbin/iptables -A POSTROUTING -t nat --out-interface br$id -s $externalip -j MASQUERADE`; 1;}
1570
                #    or do {$e=3; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1571

    
1572
                # Masquerade packets from internal ip's not going to our own subnet
1573
                # `/sbin/iptables -D POSTROUTING -t nat --out-interface br$id ! -d 10.$idleft.$idright.0/24 -j MASQUERADE`;
1574
                #eval {`/sbin/iptables -A POSTROUTING -t nat --out-interface br$id ! -d 10.$idleft.$idright.0/24 -j MASQUERADE`; 1;}
1575
                #    or do {$e=3; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1576

    
1577
                # When receiving packet from client, if it's been routed, and outgoing interface is the external interface, SNAT.
1578
                unless ($Stabile::disablesnat) {
1579
                    eval {`/sbin/iptables -A POSTROUTING -t nat -s $internalip ! -d 10.$idleft.$idright.0/24 -j SNAT --to-source $externalip`; 1; }
1580
                        or do {$e=4; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1581
                #    eval {`/sbin/iptables -A POSTROUTING -t nat -s $internalip -j SNAT --to-source $externalip`; 1; }
1582
                #        or do {$e=4; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1583
                    eval {`/sbin/iptables -I INPUT -t nat -s $internalip ! -d 10.$idleft.$idright.0/24 -j SNAT --to-source $externalip`; 1; }
1584
                        or do {$e=4; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1585
                #    eval {`/sbin/iptables -I INPUT -t nat -s $internalip -j SNAT --to-source $externalip`; 1; }
1586
                #        or do {$e=4; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1587
                }
1588
                if ($e) {
1589
                    $main::syslogit->($user, 'info', "Problem $action network $uuid ($name, $id): $@");
1590
                } else {
1591
                    $astatus = "up"
1592
                }
1593
            }
1594
        } elsif ($type eq "remoteip") {
1595
            if ($Stabile::remoteipenabled && -e "/home/irigo-$Stabile::engineuser/.ssh/id_rsa") {
1596
                # First activate the ip on remoteipprovider
1597
                my $res = $main::postToOrigo->($engineid, 'activateremoteip', "$externalip:$internalip", 'remotelocalip');
1598
                my $res_obj = JSON::from_json($res);
1599
                my $pid = '--';
1600
                my @remoteports = (80, 443, 10001);
1601
                my $rports;
1602
                if ($ports && $ports ne "--") {
1603
                    # Port mapping is defined
1604
                    my @portslist = split(/, ?| /, $ports);
1605
                    @remoteports = ();
1606
                    foreach my $port (@portslist) {
1607
                        if ($port =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)(\/\d+)?:(\d+)/) {
1608
                            my $portip = "$1.$2.$3.$4$5";
1609
                            $port = $6;
1610
                        } else {
1611
                            $port = 0 unless ($port =~ /\d+/);
1612
                        }
1613
                        if ($port < 1 || $port > 65535) {
1614
                            $postreply .= "Status=ERROR Invalid port mapping for $name\n";
1615
                            $ports = "--";
1616
                            last;
1617
                        }
1618
                        if ($port > 1 || $port < 65535) {
1619
                            push @remoteports, $port;
1620
                        }
1621
                    }
1622
                }
1623
                foreach my $port (@remoteports) {
1624
                    $rports .= "-R $externalip:$port:$internalip:$port ";
1625
                }
1626
                if ($res_obj->{status} eq 'OK') {
1627
#                    my $cmd = qq|ssh -fN -i /home/irigo-$Stabile::engineuser/.ssh/id_rsa -o "StrictHostKeyChecking=no" -o "UserKnownHostsFile=/dev/null" -o "ExitOnForwardFailure=yes" -R $externalip:10001:$internalip:10001 -R $externalip:80:$internalip:80 -R $externalip:443:$internalip:443 $Stabile::remoteipprovider|;
1628
                    my $cmd = qq|ssh -fN -i /home/irigo-$Stabile::engineuser/.ssh/id_rsa -o "StrictHostKeyChecking=no" -o "UserKnownHostsFile=/dev/null" -o "ExitOnForwardFailure=yes" $rports $Stabile::remoteipprovider|;
1629
                    eval {
1630
                        my $daemon = Proc::Daemon->new(
1631
                            work_dir => '/home/irigo-o@origo.io',
1632
                            exec_command => "$cmd"
1633
                        ) or do {$postreply .= "Status=ERROR $@";};
1634
                        $pid = $daemon->Init();
1635
                        $main::syslogit->($user, "info", "Activating remote ip $externalip at $Stabile::remoteipprovider for $Stabile::engineuser, pid=$pid");
1636
                        1;
1637
                    } or do {$e=4; $postreply .= "Status=ERROR Problem activating remote ip $@\n";};
1638
#                    sleep 1;
1639
                } else {
1640
                    $postreply .= "Status=Error $res_obj->{message}\n";
1641
                }
1642
                if ($e || !(-e "/proc/$pid")) {
1643
                    $main::syslogit->($user, 'info', "Problem $action network $uuid ($e, $name, $id): $@");
1644
                    $astatus = $status;
1645
                    $postreply .= "Status=OK Waiting to establish remote connetion\n";
1646
                } else {
1647
                    $astatus = "up"
1648
                }
1649
            }
1650
        } elsif ($type eq "externalip") {
1651
            my $route = `/sbin/ip route`;
1652
            my $tables = `/sbin/iptables -L -n`;
1653

    
1654
            # Allow external IP send packets out
1655
            `/sbin/iptables -D FORWARD --in-interface br$id -s $externalip -j RETURN`;
1656
            `/sbin/iptables -I FORWARD --in-interface br$id -s $externalip -j RETURN`;
1657

    
1658
            # We are dealing with multiple upstream routes - configure local routing
1659
            if ($proxynic && ($proxynic ne $extnic)) {
1660
                if (-e "/etc/iproute2/rt_tables" && !grep(/1 proxyarp/, `cat /etc/iproute2/rt_tables`)) {
1661
                    `/bin/echo "1 proxyarp" >> /etc/iproute2/rt_tables`;
1662
                }
1663
                if (!grep(/$proxygw/, `/sbin/ip route show table proxyarp`)) {
1664
                    `/sbin/ip route del default dev $proxynic table proxyarp`; # delete first in case proxygw has changed
1665
                    `/sbin/ip route add default via $proxygw dev $proxynic table proxyarp`;
1666
                }
1667
                if (!grep(/proxyarp/, `/sbin/ip rule show`)) {
1668
                    `/sbin/ip rule add to $proxygw/$proxysubnet table main`;
1669
                    `/sbin/ip rule add from $proxygw/$proxysubnet table proxyarp`;
1670
                }
1671
                my $proxyroute = `/sbin/ip route show table proxyarp`;
1672
#                `/sbin/ip route add $externalip/32 dev $datanic.$id:proxy src $proxyip table proxyarp` unless ($proxyroute =~ /$externalip/);
1673
                `/sbin/ip route add $externalip/32 dev br$id:proxy src $proxyip table proxyarp` unless ($proxyroute =~ /$externalip/);
1674
            }
1675
            eval {`/bin/echo 1 > /proc/sys/net/ipv4/conf/$datanic.$id/proxy_arp`; 1;}
1676
                or do {$e=1; $postreply .= "Status=ERROR Problem setting up proxy arp $@\n";};
1677
            eval {`/bin/echo 1 > /proc/sys/net/ipv4/conf/$proxynic/proxy_arp`; 1;}
1678
                or do {$e=1; $postreply .= "Status=ERROR Problem setting up proxy arp $@\n";};
1679
            eval {`/sbin/ip route add $externalip/32 dev br$id:proxy src $proxyip` unless ($route =~ /$externalip/); 1;}
1680
                or do {$e=1; $postreply .= "Status=ERROR Problem setting up proxy arp $@\n";};
1681

    
1682
            eval {`/sbin/iptables -D FORWARD -i $proxynic -d $externalip -m state --state ESTABLISHED,RELATED -j RETURN`; 1;}
1683
                or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1684
            eval {`/sbin/iptables -A FORWARD -i $proxynic -d $externalip -m state --state ESTABLISHED,RELATED -j RETURN`; 1;}
1685
                or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1686

    
1687

    
1688
            eval {`/sbin/iptables -D FORWARD -i $proxynic -d $externalip -j REJECT` if
1689
                ($tables =~ /REJECT .+ all .+ $externalip/); 1;}
1690
                or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1691

    
1692
            if ($ports && $ports ne "--") {
1693
                my @portslist = split(/, ?| /, $ports);
1694
                foreach $port (@portslist) {
1695
                    my $ipfilter;
1696
                    if ($port =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)(\/\d+)?:(\d+)/) {
1697
                        my $portip = "$1.$2.$3.$4$5";
1698
                        $port = $6;
1699
                        $ipfilter = "-s $portip";
1700
                    } else {
1701
                        $port = 0 unless ($port =~ /\d+/);
1702
                    }
1703
                    if ($port<1 || $port>65535) {
1704
                        $postreply .= "Status=ERROR Invalid port mapping for $name\n";
1705
                        $ports = "--";
1706
                        last;
1707
                    }
1708

    
1709
                    if ($port>1 && $port<65535 && $port!=67) { # Disallow setting up a dhcp server
1710
                        eval {`/sbin/iptables -A FORWARD -p tcp -i $proxynic $portfilter -d $externalip --dport $port -j RETURN`; 1;}
1711
                            or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1712
                        eval {`/sbin/iptables -A FORWARD -p udp -i $proxynic $portfilter -d $externalip --dport $port -j RETURN`; 1;}
1713
                            or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1714
                    }
1715
                }
1716
                eval {`/sbin/iptables -D FORWARD -i $proxynic -d $externalip -j REJECT`; 1;} # Drop traffic to all other ports
1717
                    or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1718
                eval {`/sbin/iptables -A FORWARD -i $proxynic -d $externalip -j REJECT`; 1;} # Drop traffic to all other ports
1719
                    or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1720
            } else {
1721
                # First allow everything else to this ip
1722
                eval {`/sbin/iptables -D FORWARD -i $proxynic -d $externalip -j RETURN`; 1;}
1723
                    or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1724
                eval {`/sbin/iptables -A FORWARD -i $proxynic -d $externalip -j RETURN`; 1;}
1725
                    or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1726
                # Then disallow setting up a dhcp server
1727
                eval {`/sbin/iptables -D FORWARD -p udp -i $proxynic -d $externalip --dport 67 -j REJECT`; 1;}
1728
                    or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1729
                eval {`/sbin/iptables -A FORWARD -p udp -i $proxynic -d $externalip --dport 67 -j REJECT`; 1;}
1730
                    or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1731
            }
1732
        }
1733
    }
1734

    
1735
    # Allow all inter-VLAN communication
1736
    `iptables -D FORWARD --in-interface br$id --out-interface br$id -j RETURN 2>/dev/null`;
1737
    `iptables -I FORWARD --in-interface br$id --out-interface br$id -j RETURN`;
1738
    # Disallow any access to vlan except mapped from external NIC i.e. ipmappings
1739
    `iptables -D FORWARD ! --in-interface $extnic --out-interface br$id -j DROP 2>/dev/null`;
1740
    `iptables -A FORWARD ! --in-interface $extnic --out-interface br$id -j DROP`;
1741

    
1742
    # Only forward packets coming from subnet assigned to vlan unless we are setting up a gateway on the proxy nic and the proxy nic is on a vlan
1743
#    `/sbin/iptables --delete FORWARD --in-interface $datanic.$id ! -s 10.$idleft.$idright.0/24 -j DROP`;
1744
    unless ($proxynic eq "$datanic.$id") {
1745
#        `/sbin/iptables --append FORWARD --in-interface $datanic.$id ! -s 10.$idleft.$idright.0/24 -j DROP`;
1746
    }
1747

    
1748
    # Enable nat'ing
1749
    eval {
1750
        #my $masq = `/sbin/iptables -L -n -t nat`;
1751
        #        if (!($masq =~ "MASQUERADE.+all.+--.+0\.0\.0\.0/0")) {
1752
        `/sbin/iptables -D POSTROUTING -t nat --out-interface $extnic -s 10.0.0.0/8 -j MASQUERADE`;
1753
        `/sbin/iptables -A POSTROUTING -t nat --out-interface $extnic -s 10.0.0.0/8 -j MASQUERADE`;
1754
        # Christian's dev environment
1755
        #            my $interfaces = `/sbin/ifconfig`;
1756
        #            if ($interfaces =~ m/ppp0/) {
1757
        #                `/sbin/iptables --table nat --append POSTROUTING --out-interface ppp0 -s 10.0.0.0/8 -j MASQUERADE`;
1758
        #            }
1759
        #        };
1760
        1;
1761
    } or do {print "Unable to enable masquerading: $@\n";};
1762

    
1763
    $uistatus = ($e)?"":validateStatus($register{$uuid});
1764
    if ($uistatus && $uistatus ne 'down' # && $uistatus ne 'nat'
1765
        ) {
1766
        $uiuuid = $uuid;
1767
        $postreply .= "Status=$uistatus OK $action $type $name\n";
1768
    } else {
1769
        $postreply .= "Status=ERROR Cannot $action $type $name ($uistatus)\n";
1770
    }
1771
    $main::syslogit->($user, 'info', "$action network $uuid ($name, $id) -> $uistatus");
1772
    updateBilling("$uistatus $uuid ($id)");
1773
    # $main::updateUI->({tab=>"networks", user=>$user, uuid=>$uiuuid, status=>$uistatus}) if ($uistatus);
1774
    return $postreply;
1775
}
1776

    
1777
sub Removeusernetworks {
1778
    my $username = shift;
1779
    return unless (($isadmin || $user eq $username) && !$isreadonly);
1780
    $user = $username;
1781
    foreach my $uuid (keys %register) {
1782
        if ($register{$uuid}->{'user'} eq $user) {
1783
            $postreply .=  "Removing network $register{$uuid}->{'name'}, $uuid" . ($console?'':'<br>') . "\n";
1784
            Deactivate($uuid);
1785
            Remove($uuid, 'remove');
1786
        }
1787
    }
1788
}
1789

    
1790
sub Remove {
1791
    my ($uuid, $action, $obj) = @_;
1792
    if ($help) {
1793
        return <<END
1794
DELETE:uuid,force:
1795
Delete a network which must be in status down or nat and should not be used by any servers, or linked to any stacks.
1796
May also be called with endpoints "/stabile/[uuid]" or "/stabile?uuid=[uuid]"
1797
Set [force] to remove even if linked to a system.
1798
END
1799
    }
1800
    $uuid = $obj->{'uuid'} if ($curuuid && $obj->{'uuid'}); # we are called from a VM with an ip address as target
1801
    my $force = $obj->{'force'};
1802
    ( my $domains, my $domainnames ) = getDomains($uuid);
1803
    ( my $systems, my $systemnames ) = getSystems($uuid);
1804

    
1805
    if ($register{$uuid}) {
1806
        my $id = $register{$uuid}->{'id'};
1807
        my $name = $register{$uuid}->{'name'};
1808
        utf8::decode($name);
1809
        my $status = $register{$uuid}->{'status'};
1810
        my $type = $register{$uuid}->{'type'};
1811
        my $internalip = $register{$uuid}->{'internalip'};
1812
        my $externalip = $register{$uuid}->{'externalip'};
1813

    
1814
        my @regvalues = values %register;
1815
        if (
1816
            $id!=0 && $id!=1 && (!$domains || $domains eq '--')
1817
                && ((!$systems || $systems eq '--' || $force)
1818
                # allow internalip's to be removed if active and only linked, i.e. not providing dhcp
1819
                || ($status eq 'down' || $status eq 'new' || $status eq 'nat' || ($type eq 'internalip' && $systems && $systems ne '--')))
1820
        ) {
1821
            # Deconfigure internal dhcp server and DNS
1822
            if ($type eq "internalip") {
1823
                my $result =  removeDHCPAddress($id, $domains, $internalip);
1824
                $postreply .= "$result\n" unless $result eq "OK";
1825
            } elsif ($type eq "ipmapping") {
1826
                my $result =  removeDHCPAddress($id, $domains, $internalip);
1827
                $postreply .= "$result\n" unless $result eq "OK";
1828
                if ($dodns) {
1829
                    $main::dnsDelete->($engineid, $externalip) if ($enginelinked);
1830
                }
1831
            } elsif ($type eq "externalip" || $type eq "remoteip") {
1832
                my $result =  removeDHCPAddress($id, $domains, $externalip);
1833
                $postreply .= "$result\n" unless $result eq "OK";
1834
                if ($dodns) {
1835
                    $main::dnsDelete->($engineid, $externalip) if ($enginelinked);
1836
                }
1837
                # Deactivate the ip on remoteipprovider
1838
                my $res = $main::postToOrigo->($engineid, 'removeremoteip', "$externalip", 'remoteip');
1839
                my $res_obj = JSON::from_json($res);
1840
                if ($res_obj->{status} ne 'OK') {
1841
                    $postreply .= "Status=OK There was a problem removing the remote IP\n";
1842
                }
1843
            }
1844
            if ($status eq 'nat') {
1845
                # Check if last network in vlan. If so take it down
1846
                my $notlast;
1847
                foreach my $val (@regvalues) {
1848
                    if ($val->{'user'} eq $user && $val->{'id'} == $id) {
1849
                        $notlast = 1;
1850
                    }
1851
                }
1852
                if (!$notlast) {
1853
                    eval {`/sbin/ifconfig $datanic.$id down`; 1;} or do {;};
1854
                    eval {`/sbin/vconfig rem $datanic.$id`; 1;} or do {;};
1855
                }
1856
            }
1857

    
1858
            unless (tie(%sysreg,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access systems register"}|; return $res;};
1859
            if ($sysreg{$systems}) { # Remove existing link to system
1860
                $sysreg{$systems}->{'networkuuids'} =~ s/$uuid,?//;
1861
                $sysreg{$systems}->{'networknames'} = s/$name,?//;
1862
            }
1863
            tied(%sysreg)->commit;
1864
            untie(%sysreg);
1865

    
1866

    
1867
            delete $register{$uuid};
1868
            tied(%register)->commit;
1869
            updateBilling("delete $val->{'externalip'}") if ($type eq "ipmapping");
1870
            $main::syslogit->($user, "info", "Deleted network $uuid ($id)");
1871
            $postreply = "[]" || $postreply;
1872
            $main::updateUI->({tab=>"networks", user=>$user, type=>"update"});
1873
        } else {
1874
            $postreply .= "Status=ERROR Cannot remove $uuid which is $status. Cannot delete network 0,1 or a network which is active or in use.\n";
1875
            $main::updateUI->({tab=>"networks", user=>$user, message=>"Cannot remove a network which is active, linked or in use."});
1876
        }
1877
    } else {
1878
        $postreply .= "Status=ERROR Network $uuid $ipaddress not found\n";
1879
    }
1880
    return $postreply;
1881
}
1882

    
1883
sub Deactivate {
1884
    my ($uuid, $action, $obj) = @_;
1885

    
1886
    if ($help) {
1887
        return <<END
1888
GET:uuid:
1889
Deactivate a network which must be in status up.
1890
END
1891
    }
1892
    $uuid = $obj->{'uuid'} if ($obj->{'uuid'});
1893

    
1894
    unless ($register{$uuid}) {
1895
        $postreply .= "Status=ERROR Connection with uuid $uuid not found\n";
1896
        return $postreply;
1897
    }
1898
    my $regnet = $register{$uuid};
1899

    
1900
    $action = $action || 'deactivate';
1901
    ( my $domains, my $domainnames ) = getDomains($uuid);
1902
    my $interfaces = `/sbin/ifconfig`;
1903

    
1904
    my $id = $regnet->{'id'};
1905
    my $name = $regnet->{'name'};
1906
    my $type = $regnet->{'type'};
1907
    my $internalip = $regnet->{'internalip'};
1908
    my $externalip = $regnet->{'externalip'};
1909
    my $ports = $regnet->{'ports'};
1910

    
1911
    if ($id!=0 && $id!=1 && $status ne 'down') {
1912
    # If gateway is created, take it down along with all user's networks
1913
        if ($action eq "stop") {
1914
            my $res = Stop($id, $action);
1915
            if ($res) {
1916
                unlink "$etcpath/dhcp-hosts-$id" if (-e "$etcpath/dhcp-hosts-$id");
1917
            };
1918
        }
1919
    } else {
1920
        $postreply .= "Status=ERROR Cannot $action network $name\n";
1921
        return $postreply;
1922
    }
1923

    
1924
    my $idleft = ($id>99)?(substr $id,0,-2)+0 : 0;
1925
    my $idright = (substr $id,-2) + 0;
1926
    my $e = 0;
1927
    my $duprules = 0;
1928

    
1929
    if ($type eq "ipmapping" || $type eq "internalip" || $type eq "remoteip") {
1930
        `iptables -D FORWARD -d $internalip -m state --state ESTABLISHED,RELATED -j RETURN`;
1931
    }
1932
    if ($type eq "ipmapping") {
1933
        # Check if external ip exists and take it down if so
1934
        if ($internalip && $internalip ne "--" && $externalip && $externalip ne "--" && ($interfaces =~ m/$externalip/g)) {
1935
            $externalip =~ /\d+\.\d+\.(\d+)\.(\d+)/;
1936
            my $ipend = "$1$2"; # Linux NIC names are limited to 15 chars - we will have to find a way to support long NIC names and bigger than /24 subnets
1937
            $ipend = $2 if (length("$extnic:$id-$ipend")>15);
1938
            eval {`/sbin/ifconfig $extnic:$id-$ipend down`; 1;} or do {$e=1; $postreply .= "Status=ERROR $@\n";};
1939

    
1940
            if ($ports && $ports ne "--") { # Port mapping is defined
1941
                my @portslist = split(/, ?| /, $ports);
1942
                foreach my $port (@portslist) {
1943
                    my $ipfilter;
1944
                    if ($port =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)(\/\d+)?:(\d+)/) {
1945
                        my $portip = "$1.$2.$3.$4$5";
1946
                        $port = $6;
1947
                        $ipfilter = "-s $portip";
1948
                    } else {
1949
                        $port = 0 unless ($port =~ /\d+/);
1950
                    }
1951
                    if ($port<1 || $port>65535) {
1952
                        $postreply .= "Status=ERROR Invalid port mapping for $name\n";
1953
                        $ports = "--";
1954
                        last;
1955
                    }
1956
                    # Remove DNAT rules
1957
                    if ($port>1 || $port<65535) {
1958
                        # repeat for good measure
1959
                        for (my $di=0; $di < 10; $di++) {
1960
                            $duprules = 0;
1961
                            eval {$duprules++ if (`/sbin/iptables -D PREROUTING -t nat -p tcp $ipfilter -d $externalip --dport $port -j DNAT --to $internalip`); 1;}
1962
                                or do {$postreply .= "Status=ERROR $@\n"; $e=1};
1963
                            eval {$duprules++ if (`/sbin/iptables -D PREROUTING -t nat -p udp $ipfilter -d $externalip --dport $port -j DNAT --to $internalip`); 1;}
1964
                                or do {$postreply .= "Status=ERROR $@\n"; $e=1};
1965
                            eval {$duprules++ if (`/sbin/iptables -D OUTPUT -t nat -p tcp $ipfilter -d $externalip --dport $port -j DNAT --to $internalip`); 1;}
1966
                                or do {$postreply .= "Status=ERROR $@\n"; $e=1};
1967
                            eval {$duprules++ if (`/sbin/iptables -D OUTPUT -t nat -p udp $ipfilter -d $externalip --dport $port -j DNAT --to $internalip`); 1;}
1968
                                or do {$postreply .= "Status=ERROR $@\n"; $e=1};
1969
                            eval {$duprules++ if (`/sbin/iptables -D POSTROUTING -t nat --out-interface br$id -s $externalip -j MASQUERADE`); 1;}
1970
                                or do {$e=3; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1971
                            # Remove access to ipmapped internal ip on $port
1972
                            eval {$duprules++ if (`/sbin/iptables -D FORWARD -d $internalip -p udp --dport $port -j RETURN`); 1;}
1973
                                or do {$e=3; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1974
                            eval {$duprules++ if (`/sbin/iptables -D FORWARD -d $internalip -p tcp --dport $port -j RETURN`); 1;}
1975
                                or do {$e=3; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1976
                            last if ($duprules >6);
1977
                        }
1978
                    }
1979
                }
1980
                # Remove SNAT rules
1981
                # repeat for good measure
1982
                for (my $di=0; $di < 10; $di++) {
1983
                    $duprules = 0;
1984
                    eval {$duprules++ if (`/sbin/iptables -D POSTROUTING -t nat -s $internalip ! -d 10.$idleft.$idright.0/24 -j SNAT --to-source $externalip`); 1; }
1985
                        or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1986
                    last if ($duprules);
1987
                }
1988
                # Remove rule to drop traffic to all other ports
1989
                eval {`/sbin/iptables -D INPUT -d $externalip -j DROP`; 1;}
1990
                    or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
1991
            } else {
1992
                # Remove DNAT rules
1993
                # repeat for good measure
1994
                for (my $di=0; $di < 10; $di++) {
1995
                    $duprules = 0;
1996
                    eval {$duprules++ if (`/sbin/iptables -D PREROUTING -t nat -d $externalip -j DNAT --to $internalip`); 1;}
1997
                        or do {$postreply .= "Status=ERROR $@\n"; $e=1};
1998
                    eval {$duprules++ if (`/sbin/iptables -D OUTPUT -t nat -d $externalip -j DNAT --to $internalip`); 1;}
1999
                        or do {$postreply .= "Status=ERROR $@\n"; $e=1};
2000
                    last if ($duprules >1);
2001
                }
2002
                # Remove blanket access to ipmapped internal ip
2003
                `iptables -D FORWARD -d $internalip -j RETURN`;
2004
            }
2005
            # Remove SNAT and MASQUERADE rules
2006
            # repeat for good measure
2007
            for (my $di=0; $di < 10; $di++) {
2008
                $duprules = 0;
2009
            #    eval {$duprules++ if (`/sbin/iptables -D POSTROUTING -t nat --out-interface br$id -s $externalip -j MASQUERADE`); 1;}
2010
            #        or do {$e=3; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2011
                eval {$duprules++ if (`/sbin/iptables -D POSTROUTING -t nat --out-interface br$id ! -d 10.$idleft.$idright.0/24 -j MASQUERADE`); 1;}
2012
                    or do {$e=3; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2013

    
2014
                eval {$duprules++ if (`/sbin/iptables -D POSTROUTING -t nat -s $internalip ! -d 10.$idleft.$idright.0/24 -j SNAT --to-source $externalip`); 1; }
2015
                    or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2016
            #    eval {$duprules++ if (`/sbin/iptables -D POSTROUTING -t nat -s $internalip -j SNAT --to-source $externalip`); 1; }
2017
            #        or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2018
                eval {$duprules++ if (`/sbin/iptables -D INPUT -t nat -s $internalip ! -d 10.$idleft.$idright.0/24 -j SNAT --to-source $externalip`); 1; }
2019
                    or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2020
            #    eval {$duprules++ if (`/sbin/iptables -D INPUT -t nat -s $internalip -j SNAT --to-source $externalip`); 1; }
2021
            #        or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2022
            #    eval {$duprules++ if (`/sbin/iptables -D INPUT -t nat -s $internalip ! -d 10.$idleft.$idright.0/24 -j SNAT --to-source $externalip`); 1; }
2023
            #        or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2024
            #    eval {$duprules++ if (`/sbin/iptables -D INPUT -t nat -s $internalip -j SNAT --to-source $externalip`); 1; }
2025
            #        or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2026
                last if ($duprules >1);
2027
            }
2028
            # `/sbin/iptables -D POSTROUTING -t nat -s $internalip -j LOG --log-prefix "SNAT-POST"`;
2029
            # `/sbin/iptables -D INPUT -t nat -s $internalip -j LOG --log-prefix "SNAT-INPUT"`;
2030
            # `/sbin/iptables -D OUTPUT -t nat -s $internalip -j LOG --log-prefix "SNAT-OUTPUT"`;
2031
            # `/sbin/iptables -D PREROUTING -t nat -s $internalip -j LOG --log-prefix "SNAT-PRE"`;
2032
        }
2033
    } elsif ($type eq "remoteip") {
2034
        `pkill -f 'R $externalip'`;
2035
        # Deactivate the ip on remoteipprovider
2036
        my $res = $main::postToOrigo->($engineid, 'deactivateremoteip', "$externalip", 'remoteip');
2037
        my $res_obj = JSON::from_json($res);
2038
        if ($res_obj->{status} ne 'OK') {
2039
            $postreply .= "Status=OK There was a problem deactivating the remote IP\n";
2040
        }
2041
    } elsif ($type eq "externalip") {
2042
        if ($externalip && $externalip ne "--") {
2043
            # We are dealing with multiple upstream routes - configure local routing
2044
            if ($proxynic && $proxynic ne $extnic) {
2045
                my $proxyroute = `/sbin/ip route show table proxyarp`;
2046
                `/sbin/ip route del $externalip/32 dev br$id:proxy src $proxyip table proxyarp` if ($proxyroute =~ /$externalip/);
2047
            }
2048

    
2049
            eval {`/sbin/ip route del $externalip/32 dev br$id:proxy`; 1;}
2050
                or do {$e=1; $postreply .= "Status=ERROR Problem deconfiguring proxy arp $@\n";};
2051

    
2052
            if ($ports && $ports ne "--") {
2053
                my @portslist = split(/, ?| /, $ports);
2054
                foreach my $port (@portslist) {
2055
                    my $ipfilter;
2056
                    if ($port =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)(\/\d+)?:(\d+)/) {
2057
                        my $portip = "$1.$2.$3.$4$5";
2058
                        $port = $6;
2059
                        $ipfilter = "-s $portip";
2060
                    } else {
2061
                        $port = 0 unless ($port =~ /\d+/);
2062
                    }
2063
                    if ($port<1 || $port>65535) {
2064
                        $postreply .= "Status=ERROR Invalid port mapping for $name\n";
2065
                        $ports = "--";
2066
                        last;
2067
                    }
2068

    
2069
                    if ($port>1 || $port<65535) {
2070
                        # repeat for good measure
2071
                        for (my $di=0; $di < 10; $di++) {
2072
                            $duprules = 0;
2073
                            eval {$duprules++ if (`/sbin/iptables -D FORWARD -p tcp -i $proxynic $ipfilter -d $externalip --dport $port -j RETURN`); 1;}
2074
                                or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2075
                            eval {$duprules++ if (`/sbin/iptables -D FORWARD -p udp -i $proxynic $ipfilter -d $externalip --dport $port -j RETURN`); 1;}
2076
                                or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2077
                            last if ($duprules > 1);
2078
                        }
2079
                    }
2080
                }
2081
            }
2082
            # Remove rule to allow forwarding from $externalip
2083
	        `/sbin/iptables --delete FORWARD --in-interface br$id -s $externalip -j RETURN`;
2084
            # Remove rule to disallow setting up a dhcp server
2085
            eval {`/sbin/iptables -D FORWARD -p udp -i $proxynic -d $externalip --dport 67 -j REJECT`; 1;}
2086
                or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2087
            # Leave outgoing connectivity - not
2088
            eval {`/sbin/iptables -D FORWARD -i $proxynic -d $externalip -m state --state ESTABLISHED,RELATED -j RETURN`; 1;}
2089
                or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2090
            eval {`/sbin/iptables -D FORWARD -i $proxynic -d $externalip -j RETURN`; 1;}
2091
                or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2092
            # No need to reject - we reject all per default to the subnet
2093
            eval {`/sbin/iptables -D FORWARD -i $proxynic -d $externalip -j REJECT`; 1;}
2094
                or do {$e=1; $postreply .= "Status=ERROR Problem setting up routing $@\n";};
2095
        }
2096
    }
2097
    # Deconfigure internal dhcp server
2098
    if ($type eq "internalip" || $type eq "ipmapping" || $type eq "remoteip") {
2099
        my $result =  removeDHCPAddress($id, $domains, $internalip);
2100
        if ($result ne "OK") {
2101
            $e=1;
2102
            $postreply .= "$result\n";
2103
        }
2104
    } elsif ($type eq "externalip" && $domains) {
2105
        my $result =  removeDHCPAddress($id, $domains, $externalip);
2106
        if ($result ne "OK") {
2107
            $e=1;
2108
            $postreply .= "$result\n";
2109
        }
2110
    }
2111
    $uistatus = ($e)?"":validateStatus($register{$uuid});
2112
    if ($uistatus) {
2113
        $uiuuid = $uuid;
2114
        $postreply .= "Status=$uistatus OK $action $type $name: $uistatus\n";
2115
    } else {
2116
        $postreply .= "Status=ERROR Cannot $action $type $name: $uistatus\n";
2117
    }
2118
    $main::syslogit->($user, 'info', "$action network $uuid ($name, $id) -> $uistatus");
2119
    updateBilling("$uistatus $uuid ($id)");
2120
    # $main::updateUI->({tab=>"networks", user=>$user, uuid=>$uiuuid, status=>$uistatus}) if ($uistatus);
2121
    return $postreply;
2122
}
2123

    
2124
sub Stop {
2125
    my ($id, $action) = @_;
2126
    # Check if we were passed a uuid
2127
    if ($id =~ /\-/ && $register{$id} && ($register{$id}->{'user'} eq $user || $isadmin)) {
2128
        $id = $register{$id}->{'id'}
2129
    }
2130
    if ($help) {
2131
        return <<END
2132
GET:uuid:
2133
Stops a network by removing gateway. Network must be in status up or nat.
2134
END
2135
    }
2136

    
2137
    my $idleft = ($id>99)?(substr $id,0,-2)+0 : 0;
2138
    my $idright = (substr $id,-2) + 0;
2139
    my $e = 0;
2140
    # First deactivate all user's networks with same id
2141
    my @regkeys = (tied %register)->select_where("user = '$user'");
2142
    foreach my $key (@regkeys) {
2143
        my $valref = $register{$key};
2144
        my $cuuid = $valref->{'uuid'};
2145
        my $ctype = $valref->{'type'};
2146
        my $cdbuser = $valref->{'user'};
2147
        my $cid = $valref->{'id'};
2148
    # Only list networks belonging to current user
2149
        if ($user eq $cdbuser && $id eq $cid && $ctype ne "gateway") {
2150
            if ($ctype eq "internalip" || $ctype eq "ipmapping" || $ctype eq "externalip") {
2151
                my $result = Deactivate($cuuid, 'deactivate');
2152
                if ($result =~ /\w+=ERROR (.+)/i) {
2153
                    $e = $1;
2154
                }
2155
            }
2156
        }
2157
     }
2158
    my $interfaces = `/sbin/ifconfig br$id`;
2159
     # Only take down interface and vlan if gateway IP is active on interface
2160
    if ($e) {
2161
        $postreply .= "Status=Error Not taking down gateway, got an error: $e\n"
2162
#    } elsif ($interfaces =~ /^$datanic\.$id.+\n.+inet .+10\.$idleft\.$idright\.1/
2163
    } elsif ($interfaces =~ /10\.$idleft\.$idright\.1/) {
2164
        eval {`/sbin/brctl delif br$id $datanic.$id`; 1;} or do {$e=1;};
2165
        eval {`/sbin/ifconfig br$id down`; 1;} or do {$e=1;};
2166
        eval {`/sbin/ifconfig $datanic.$id down`; 1;} or do {$e=1;};
2167
        eval {`/sbin/vconfig rem $datanic.$id`; 1;} or do {$e=1;};
2168
        eval {`/sbin/brctl delbr br$id`; 1;} or do {$e=1;};
2169
    } else {
2170
        $postreply .= "Status=Error Not taking down interface, gateway 10.$idleft.$idright.1 is not active on interface br$id - $interfaces.\n"
2171
    }
2172
    # Remove rule to only forward packets coming from subnet assigned to vlan
2173
#    `/sbin/iptables --delete FORWARD --in-interface $datanic.$id ! -s 10.$idleft.$idright.0/24 -j DROP`;
2174

    
2175
    $uistatus = ($e)?$uistatus:"down";
2176
    if ($uistatus eq 'down') {
2177
        $uiuuid = $uuid;
2178
        $postreply .= "Status=$uistatus OK $action gateway: $uistatus\n";
2179
    } else {
2180
        $postreply .= "Status=Error Cannot $action $type $name: $uistatus\n";
2181
    }
2182
    return $postreply;
2183
}
2184

    
2185
sub getDomains {
2186
    my $uuid = shift;
2187
    my $domains;
2188
    my $domainnames;
2189
    my @domregvalues = values %domreg;
2190
    foreach my $domval (@domregvalues) {
2191
        if (($domval->{'networkuuid1'} eq $uuid || $domval->{'networkuuid2'} eq $uuid || $domval->{'networkuuid3'} eq $uuid)
2192
                && $domval->{'user'} eq $user) {
2193
            $domains .= $domval->{'uuid'} . ", ";
2194
            $domainnames .= $domval->{'name'} . ", ";
2195
        }
2196
    }
2197
    $domains = substr $domains, 0, -2;
2198
    $domainnames = substr $domainnames, 0, -2;
2199
    return ($domains, $domainnames); 
2200
}
2201

    
2202
sub getSystems {
2203
    my $uuid = shift;
2204
    my $systems;
2205
    my $systemnames;
2206
    unless (tie(%sysreg,'Tie::DBI', Hash::Merge::merge({table=>'systems'}, $Stabile::dbopts)) ) {$res .= qq|{"status": "Error": "message": "Unable to access systems register"}|; return $res;};
2207
    my @sysregvalues = values %sysreg;
2208
    foreach my $sysval (@sysregvalues) {
2209
        my $networkuuids = $sysval->{'networkuuids'};
2210
        if ($networkuuids =~ /$uuid/ && $sysval->{'user'} eq $user) {
2211
            $systems = $sysval->{'uuid'};
2212
            $systemnames = $sysval->{'name'};
2213
            last;
2214
        }
2215
    }
2216
    unless ($systems) {
2217
        my @sysregvalues = values %domreg;
2218
        foreach my $sysval (@sysregvalues) {
2219
            my $networkuuids = $sysval->{'networkuuids'};
2220
            if ($networkuuids =~ /$uuid/ && $sysval->{'user'} eq $user) {
2221
                $systems = $sysval->{'uuid'};
2222
                $systemnames = $sysval->{'name'};
2223
                last;
2224
            }
2225
        }
2226
    }
2227
    return ($systems, $systemnames);
2228
}
2229

    
2230
sub getNextId {
2231
	# Find the next available vlan id
2232
	my $reqid = shift;
2233
	my $username = shift;
2234
	$username = $user unless ($username);
2235
    my $nextid = 1;
2236
	my $vlanstart = $Stabile::config->get('VLAN_RANGE_START');
2237
	my $vlanend = $Stabile::config->get('VLAN_RANGE_END');
2238

    
2239
    if ($reqid eq 0 || $reqid == 1) {
2240
        return $requid;
2241
    } elsif ($reqid && ($reqid > $vlanend || $reqid < $vlanstart)) {
2242
        return -1 unless ($isadmin);
2243
    }
2244

    
2245
	$reqid = $reqid + 0;
2246

    
2247
    my %ids;
2248
    # First check if the user has an existing vlan, if so use the first we find as default value
2249
    my @regvalues = values %register;
2250
    @regvalues = (sort {$a->{id} <=> $b->{id}} @regvalues);
2251
    foreach my $val (@regvalues) { # Traverse all id's in use
2252
        my $id = 0 + $val->{'id'};
2253
        my $dbuser = $val->{'user'};
2254
        if ($id > 1) {
2255
            if ($username eq $dbuser) { # If a specific id was requested map all id's
2256
                if (!$reqid) {# If no specific id was asked for, stop now, and use the user's first one
2257
                    $nextid = $id;
2258
                    last;
2259
                }
2260
            } else {
2261
                $ids{$id} = 1; # Mark this id as used (by another user)
2262
            }
2263
        }
2264
    }
2265
    if ($nextid>1) {
2266
        return $nextid;
2267
    } elsif ($reqid) {
2268
        if (!$ids{$reqid} || $isadmin) { # If an admin is requesting id used by another, assume he knows what he is doing
2269
            $nextid = $reqid; # Safe to use
2270
        } else {
2271
            $nextid = -1; # Id already in use by another
2272
        }
2273
    } elsif ($nextid == 1) { # This user is not currently using any vlan's, find the first free one
2274
        for ($n=$vlanstart; $n<$vlanend; $n++) {
2275
            if (!$ids{$n}) { # Don't return an id used (by another user)
2276
                $nextid = $n;
2277
                last;
2278
            }
2279
        }
2280
    }
2281
	return $nextid;
2282
}
2283

    
2284
sub getNextRemoteIP {
2285
    my $internalip = shift;
2286
    my $nextip = "";
2287
    my $oc = overQuotas(1);
2288
    if ($oc) { # Enforce quotas
2289
        $postreply .= "Status=ERROR Over quota allocating external IP\n";
2290
    } else {
2291
        my $res = $main::postToOrigo->($engineid, 'provisionremoteip', $internalip, 'internalip');
2292
        my $res_obj = JSON::from_json($res);
2293
        $nextip = $res_obj->{remoteip} if ($res_obj->{remoteip});
2294
    }
2295
    $postreply .= "Status=ERROR No more ($oc) remote IPs available\n" unless ($nextip);
2296
    return $nextip;
2297

    
2298
}
2299
sub getNextExternalIP {
2300
	# Find the next available IP
2301
	my $extip = shift;
2302
	my $extuuid = shift;
2303
	my $proxyarp = shift; # Are we trying to assign a proxy arp's external IP?
2304
	$extip = "" if ($extip eq "--");
2305

    
2306
	my $extipstart;
2307
	my $extipend;
2308

    
2309
    if ($proxyarp) {
2310
        $extipstart = $Stabile::config->get('PROXY_IP_RANGE_START');
2311
        $extipend = $Stabile::config->get('PROXY_IP_RANGE_END');
2312
    } else {
2313
        $extipstart = $Stabile::config->get('EXTERNAL_IP_RANGE_START');
2314
        $extipend = $Stabile::config->get('EXTERNAL_IP_RANGE_END');
2315
    }
2316

    
2317
	return "" unless ($extipstart && $extipend);
2318

    
2319
	my $interfaces = `/sbin/ifconfig`;
2320
#	$interfaces =~ m/eth0 .+\n.+inet addr:(\d+\.\d+\.\d+)\.(\d+)/;
2321
	$extipstart =~  m/(\d+\.\d+\.\d+)\.(\d+)/;
2322
	my $bnet1 = $1;
2323
	my $bhost1 = $2+0;
2324
	$extipend =~  m/(\d+\.\d+\.\d+)\.(\d+)/;
2325
	my $bnet2 = $1;
2326
	my $bhost2 = $2+0;
2327
	my $nextip = "";
2328
	if ($bnet1 ne $bnet2) {
2329
		print "Status=ERROR Only 1 class C subnet is supported for $name\n";
2330
		return "";
2331
	}
2332
	my %ids;
2333
	# First create map of IP's reserved by other servers in DB
2334
	my @regvalues = values %register;
2335
	foreach my $val (@regvalues) {
2336
		my $ip = $val->{'externalip'};
2337
		# $ip =~ m/(\d+\.\d+\.\d+)\.(\d+)/;
2338
		# my $id = $2;
2339
		$ids{$ip} = $val->{'uuid'} unless ($extuuid eq $val->{'uuid'});
2340
	}
2341
    my $oc = overQuotas(1);
2342
	if ($oc) { # Enforce quotas
2343
        $postreply .= "Status=ERROR Over quota allocating external IP\n";
2344
	} elsif ($extip && $extip =~  m/($bnet1)\.(\d+)/ && $2>=$bhost1 && $2<$bhost2) {
2345
	# An external ip was supplied - check if it's free and ok
2346
		if (!$ids{$extip} && !($interfaces =~ m/$extip.+\n.+inet addr:$extip/) && $extip=~/$bnet$\.(\d)/) {
2347
			$nextip = $extip;
2348
		}
2349
	} else {
2350
	# Find random IP not reserved, and check it is not in use (for other purposes)
2351
	    my @bhosts = ($bhost1..$bhost2);
2352
        my @rbhosts = shuffle @bhosts;
2353
		for ($n=0; $n<$bhost2-$bhost1; $n++) {
2354
		    my $nb = $rbhosts[$n];
2355
			if (!$ids{"$bnet1.$nb"}) {
2356
				if (!($interfaces =~ m/$extip.+\n.+inet addr:$bnet1\.$nb/)) {
2357
					$nextip = "$bnet1.$nb";
2358
					last;
2359
				}
2360
			}
2361
		}
2362
	}
2363
	$postreply .= "Status=ERROR No more ($oc) external IPs available\n" unless ($nextip);
2364
	return $nextip;
2365
}
2366

    
2367
sub ip2domain {
2368
    my $ip = shift;
2369
    my $ruuid;
2370
    if ($ip) {
2371
        my @regkeys = (tied %register)->select_where("internalip = '$ip' OR externalip = '$ip'");
2372
        foreach my $k (@regkeys) {
2373
            my $valref = $register{$k};
2374
            if ($valref->{'internalip'} eq $ip || $valref->{'externalip'} eq $ip) {
2375
                $ruuid = $valref->{'domains'};
2376
                last;
2377
            }
2378
        }
2379
    }
2380
    return $ruuid;
2381
}
2382

    
2383
sub getNextInternalIP {
2384
	my $intip = shift;
2385
	my $uuid = shift;
2386
	my $id = shift;
2387
	my $username = shift;
2388
	$username = $user unless ($username);
2389
	my $nextip = "";
2390
	my $intipnum;
2391
	my $subnet;
2392
	my %ids;
2393
    my $ping = Net::Ping->new();
2394

    
2395
    $id = getNextId() unless ($id);
2396
    my $idleft = ($id>99)?(substr $id,0,-2)+0 : 0;
2397
    my $idright = (substr $id,-2) + 0;
2398
    $intip = "10.$idleft.$idright.0" if (!$intip || $intip eq '--');
2399
    
2400
    return '' unless ($intip =~ m/(\d+\.\d+\.\d+)\.(\d+)/ );
2401
    $subnet = $1;
2402
    $intipnum = $2;
2403

    
2404
	# First create hash of IP's reserved by other servers in DB
2405
	my @regvalues = values %register;
2406
	foreach my $val (@regvalues) {
2407
    	if ($val->{'user'} eq $username) {
2408
            my $ip = $val->{'internalip'} ;
2409
            $ids{$ip} = $val->{'uuid'};
2410
		}
2411
	}
2412

    
2413
	if ($intipnum && $intipnum>1 && $intipnum<255) {
2414
	# An internal ip was supplied - check if it's free, if not keep the ip already registered in the db
2415
        if (!$ids{$intip}
2416
#            && !($ping->ping($intip, 0.1)) # 0.1 secs timeout, check if ip is in use, possibly on another engine
2417
            && !(`arping -C1 -c2 -D -I $datanic.$id $intip` =~ /reply from/)  # check if ip is created on another engine
2418
        ) {
2419
            $nextip = $intip;
2420
        } else {
2421
            $nextip = $register{$uuid}->{'internalip'}
2422
        }
2423
	} else {
2424
	# Find first IP not reserved
2425
		for ($n=2; $n<255; $n++) {
2426
			if (!$ids{"$subnet.$n"}
2427
# TODO: The arping check takes too long - two networks created by the same user can too easily be assigned the same IP's
2428
#                && !(`arping -f -c2 -D -I $datanic.$id $subnet.$n` =~ /reply from/)  # check if ip is created on another engine
2429
			) {
2430
                $nextip = "$subnet.$n";
2431
                last;
2432
			}
2433
		}
2434
	}
2435
	$postreply .= "Status=ERROR No more internal IPs available\n" if (!$nextip);
2436
	return $nextip;
2437
}
2438

    
2439
sub validateStatus {
2440
    my $valref = shift;
2441

    
2442
    my $interfaces = `/sbin/ifconfig -a | grep inet`;
2443
    my $uuid = $valref->{'uuid'};
2444
    my $type = $valref->{'type'};
2445
    my $id = $valref->{'id'};
2446
    my $idleft = ($id>99)?(substr $id,0,-2)+0 : 0;
2447
    my $idright = (substr $id,-2) + 0;
2448

    
2449
    ( $valref->{'domains'}, $valref->{'domainnames'} ) = getDomains($uuid);
2450
    my ( $systems, $systemnames ) = getSystems($uuid);
2451
    my $extip = $valref->{'externalip'};
2452
    my $intip = $valref->{'internalip'};
2453

    
2454
    if ($type eq "gateway") {
2455
        $valref->{'internalip'} = "10.$idleft.$idright.1" if ($id>1);
2456
    } else {
2457
        if ($intip && $intip ne "--" && $extip && $extip ne "--") {
2458
            $type = "ipmapping" unless ($type eq 'remoteip');
2459
        } elsif ($intip && $intip ne "--") {
2460
            $type = "internalip";
2461
        } elsif ($extip && $extip ne "--") {
2462
            $type = "externalip";
2463
        } else {
2464
            $type = "gateway";
2465
        }
2466
        $valref->{'type'} = $type;
2467
    }
2468

    
2469
    $valref->{'status'} = "down";
2470
    my $nat;
2471
    if ($id == 0 || $id == 1) {
2472
        $valref->{'status'} = "nat";
2473
    # Check if vlan $id is created (and doing nat)
2474
#    } elsif ($interfaces =~ m/$datanic\.$id.+\n.+10\.$idleft\.$idright\.1/) {
2475
    } elsif (-e "/proc/net/vlan/$datanic.$id") {
2476
        $nat = 1;
2477
    }
2478

    
2479
    if ($type eq "internalip" || $type eq "ipmapping" || $type eq "remoteip") {
2480
        $valref->{'status'} = "nat" if ($nat);
2481
        my $dhcprunning;
2482
        my $dhcpconfigured;
2483
        eval {
2484
            my $psid;
2485
            $psid = `/bin/cat /var/run/stabile-$id.pid` if (-e "/var/run/stabile-$id.pid");
2486
            chomp $psid;
2487
            $dhcprunning = -e "/proc/$psid" if ($psid);
2488
            my $dhcphosts;
2489
            $dhcphosts = lc `/bin/cat $etcpath/dhcp-hosts-$id` if (-e "$etcpath/dhcp-hosts-$id");
2490
            $dhcpconfigured = ($dhcphosts =~ /$intip/);
2491
            1;
2492
        } or do {;};
2493

    
2494
        if ($type eq "internalip" || $type eq "remoteip") {
2495
        # Check if external ip has been created and dhcp is ok
2496
            if ($nat && (($dhcprunning && $dhcpconfigured) || $systems)) {
2497
                if ($type eq "remoteip") {
2498
                    if (`pgrep -f 'ssh .* $externalip'`) {
2499
                        $valref->{'status'} = "up";
2500
                    }
2501
                } else {
2502
                    $valref->{'status'} = "up";
2503
                }
2504
            }
2505
        } elsif ($type eq "ipmapping") {
2506
        # Check if external ip has been created, dhcp is ok and vlan interface is created
2507
        # An ipmapping linked to a system is considered up if external interface exists
2508
        # Update: It appears that ip addresses on virtual interfaces are periodically lost for some reason
2509
        # the interface however still responds to the ip address if iptables rules referencing this exists
2510
        # so we have relaxed the up requirement
2511
            if ($nat
2512
        #            && $interfaces =~ m/$extip/ # interfaces seem to drop out of sight after while even if still active
2513
                    && (($dhcprunning && $dhcpconfigured) || ($systems && $interfaces =~ m/$extip/))
2514
            ) {
2515
                $valref->{'status'} = "up";
2516
            }
2517
        }
2518

    
2519
    } elsif ($type eq "externalip") {
2520
        my $dhcprunning;
2521
        my $dhcpconfigured;
2522
        eval {
2523
            my $psid;
2524
            $psid = `/bin/cat /var/run/stabile-$id.pid` if (-e "/var/run/stabile-$id.pid");
2525
            chomp $psid;
2526
            $dhcprunning = -e "/proc/$psid" if ($psid);
2527
            my $dhcphosts;
2528
            $dhcphosts = `/bin/cat $etcpath/dhcp-hosts-$id` if (-e "$etcpath/dhcp-hosts-$id");
2529
            $dhcpconfigured = ($dhcphosts =~ /$extip/);
2530
            1;
2531
        } or do {;};
2532

    
2533
        my $vproxy = `/bin/cat /proc/sys/net/ipv4/conf/$datanic.$id/proxy_arp`; chomp $vproxy;
2534
        my $eproxy = `/bin/cat /proc/sys/net/ipv4/conf/$proxynic/proxy_arp`; chomp $eproxy;
2535
        my $proute = `/sbin/ip route | grep "$extip dev"`; chomp $proute;
2536
        if ($vproxy && $eproxy && $proute) {
2537
            if ((($dhcprunning && $dhcpconfigured) || $systems)) {
2538
                $valref->{'status'} = "up";
2539
            } elsif (!$valref->{'domains'}) {
2540
                $valref->{'status'} = "nat";
2541
            }
2542
        } else {
2543
            #print "$vproxy && $eproxy && $proute && $dhcprunning && $dhcpconfigured :: $extip\n";        
2544
        }
2545

    
2546
    } elsif ($type eq "gateway") {
2547
        if ($nat || $id == 0 || $id == 1) {$valref->{'status'} = "up";}
2548
    }
2549
    return $valref->{'status'};
2550
}
2551

    
2552
sub trim{
2553
   my $string = shift;
2554
   $string =~ s/^\s+|\s+$//g;
2555
   return $string;
2556
}
2557

    
2558
sub overQuotas {
2559
    my $reqips = shift; # number of new ip's we are asking for
2560
	my $usedexternalips = 0;
2561
	my $overquota = 0;
2562
    return $overquota if ($Stabile::userprivileges =~ /a/); # Don't enforce quotas for admins
2563

    
2564
	my $externalipquota = $Stabile::userexternalipquota;
2565
	if (!$externalipquota) {
2566
        $externalipquota = $Stabile::config->get('EXTERNAL_IP_QUOTA');
2567
    }
2568

    
2569
	my $rxquota = $Stabile::userrxquota;
2570
	if (!$rxquota) {
2571
        $rxquota = $Stabile::config->get('RX_QUOTA');
2572
    }
2573

    
2574
	my $txquota = $Stabile::usertxquota;
2575
	if (!$txquota) {
2576
        $txquota = $Stabile::config->get('TX_QUOTA');
2577
    }
2578

    
2579
    my @regkeys = (tied %register)->select_where("user = '$user'");
2580
	foreach my $k (@regkeys) {
2581
	    my $val = $register{$k};
2582
		if ($val->{'user'} eq $user && $val->{'externalip'} && $val->{'externalip'} ne "--" ) {
2583
		    $usedexternalips += 1;
2584
		}
2585
	}
2586
	if ((($usedexternalips + $reqips) > $externalipquota) && $externalipquota > 0) { # -1 means no quota
2587
	    $overquota = $usedexternalips;
2588
	} elsif ($rx > $rxquota*1024 && $rxquota > 0) {
2589
	    $overquota = -1;
2590
	} elsif ($tx > $txquota*1024 && $txquota > 0) {
2591
	    $overquota = -2;
2592
	}
2593
	return $overquota;
2594
}
2595

    
2596
sub updateBilling {
2597
    my $event = shift;
2598
    my %billing;
2599
    my @regkeys = (tied %register)->select_where("user = '$user' or user = 'common'") unless ($fulllist);
2600
    foreach my $k (@regkeys) {
2601
        my $valref = $register{$k};
2602
        my %val = %{$valref}; # Deference and assign to new array, effectively cloning object
2603
        if ($val{'user'} eq $user && ($val{'type'} eq 'ipmapping' || $val{'type'} eq 'externalip') && $val{'externalip'} ne '--') {
2604
            $billing{$val{'id'}}->{'externalip'} += 1;
2605
        }
2606
    }
2607

    
2608
    my %billingreg;
2609
    my $monthtimestamp = timelocal(0,0,0,1,$mon,$year); #$sec,$min,$hour,$mday,$mon,$year
2610

    
2611
    unless ( tie(%billingreg,'Tie::DBI', Hash::Merge::merge({table=>'billing_networks', key=>'useridtime'}, $Stabile::dbopts)) ) {return "Unable to access billing register"};
2612

    
2613
    my $rx_bytes_total = 0;
2614
    my $tx_bytes_total = 0;
2615

    
2616
    my $prevmonth = $month-1;
2617
    my $prevyear = $year;
2618
    if ($prevmonth == 0) {$prevmonth=12; $prevyear--;};
2619
    $prevmonth = substr("0" . $prevmonth, -2);
2620
    my $prev_rx_bytes_total = 0;
2621
    my $prev_tx_bytes_total = 0;
2622

    
2623
    foreach my $id (keys %billing) {
2624
        my $b = $billing{$id};
2625
        my $externalip = $b->{'externalip'};
2626
        my $externalipavg = 0;
2627
        my $startexternalipavg = 0;
2628
        my $starttimestamp = $current_time;
2629
        my $rx_bytes = 0;
2630
        my $tx_bytes = 0;
2631
#        my $rx_stats = "/sys/class/net/$datanic.$id/statistics/rx_bytes";
2632
#        my $tx_stats = "/sys/class/net/$datanic.$id/statistics/tx_bytes";
2633
        my $rx_stats = "/sys/class/net/br$id/statistics/rx_bytes";
2634
        my $tx_stats = "/sys/class/net/br$id/statistics/tx_bytes";
2635
        $rx_bytes = `/bin/cat $rx_stats` if (-e $rx_stats);
2636
        chomp $rx_bytes;
2637
        $tx_bytes = `/bin/cat $tx_stats` if (-e $tx_stats);
2638
        chomp $tx_bytes;
2639

    
2640
        if ($current_time - $monthtimestamp < 4*3600) {
2641
            $starttimestamp = $monthtimestamp;
2642
            $externalipavg = $externalip;
2643
            $startexternalipavg = $externalip;
2644
        }
2645

    
2646
        my $bill = $billingreg{"$user-$id-$year-$month"};
2647
        my $regrx_bytes = $bill->{'rx'};
2648
        my $regtx_bytes = $bill->{'tx'};
2649
        $rx_bytes += $regrx_bytes if ($regrx_bytes > $rx_bytes); # Network interface was reloaded
2650
        $tx_bytes += $regtx_bytes if ($regtx_bytes > $tx_bytes); # Network interface was reloaded
2651

    
2652
        # Update timestamp and averages on existing row
2653
        if ($billingreg{"$user-$id-$year-$month"}) {
2654
            $startexternalipavg = $bill->{'startexternalipavg'};
2655
            $starttimestamp = $bill->{'starttimestamp'};
2656

    
2657
            $externalipavg = ($startexternalipavg*($starttimestamp - $monthtimestamp) + $externalip*($current_time - $starttimestamp)) /
2658
                            ($current_time - $monthtimestamp);
2659

    
2660
            $billingreg{"$user-$id-$year-$month"}->{'externalip'} = $externalip;
2661
            $billingreg{"$user-$id-$year-$month"}->{'externalipavg'} = $externalipavg;
2662
            $billingreg{"$user-$id-$year-$month"}->{'timestamp'} = $current_time;
2663
            $billingreg{"$user-$id-$year-$month"}->{'rx'} = $rx_bytes;
2664
            $billingreg{"$user-$id-$year-$month"}->{'tx'} = $tx_bytes;
2665
        }
2666

    
2667
        # No row found or something happened which justifies writing a new row
2668
        if (!$billingreg{"$user-$id-$year-$month"}
2669
        || ($b->{'externalip'} != $bill->{'externalip'})
2670
        ) {
2671

    
2672
            my $inc = 0;
2673
            if ($billingreg{"$user-$id-$year-$month"}) {
2674
                $startexternalipavg = $externalipavg;
2675
                $starttimestamp = $current_time;
2676
                $inc = $bill->{'inc'};
2677
            }
2678
            # Write a new row
2679
            $billingreg{"$user-$id-$year-$month"} = {
2680
                externalip=>$externalip+0,
2681
                externalipavg=>$externalipavg,
2682
                startexternalipavg=>$startexternalipavg,
2683
                timestamp=>$current_time,
2684
                starttimestamp=>$starttimestamp,
2685
                event=>$event,
2686
                inc=>$inc+1,
2687
                rx=>$rx_bytes,
2688
                tx=>$tx_bytes
2689
            };
2690
        }
2691

    
2692
        $rx_bytes_total += $rx_bytes;
2693
        $tx_bytes_total += $tx_bytes;
2694
        my $prevbill = $billingreg{"$user-$id-$prevyear-$prevmonth"};
2695
        $prev_rx_bytes_total += $prevbill->{'rx'};
2696
        $prev_tx_bytes_total += $prevbill->{'tx'};
2697
    }
2698
    untie %billingreg;
2699
    $rx = ($rx_bytes_total>$prev_rx_bytes_total)?$rx_bytes_total - $prev_rx_bytes_total:$rx_bytes_total;
2700
    $tx = ($tx_bytes_total>$prev_tx_bytes_total)?$tx_bytes_total - $prev_tx_bytes_total:$tx_bytes_total;
2701
    my $oq = overQuotas();
2702
    if ($oq && $oq<0) {
2703
        foreach my $id (keys %billing) {
2704
            $main::syslogit->($user, 'info', "$user over rx/tx quota ($oq) stopping network $id");
2705
            Stop($id, 'stop');
2706
        }
2707
    }
2708
}
2709

    
2710
sub Bit2netmask {
2711
	my $netbit = shift;
2712
	my $_bit         = ( 2 ** (32 - $netbit) ) - 1;
2713
	my ($full_mask)  = unpack( "N", pack( "C4", split(/./, '255.255.255.255') ) );
2714
	my $netmask      = join( '.', unpack( "C4", pack( "N", ( $full_mask ^ $_bit ) ) ) );
2715
	return $netmask;
2716
}
(3-3/9)