Handle hostnames with upper-case letters
[webmin.git] / net / linux-lib.pl
1 # linux-lib.pl
2 # Active interface functions for all flavours of linux
3
4 # active_interfaces([include-no-address])
5 # Returns a list of currently ifconfig'd interfaces
6 sub active_interfaces
7 {
8 local ($empty) = @_;
9 local(@rv, @lines, $l);
10 &clean_language();
11 &open_execute_command(IFC, "ifconfig -a", 1, 1);
12 while(<IFC>) {
13         s/\r|\n//g;
14         if (/^\S+/) { push(@lines, $_); }
15         else { $lines[$#lines] .= $_; }
16         }
17 close(IFC);
18 &reset_environment();
19 my $ethtool = &has_command("ethtool");
20 foreach $l (@lines) {
21         local %ifc;
22         $l =~ /^([^:\s]+)/; $ifc{'name'} = $1;
23         $l =~ /^(\S+)/; $ifc{'fullname'} = $1;
24         if ($l =~ /^(\S+):(\d+)/) { $ifc{'virtual'} = $2; }
25         if ($l =~ /inet addr:(\S+)/) { $ifc{'address'} = $1; }
26         elsif (!$empty) { next; }
27         if ($l =~ /Mask:(\S+)/) { $ifc{'netmask'} = $1; }
28         if ($l =~ /Bcast:(\S+)/) { $ifc{'broadcast'} = $1; }
29         if ($l =~ /HWaddr (\S+)/) { $ifc{'ether'} = $1; }
30         if ($l =~ /MTU:(\d+)/) { $ifc{'mtu'} = $1; }
31         if ($l =~ /P-t-P:(\S+)/) { $ifc{'ptp'} = $1; }
32         $ifc{'up'}++ if ($l =~ /\sUP\s/);
33         $ifc{'promisc'}++ if ($l =~ /\sPROMISC\s/);
34         local (@address6, @netmask6, @scope6);
35         while($l =~ s/inet6 addr:\s*(\S+)\/(\d+)\s+Scope:(Global)//i) {
36                 local ($address6, $netmask6, $scope6) = ($1, $2, $3);
37                 push(@address6, $address6);
38                 push(@netmask6, $netmask6);
39                 push(@scope6, $scope6);
40                 }
41         $ifc{'address6'} = \@address6;
42         $ifc{'netmask6'} = \@netmask6;
43         $ifc{'scope6'} = \@scope6;
44         $ifc{'edit'} = ($ifc{'name'} !~ /^ppp/);
45         $ifc{'index'} = scalar(@rv);
46
47         # Get current status for ethtool
48         if ($ifc{'fullname'} =~ /^eth(\d+)$/ && $ethtool) {
49                 my $out = &backquote_command(
50                         "$ethtool $ifc{'fullname'} 2>/dev/null");
51                 if ($out =~ /Speed:\s+(\S+)/i) {
52                         $ifc{'speed'} = $1;
53                         }
54                 if ($out =~ /Duplex:\s+(\S+)/i) {
55                         $ifc{'duplex'} = $1;
56                         }
57                 if ($out =~ /Link\s+detected:\s+(\S+)/i) {
58                         $ifc{'link'} = lc($1) eq 'yes' ? 1 : 0;
59                         }
60                 }
61         push(@rv, \%ifc);
62         }
63 return @rv;
64 }
65
66 # activate_interface(&details)
67 # Create or modify an interface
68 sub activate_interface
69 {
70 local $a = $_[0];
71 # For Debian 5.0+ the "vconfig add" command is deprecated, this is handled by ifup.
72 if(($a->{'vlan'} == 1) && !(($gconfig{'os_type'} eq 'debian-linux') && ($gconfig{'os_version'} >= 5))) {
73         local $vconfigCMD = "vconfig add " .
74                             $a->{'physical'} . " " . $a->{'vlanid'};
75         local $vconfigout = &backquote_logged("$vconfigCMD 2>&1");
76         if ($?) { &error($vonconfigout); }
77         }
78
79 local $cmd;
80 if (&use_ifup_command($a)) {
81         # Use Debian ifup command
82         if($a->{'vlan'} == 1) {
83                 # name and fullname for VLAN tagged interfaces are "auto" so we need to ifup using physical and vlanid. 
84                 if ($a->{'up'}) { $cmd .= "ifup $a->{'physical'}" . "." . $a->{'vlanid'}; }
85                 else { $cmd .= "ifdown $a->{'physical'}" . "." . $a->{'vlanid'}; }
86         }
87         elsif ($a->{'up'}) { $cmd .= "ifup $a->{'fullname'}"; }
88         else { $cmd .= "ifdown $a->{'fullname'}"; }
89         }
90 else {
91         # Build ifconfig command manually
92         if($a->{'vlan'} == 1) {
93                 $cmd .= "ifconfig $a->{'physical'}.$a->{'vlanid'}";
94                 }
95         else {
96                 $cmd .= "ifconfig $a->{'name'}";
97                 if ($a->{'virtual'} ne "") {
98                         $cmd .= ":$a->{'virtual'}";
99                         }
100                 }
101         $cmd .= " $a->{'address'}";
102         if ($a->{'netmask'}) { $cmd .= " netmask $a->{'netmask'}"; }
103         if ($a->{'broadcast'}) { $cmd .= " broadcast $a->{'broadcast'}"; }
104         if ($a->{'mtu'} && $a->{'virtual'} eq "") { $cmd .= " mtu $a->{'mtu'}";}
105         if ($a->{'up'}) { $cmd .= " up"; }
106         else { $cmd .= " down"; }
107         }
108 local $out = &backquote_logged("$cmd 2>&1");
109 if ($?) { &error($out); }
110
111 # Apply ethernet address
112 if ($a->{'ether'} && !&use_ifup_command($a)) {
113         $out = &backquote_logged(
114                 "ifconfig $a->{'name'} hw ether $a->{'ether'} 2>&1");
115         if ($?) { &error($out); }
116         }
117
118 if ($a->{'virtual'} eq '') {
119         # Remove old IPv6 addresses
120         local $l = &backquote_command("ifconfig $a->{'name'}");
121         while($l =~ s/inet6 addr:\s*(\S+)\/(\d+)\s+Scope:(\S+)//) {
122                 local $cmd = "ifconfig $a->{'name'} inet6 del $1/$2 2>&1";
123                 $out = &backquote_logged($cmd);
124                 &error("Failed to remove old IPv6 address : $out") if ($?);
125                 }
126
127         # Add IPv6 addresses
128         for(my $i=0; $i<@{$a->{'address6'}}; $i++) {
129                 local $cmd = "ifconfig $a->{'name'} inet6 add ".
130                      $a->{'address6'}->[$i]."/".$a->{'netmask6'}->[$i]." 2>&1";
131                 $out = &backquote_logged($cmd);
132                 &error("Failed to add IPv6 address : $out") if ($?);
133                 }
134         }
135 }
136
137 # deactivate_interface(&details)
138 # Shutdown some active interface
139 sub deactivate_interface
140 {
141 local $a = $_[0];
142 local $name = $a->{'name'}.
143               ($a->{'virtual'} ne "" ? ":$a->{'virtual'}" : "");
144 local $address = $a->{'address'}.
145         ($a->{'virtual'} ne "" ? ":$a->{'virtual'}" : "");
146 local $netmask = $a->{'netmask'};
147  
148 if ($a->{'virtual'} ne "") {
149         # Shutdown virtual interface by setting address to 0
150         local $out = &backquote_logged("ifconfig $name 0 2>&1");
151         }
152 # Delete all v6 addresses
153 for(my $i=0; $i<@{$a->{'address6'}}; $i++) {
154         local $cmd = "ifconfig $a->{'name'} inet6 del ".
155                      $a->{'address6'}->[$i]."/".$a->{'netmask6'}->[$i];
156         &backquote_logged("$cmd 2>&1");
157         }
158
159 # Check if still up somehow
160 local ($still) = grep { $_->{'fullname'} eq $name } &active_interfaces();
161 if ($still) {
162         # Old version of ifconfig or non-virtual interface.. down it
163         local $out;
164         if (&use_ifup_command($a)) {
165                 $out = &backquote_logged("ifdown $name 2>&1");
166                 }
167         else {
168                 $out = &backquote_logged("ifconfig $name down 2>&1");
169                 }
170         local ($still) = grep { $_->{'fullname'} eq $name }
171                       &active_interfaces();
172         if ($still && $still->{'up'}) {
173                 &error($out ? "<pre>$out</pre>"
174                             : "Interface is still active even after being ".
175                               "shut down");
176                 }
177         if (&iface_type($name) =~ /^(.*) (VLAN)$/) {
178                 $out = &backquote_logged("vconfig rem $name 2>&1");
179                 }
180         }
181 }
182
183 # use_ifup_command(&iface)
184 # Returns 1 if the ifup command must be used to bring up some interface.
185 # True on Debian 5.0+ for non-ethernet, typically bonding and VLAN tagged interfaces.
186 sub use_ifup_command
187 {
188 local ($iface) = @_;
189 return $gconfig{'os_type'} eq 'debian-linux' &&
190        $gconfig{'os_version'} >= 5 &&
191        $iface->{'name'} !~ /^(eth|lo)/ &&
192        $iface->{'virtual'} eq '';
193 }
194
195 # iface_type(name)
196 # Returns a human-readable interface type name
197 sub iface_type
198 {
199 if ($_[0] =~ /^(.*)\.(\d+)$/) {
200         return &iface_type("$1")." VLAN";
201         }
202 return "PPP" if ($_[0] =~ /^ppp/);
203 return "SLIP" if ($_[0] =~ /^sl/);
204 return "PLIP" if ($_[0] =~ /^plip/);
205 return "Ethernet" if ($_[0] =~ /^eth/);
206 return "Wireless Ethernet" if ($_[0] =~ /^(wlan|ath)/);
207 return "Arcnet" if ($_[0] =~ /^arc/);
208 return "Token Ring" if ($_[0] =~ /^tr/);
209 return "Pocket/ATP" if ($_[0] =~ /^atp/);
210 return "Loopback" if ($_[0] =~ /^lo/);
211 return "ISDN rawIP" if ($_[0] =~ /^isdn/);
212 return "ISDN syncPPP" if ($_[0] =~ /^ippp/);
213 return "CIPE" if ($_[0] =~ /^cip/);
214 return "VmWare" if ($_[0] =~ /^vmnet/);
215 return "Wireless" if ($_[0] =~ /^wlan/);
216 return "Bonded" if ($_[0] =~ /^bond/);
217 return "OpenVZ" if ($_[0] =~ /^venet/);
218 return "Bridge" if ($_[0] =~ /^br/);
219 return $text{'ifcs_unknown'};
220 }
221
222 # list_routes()
223 # Returns a list of active routes
224 sub list_routes
225 {
226 local @rv;
227 &open_execute_command(ROUTES, "netstat -rn", 1, 1);
228 while(<ROUTES>) {
229         s/\s+$//;
230         if (/^([0-9\.]+)\s+([0-9\.]+)\s+([0-9\.]+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)$/) {
231                 push(@rv, { 'dest' => $1,
232                             'gateway' => $2,
233                             'netmask' => $3,
234                             'iface' => $4 });
235                 }
236         }
237 close(ROUTES);
238 &open_execute_command(ROUTES, "netstat -rn -A inet6", 1, 1);
239 while(<ROUTES>) {
240         s/\s+$//;
241         if (/^([0-9a-z:]+)\/([0-9]+)\s+([0-9a-z:]+)\s+\S+\s+\S+\s+\S+\s+\S+\s+(\S+)$/) {
242                 push(@rv, { 'dest' => $1,
243                             'gateway' => $3,
244                             'netmask' => $2,
245                             'iface' => $4 });
246                 }
247         }
248 close(ROUTES);
249 return @rv;
250 }
251
252 # load_module(&details)
253 # Load or modify a loaded module
254 sub load_module
255 {
256 local $a = $_[0];
257 local $cmd = "modprobe bonding";
258
259 if($a->{'mode'}) {$cmd .= " mode=" . $a->{'mode'};}
260 if($a->{'miimon'}) {$cmd .= " miimon=" . $a->{'miimon'};}
261 if($a->{'downdelay'}) {$cmd .= " downdelay=" . $a->{'downdelay'};}
262 if($a->{'updelay'}) {$cmd .= " updelay=" . $a->{'updelay'};}
263
264 local $out = &backquote_logged("$cmd 2>&1");
265 if ($?) { &error($out); }
266 }
267
268 # Tries to unload the module
269 # unload_module(name)
270 sub unload_module
271 {
272         my ($name) = @_;
273         my $cmd = "modprobe -r bonding";
274         local $out = &backquote_logged("$cmd 2>&1");
275         if($?) { &error($out);}
276 }
277
278 # list_interfaces()
279 # return a list of interfaces
280 sub list_interfaces
281 {
282         my @ret;
283         $cmd = "ifconfig -a";
284         local $out = &backquote_logged("$cmd 2>&1");
285         if ($?) { &error($out); }
286         
287         @lines = split("\n", $out);
288         foreach $line(@lines) {
289                 $line =~ /^([\w|.]*)/m;
290                 if(($1)) {
291                         push(@ret, $1);
292                 }
293         }
294         return @ret;
295 }
296
297 # create_route(&route)
298 # Delete one active route, as returned by list_routes. Returns an error message
299 # on failure, or undef on success
300 sub delete_route
301 {
302 local ($route) = @_;
303 local $cmd = "route ".
304         (&check_ip6address($route->{'dest'}) || $route->{'dest'} eq '::' ?
305          "-A inet6 " : "-A inet ")."del ";
306 if (!$route->{'dest'} || $route->{'dest'} eq '0.0.0.0' ||
307     $route->{'dest'} eq '::') {
308                 $cmd .= " default";
309         }
310 elsif ($route->{'netmask'} eq '255.255.255.255') {
311         $cmd .= " -host $route->{'dest'}";
312         }
313 elsif (!&check_ip6address($route->{'dest'})) {
314         $cmd .= " -net $route->{'dest'}";
315         if ($route->{'netmask'} && $route->{'netmask'} ne '0.0.0.0') {
316                 $cmd .= " netmask $route->{'netmask'}";
317                 }
318         }
319 else {
320         $cmd .= "$route->{'dest'}/$route->{'netmask'}";
321         }
322 if ($route->{'gateway'}) {
323         $cmd .= " gw $route->{'gateway'}";
324         }
325 elsif ($route->{'iface'}) {
326         $cmd .= " dev $route->{'iface'}";
327         }
328 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
329 return $? ? $out : undef;
330 }
331
332 # create_route(&route)
333 # Adds a new active route
334 sub create_route
335 {
336 local ($route) = @_;
337 local $cmd = "route ".
338         (&check_ip6address($route->{'dest'}) ||
339          &check_ip6address($route->{'gateway'}) ?
340          "-A inet6 " : "-A inet ")."add ";
341 if (!$route->{'dest'} || $route->{'dest'} eq '0.0.0.0' ||
342     $route->{'dest'} eq '::') {
343         $cmd .= " default";
344         }
345 elsif ($route->{'netmask'} eq '255.255.255.255') {
346         $cmd .= " -host $route->{'dest'}";
347         }
348 elsif (!&check_ip6address($route->{'dest'})) {
349         $cmd .= " -net $route->{'dest'}";
350         if ($route->{'netmask'} && $route->{'netmask'} ne '0.0.0.0') {
351                 $cmd .= " netmask $route->{'netmask'}";
352                 }
353         }
354 else {
355         $cmd .= "$route->{'dest'}/$route->{'netmask'}";
356         }
357 if ($route->{'gateway'}) {
358         $cmd .= " gw $route->{'gateway'}";
359         }
360 elsif ($route->{'iface'}) {
361         $cmd .= " dev $route->{'iface'}";
362         }
363 local $out = &backquote_logged("$cmd 2>&1 </dev/null");
364 return $? ? $out : undef;
365 }
366
367 # iface_hardware(name)
368 # Does some interface have an editable hardware address
369 sub iface_hardware
370 {
371 return $_[0] =~ /^eth/;
372 }
373
374 # allow_interface_clash()
375 # Returns 0 to indicate that two virtual interfaces with the same IP
376 # are not allowed
377 sub allow_interface_clash
378 {
379 return 0;
380 }
381
382 # get_dns_config()
383 # Returns a hashtable containing keys nameserver, domain, search & order
384 sub get_dns_config
385 {
386 local $dns;
387 local $rc;
388 if ($use_suse_dns && ($rc = &parse_rc_config()) && $rc->{'NAMESERVER'}) {
389         # Special case - get DNS settings from SuSE config
390         local @ns = split(/\s+/, $rc->{'NAMESERVER'}->{'value'});
391         $dns->{'nameserver'} = [ grep { $_ ne "YAST_ASK" } @ns ];
392         local $src = $rc->{'SEARCHLIST'};
393         $dns->{'domain'} = [ split(/\s+/, $src->{'value'}) ] if ($src);
394         $dnsfile = $rc_config;
395         }
396 else {
397         &open_readfile(RESOLV, "/etc/resolv.conf");
398         while(<RESOLV>) {
399                 s/\r|\n//g;
400                 s/#.*$//;
401                 s/;.*$//;
402                 if (/nameserver\s+(.*)/) {
403                         push(@{$dns->{'nameserver'}}, split(/\s+/, $1));
404                         }
405                 elsif (/domain\s+(\S+)/) {
406                         $dns->{'domain'} = [ $1 ];
407                         }
408                 elsif (/search\s+(.*)/) {
409                         $dns->{'domain'} = [ split(/\s+/, $1) ];
410                         }
411                 }
412         close(RESOLV);
413         $dnsfile = "/etc/resolv.conf";
414         }
415 &open_readfile(SWITCH, "/etc/nsswitch.conf");
416 while(<SWITCH>) {
417         s/\r|\n//g;
418         if (/^\s*hosts:\s+(.*)/) {
419                 $dns->{'order'} = $1;
420                 }
421         }
422 close(SWITCH);
423 $dns->{'files'} = [ $dnsfile, "/etc/nsswitch.conf" ];
424 return $dns;
425 }
426
427 # save_dns_config(&config)
428 # Writes out the resolv.conf and nsswitch.conf files
429 sub save_dns_config
430 {
431 local $rc;
432 &lock_file($rc_config) if ($suse_dns_config);
433 if ($use_suse_dns && ($rc = &parse_rc_config()) && $rc->{'NAMESERVER'}) {
434         # Update SuSE config file
435         &save_rc_config($rc, "NAMESERVER", join(" ", @{$_[0]->{'nameserver'}}));
436         &save_rc_config($rc, "SEARCHLIST", join(" ", @{$_[0]->{'domain'}}));
437         }
438 else {
439         # Update standard resolv.conf file
440         &lock_file("/etc/resolv.conf");
441         &open_readfile(RESOLV, "/etc/resolv.conf");
442         local @resolv = <RESOLV>;
443         close(RESOLV);
444         &open_tempfile(RESOLV, ">/etc/resolv.conf");
445         foreach (@{$_[0]->{'nameserver'}}) {
446                 &print_tempfile(RESOLV, "nameserver $_\n");
447                 }
448         if ($_[0]->{'domain'}) {
449                 if ($_[0]->{'domain'}->[1]) {
450                         &print_tempfile(RESOLV,
451                                 "search ",join(" ", @{$_[0]->{'domain'}}),"\n");
452                         }
453                 else {
454                         &print_tempfile(RESOLV,
455                                 "domain $_[0]->{'domain'}->[0]\n");
456                         }
457                 }
458         foreach (@resolv) {
459                 &print_tempfile(RESOLV, $_)
460                         if (!/^\s*(nameserver|domain|search)\s+/);
461                 }
462         &close_tempfile(RESOLV);
463         &unlock_file("/etc/resolv.conf");
464         }
465
466 # On Debian, if dns-nameservers are defined in interfaces, update them too
467 if ($gconfig{'os_type'} eq 'debian-linux' && defined(&get_interface_defs)) {
468         local @ifaces = &get_interface_defs();
469         foreach my $i (@ifaces) {
470                 local ($dns) = grep { $_->[0] eq 'dns-nameservers' } @{$i->[3]};
471                 if ($dns) {
472                         $dns->[1] = join(' ', @{$_[0]->{'nameserver'}});
473                         &modify_interface_def($i->[0], $i->[1], $i->[2],
474                                               $i->[3], 0);
475                         }
476                 }
477         }
478
479 # Update resolution order in nsswitch.conf
480 &lock_file("/etc/nsswitch.conf");
481 &open_readfile(SWITCH, "/etc/nsswitch.conf");
482 local @switch = <SWITCH>;
483 close(SWITCH);
484 &open_tempfile(SWITCH, ">/etc/nsswitch.conf");
485 foreach (@switch) {
486         if (/^\s*hosts:\s+/) {
487                 &print_tempfile(SWITCH, "hosts:\t$_[0]->{'order'}\n");
488                 }
489         else {
490                 &print_tempfile(SWITCH, $_);
491                 }
492         }
493 &close_tempfile(SWITCH);
494 &unlock_file("/etc/nsswitch.conf");
495
496 # Update SuSE config file
497 if ($suse_dns_config && $rc->{'USE_NIS_FOR_RESOLVING'}) {
498         if ($_[0]->{'order'} =~ /nis/) {
499                 &save_rc_config($rc, "USE_NIS_FOR_RESOLVING", "yes");
500                 }
501         else {
502                 &save_rc_config($rc, "USE_NIS_FOR_RESOLVING", "no");
503                 }
504         }
505 &unlock_file($rc_config) if ($suse_dns_config);
506 }
507
508 $max_dns_servers = 3;
509
510 # order_input(&dns)
511 # Returns HTML for selecting the name resolution order
512 sub order_input
513 {
514 my @o = split(/\s+/, $_[0]->{'order'});
515 @o = map { s/nis\+/nisplus/; s/yp/nis/; $_; } @o;
516 my @opts = ( [ "files", "Hosts file" ], [ "dns", "DNS" ], [ "nis", "NIS" ],
517              [ "nisplus", "NIS+" ], [ "ldap", "LDAP" ], [ "db", "DB" ],
518              [ "mdns4", "Multicast DNS" ] );
519 if (&indexof("mdns4_minimal", @o) >= 0) {
520         push(@opts, [ "mdns4_minimal", "Multicast DNS (minimal)" ]);
521         }
522 return &common_order_input("order", join(" ", @o), \@opts);
523 }
524
525 # parse_order(&dns)
526 # Parses the form created by order_input()
527 sub parse_order
528 {
529 if (defined($in{'order'})) {
530         $in{'order'} =~ /\S/ || &error($text{'dns_eorder'});
531         $_[0]->{'order'} = $in{'order'};
532         }
533 else {
534         local($i, @order);
535         for($i=0; defined($in{"order_$i"}); $i++) {
536                 push(@order, $in{"order_$i"}) if ($in{"order_$i"});
537                 }
538         $_[0]->{'order'} = join(" ", @order);
539         }
540 }
541
542 1;
543