Handle hostnames with upper-case letters
[webmin.git] / net / freebsd-lib.pl
1 # freebsd-lib.pl
2 # Networking functions for FreeBSD
3
4 $virtual_netmask = "255.255.255.255";   # Netmask for virtual interfaces
5
6 # active_interfaces()
7 # Returns a list of currently ifconfig'd interfaces
8 sub active_interfaces
9 {
10 local(@rv, @lines, $l);
11 local @boot = &boot_interfaces();
12 local %boot = map { $_->{'address'}, $_ } @boot;
13 local %bootname = map { $_->{'fullname'}, $_ } @boot;
14 &open_execute_command(IFC, "ifconfig -a", 1, 1);
15 while(<IFC>) {
16         s/\r|\n//g;
17         if (/^\S+:/) { push(@lines, $_); }
18         elsif (@lines) { $lines[$#lines] .= $_; }
19         }
20 close(IFC);
21 foreach $l (@lines) {
22         local %ifc;
23         $l =~ /^([^:\s]+):/;
24         $ifc{'name'} = $ifc{'fullname'} = $1;
25         if ($l =~ /^(\S+):(\d+):\s/) { $ifc{'virtual'} = $2; }
26         local $bootiface = $bootname{$ifc{'fullname'}};
27         local $bootip = $bootiface ? $bootiface->{'address'} : undef;
28         if ($l =~ s/inet\s+($bootip)\s+netmask\s+(\S+)\s+broadcast\s+(\S+)// ||
29             $l =~ s/inet\s+(\S+)\s+netmask\s+(\S+)\s+broadcast\s+(\S+)//) {
30                 $ifc{'address'} = $1;
31                 $ifc{'netmask'} = &parse_hex($2);
32                 $ifc{'broadcast'} = $3;
33                 }
34         elsif ($l =~ s/inet\s+($bootip)\s+netmask\s+(\S+)// ||
35                $l =~ s/inet\s+(\S+)\s+netmask\s+(\S+)//) {
36                 $ifc{'address'} = $1;
37                 $ifc{'netmask'} = &parse_hex($2);
38                 }
39         else { next; }
40         if ($l =~ /ether\s+(\S+)/) { $ifc{'ether'} = $1; }
41         if ($l =~ /mtu\s+(\S+)/) { $ifc{'mtu'} = $1; }
42         $ifc{'up'}++ if ($l =~ /\<UP/);
43         $ifc{'edit'} = &iface_type($ifc{'name'}) =~ /ethernet|loopback/i;
44         $ifc{'index'} = scalar(@rv);
45         if ($ifc{'ether'}) {
46                 $ifc{'ether'} = join(":", map { length($_) == 1 ? "0".$_ : $_ }
47                                               split(/:/, $ifc{'ether'}));
48                 }
49         push(@rv, \%ifc);
50
51         # Add aliases as virtual interfaces. Try to match boot-time interface
52         # numbers where possible
53         local %vtaken = map { $_->{'virtual'}, 1 }
54                             grep { $_->{'name'} eq $vifc{'name'} &&
55                                    $_->{'virtual'} ne "" } @boot;
56         while($l =~ s/inet\s+(\S+)\s+netmask\s+(\S+)(\s+broadcast\s+(\S+))?//) {
57                 local %vifc = %ifc;
58                 $vifc{'address'} = $1;
59                 $vifc{'netmask'} = &parse_hex($2);
60                 $vifc{'broadcast'} = $4;
61                 $vifc{'up'} = 1;
62                 $vifc{'edit'} = $ifc{'edit'};
63                 local $boot = $boot{$vifc{'address'}};
64                 if ($boot) {
65                         $vifc{'virtual'} = $boot->{'virtual'};
66                         }
67                 else {
68                         for($vifc{'virtual'}=0; $vtaken{$vifc{'virtual'}};
69                                                 $vifc{'virtual'}++) { }
70                         }
71                 $vtaken{$vifc{'virtual'}}++;
72                 $vifc{'fullname'} = $vifc{'name'}.':'.$vifc{'virtual'};
73                 $vifc{'index'} = scalar(@rv);
74                 push(@rv, \%vifc);
75                 }
76         }
77 return @rv;
78 }
79
80 # activate_interface(&details)
81 # Create or modify an interface
82 sub activate_interface
83 {
84 local %act;
85 map { $act{$_->{'fullname'}} = $_ } &active_interfaces();
86 local $old = $act{$_[0]->{'fullname'}};
87 $act{$_[0]->{'fullname'}} = $_[0];
88 &interface_sync(\%act, $_[0]->{'name'}, $_[0]->{'fullname'});
89 }
90
91 # deactivate_interface(&details)
92 # Deactive an interface
93 sub deactivate_interface
94 {
95 local %act;
96 local @act = &active_interfaces();
97 if ($_[0]->{'virtual'} eq '') {
98         @act = grep { $_->{'name'} ne $_[0]->{'name'} } @act;
99         }
100 else {
101         @act = grep { $_->{'fullname'} ne $_[0]->{'fullname'} } @act;
102         }
103 map { $act{$_->{'fullname'}} = $_ } @act;
104 &interface_sync(\%act, $_[0]->{'name'}, $_[0]->{'fullname'});
105 }
106
107 # interface_sync(interfaces, name, changee)
108 sub interface_sync
109 {
110 # Remove all IP addresses except for the primary one (unless it is being edited)
111 local $pri = $_[0]->{$_[1]};
112 local $ifconfig = &has_command("ifconfig");
113 while(1) {
114         local $out;
115         &execute_command("$ifconfig $_[1]", undef, \$out);
116         last if ($out !~ /([\000-\377]*)\s+inet\s+(\d+\.\d+\.\d+\.\d+)/);
117         last if ($2 eq $pri->{'address'} && $_[2] ne $pri->{'fullname'});
118         &system_logged("$ifconfig $_[1] delete $2 >/dev/null 2>&1");
119         }
120
121 # Add them back again, except for the primary unless it is being changed
122 foreach $a (sort { $a->{'fullname'} cmp $b->{'fullname'} }
123                  grep { $_->{'name'} eq $_[1] } values(%{$_[0]})) {
124         next if ($a->{'fullname'} eq $pri->{'fullname'} &&
125                  $_[2] ne $pri->{'fullname'});
126         local $cmd = "$ifconfig $a->{'name'}";
127         if ($a->{'virtual'} ne '') {
128                 $cmd .= " alias $a->{'address'}";
129                 }
130         else {
131                 $cmd .= " $a->{'address'}";
132                 }
133         if ($a->{'netmask'}) { $cmd .= " netmask $a->{'netmask'}"; }
134         if ($a->{'broadcast'}) { $cmd .= " broadcast $a->{'broadcast'}"; }
135         if ($a->{'mtu'}) { $cmd .= " mtu $a->{'mtu'}"; }
136         local $out = &backquote_logged("$cmd 2>&1");
137         #if ($? && $out !~ /file exists/i) {
138         if ($?) {
139                 &error($out);
140                 }
141         if ($a->{'virtual'} eq '') {
142                 if ($a->{'up'}) { $out = &backquote_logged("$ifconfig $a->{'name'} up 2>&1"); }
143                 else { $out = &backquote_logged("$ifconfig $a->{'name'} down 2>&1"); }
144                 &error($out) if ($?);
145                 }
146         }
147 }
148
149 # boot_interfaces()
150 # Returns a list of interfaces brought up at boot time
151 sub boot_interfaces
152 {
153 local %rc = &get_rc_conf();
154 local @rv;
155 foreach $r (keys %rc) {
156         local $v = $rc{$r};
157         local %ifc;
158         if ($r =~ /^ifconfig_([a-z0-9]+)$/) {
159                 # Non-virtual interface
160                 %ifc = ( 'name' => $1,
161                          'fullname' => $1 );
162                 }
163         elsif ($r =~ /^ifconfig_([a-z0-9]+)_alias(\d+)$/) {
164                 # Virtual interface
165                 %ifc = ( 'name' => $1,
166                          'virtual' => $2,
167                          'fullname' => "$1:$2" );
168                 }
169         else { next; }
170
171         if ($v =~ /^inet\s+(\S+)/ || /^([0-9\.]+)/) {
172                 $ifc{'address'} = $1;
173                 }
174         else { next; }
175         local @a = split(/\./, $ifc{'address'});
176         if ($v =~ /netmask\s+(0x\S+)/) {
177                 $ifc{'netmask'} = &parse_hex($1);
178                 }
179         elsif ($v =~ /netmask\s+([0-9\.]+)/) {
180                 $ifc{'netmask'} = $1;
181                 }
182         else {
183                 $ifc{'netmask'} = $a[0] >= 192 ? "255.255.255.0" :
184                                   $a[0] >= 128 ? "255.255.0.0" :
185                                                  "255.0.0.0";
186                 }
187         if ($v =~ /broadcast\s+(0x\S+)/) {
188                 $ifc{'broadcast'} = &parse_hex($1);
189                 }
190         elsif ($v =~ /broadcast\s+([0-9\.]+)/) {
191                 $ifc{'broadcast'} = $1;
192                 }
193         else {
194                 local @n = split(/\./, $ifc{'netmask'});
195                 $ifc{'broadcast'} = sprintf "%d.%d.%d.%d",
196                                         ($a[0] | ~int($n[0]))&0xff,
197                                         ($a[1] | ~int($n[1]))&0xff,
198                                         ($a[2] | ~int($n[2]))&0xff,
199                                         ($a[3] | ~int($n[3]))&0xff;
200                 }
201         $ifc{'mtu'} = $1 if ($v =~ /mtu\s+(\d+)/);
202         $ifc{'up'} = 1;
203         $ifc{'edit'} = 1;
204         $ifc{'index'} = scalar(@rv);
205         $ifc{'file'} = "/etc/rc.conf";
206         push(@rv, \%ifc);
207         }
208 return @rv;
209 }
210
211 # save_interface(&details)
212 # Create or update a boot-time interface
213 sub save_interface
214 {
215 local $str = "inet $_[0]->{'address'}";
216 $str .= " netmask $_[0]->{'netmask'}" if ($_[0]->{'netmask'});
217 $str .= " broadcast $_[0]->{'broadcast'}" if ($_[0]->{'broadcast'});
218 &lock_file("/etc/rc.conf");
219 if ($_[0]->{'virtual'} eq '') {
220         &save_rc_conf('ifconfig_'.$_[0]->{'name'}, $str);
221         }
222 else {
223         local @boot = &boot_interfaces();
224         local ($old) = grep { $_->{'fullname'} eq $_[0]->{'fullname'} } @boot;
225         if (!$old && $_[0]->{'virtual'} ne '') {
226                 # A new virtual interface .. pick a virtual number automaticlly
227                 local $b;
228                 $_[0]->{'virtual'} = 0;
229                 foreach $b (&boot_interfaces()) {
230                         if ($b->{'name'} eq $_[0]->{'name'} &&
231                             $b->{'virtual'} ne '' &&
232                             $b->{'virtual'} >= $_[0]->{'virtual'}) {
233                                 $_[0]->{'virtual'} = $b->{'virtual'}+1;
234                                 }
235                         }
236                 $_[0]->{'fullname'} = $_[0]->{'name'}.':'.$_[0]->{'virtual'};
237                 }
238         &save_rc_conf('ifconfig_'.$_[0]->{'name'}.'_alias'.$_[0]->{'virtual'},
239                       $str);
240         }
241 &unlock_file("/etc/rc.conf");
242 }
243
244 # delete_interface(&details, [noshift])
245 # Delete a boot-time interface
246 sub delete_interface
247 {
248 &lock_file("/etc/rc.conf");
249 if ($_[0]->{'virtual'} eq '') {
250         # Remove the real interface
251         &save_rc_conf('ifconfig_'.$_[0]->{'name'});
252         }
253 else {
254         # Remove a virtual interface, and shift down all aliases above it
255         &save_rc_conf('ifconfig_'.$_[0]->{'name'}.'_alias'.$_[0]->{'virtual'});
256         if (!$_[1]) {
257                 local ($b, %lastb);
258                 foreach $b (&boot_interfaces()) {
259                         if ($b->{'name'} eq $_[0]->{'name'} &&
260                             $b->{'virtual'} ne '' &&
261                             $b->{'virtual'} > $_[0]->{'virtual'}) {
262                                 # This one needs to be shifted down
263                                 %lastb = %$b;
264                                 $b->{'virtual'}--;
265                                 &save_interface($b);
266                                 }
267                         }
268                 &delete_interface(\%lastb, 1) if (%lastb);
269                 }
270         }
271 &unlock_file("/etc/rc.conf");
272 }
273
274 # iface_type(name)
275 # Returns a human-readable interface type name
276 sub iface_type
277 {
278 return  $_[0] =~ /^tun/ ? "Loopback tunnel" :
279         $_[0] =~ /^sl/ ? "SLIP" :
280         $_[0] =~ /^ppp/ ? "PPP" :
281         $_[0] =~ /^lo/ ? "Loopback" :
282         $_[0] =~ /^ar/ ? "Arnet" :
283         $_[0] =~ /^(wlan|athi|ral)/ ? "Wireless ethernet" :
284         $_[0] =~ /^(bge|em|myk)/ ? "Gigabit ethernet" :
285         $_[0] =~ /^(ax|mx|nve|pn|rl|tx|wb|nfe|sis)/ ? "Fast ethernet" :
286         $_[0] =~ /^(cs|dc|de|ed|el|ex|fe|fxp|ie|le|lnc|tl|vr|vx|xl|ze|zp)/ ? "Ethernet" : $text{'ifcs_unknown'};
287 }
288
289 # iface_hardware(name)
290 # Does some interface have an editable hardware address
291 sub iface_hardware
292 {
293 return 0;
294 }
295
296 # can_edit(what)
297 # Can some boot-time interface parameter be edited?
298 sub can_edit
299 {
300 return $_[0] =~ /netmask|broadcast/;
301 }
302
303 # valid_boot_address(address)
304 # Is some address valid for a bootup interface
305 sub valid_boot_address
306 {
307 return &check_ipaddress($_[0]);
308 }
309
310 # get_dns_config()
311 # Returns a hashtable containing keys nameserver, domain, search & order
312 sub get_dns_config
313 {
314 local $dns;
315 &open_readfile(RESOLV, "/etc/resolv.conf");
316 while(<RESOLV>) {
317         s/\r|\n//g;
318         s/#.*$//g;
319         if (/nameserver\s+(.*)/) {
320                 push(@{$dns->{'nameserver'}}, split(/\s+/, $1));
321                 }
322         elsif (/domain\s+(\S+)/) {
323                 $dns->{'domain'} = [ $1 ];
324                 }
325         elsif (/search\s+(.*)/) {
326                 $dns->{'domain'} = [ split(/\s+/, $1) ];
327                 }
328         }
329 close(RESOLV);
330
331 local @order;
332 local $orderfile;
333 if (-r "/etc/nsswitch.conf") {
334         # FreeBSD 5.0 and later use nsswitch.conf
335         $orderfile = "/etc/nsswitch.conf";
336         &open_readfile(SWITCH, $orderfile);
337         while(<SWITCH>) {
338                 s/\r|\n//g;
339                 if (/^\s*hosts:\s+(.*)/) {
340                         $dns->{'order'} = $1;
341                         }
342                 }
343         close(SWITCH);
344         }
345 else {
346         # Older versions use host.conf
347         $orderfile = "/etc/host.conf";
348         &open_readfile(HOST, $orderfile);
349         while(<HOST>) {
350                 s/\r|\n//g;
351                 s/#.*$//;
352                 push(@order, $_) if (/\S/);
353                 }
354         close(HOST);
355         $dns->{'order'} = join(" ", @order);
356         }
357 $dns->{'files'} = [ "/etc/resolv.conf", $orderfile ];
358 return $dns;
359 }
360
361 # save_dns_config(&config)
362 # Writes out the resolv.conf and host.conf files
363 sub save_dns_config
364 {
365 &lock_file("/etc/resolv.conf");
366 &open_readfile(RESOLV, "/etc/resolv.conf");
367 local @resolv = <RESOLV>;
368 close(RESOLV);
369 &open_tempfile(RESOLV, ">/etc/resolv.conf");
370 foreach (@{$_[0]->{'nameserver'}}) {
371         print RESOLV "nameserver $_\n";
372         }
373 if ($_[0]->{'domain'}) {
374         if ($_[0]->{'domain'}->[1]) {
375                 &print_tempfile(RESOLV, "search ",join(" ", @{$_[0]->{'domain'}}),"\n");
376                 }
377         else {
378                 &print_tempfile(RESOLV, "domain $_[0]->{'domain'}->[0]\n");
379                 }
380         }
381 foreach (@resolv) {
382         &print_tempfile(RESOLV, $_) if (!/^\s*(nameserver|domain|search)\s+/);
383         }
384 &close_tempfile(RESOLV);
385 &unlock_file("/etc/resolv.conf");
386
387 if (-r "/etc/nsswitch.conf") {
388         # Save to new nsswitch.conf, for FreeBSD 5.0 and later
389         &lock_file("/etc/nsswitch.conf");
390         &open_readfile(SWITCH, "/etc/nsswitch.conf");
391         local @switch = <SWITCH>;
392         close(SWITCH);
393         &open_tempfile(SWITCH, ">/etc/nsswitch.conf");
394         foreach (@switch) {
395                 if (/^\s*hosts:\s+/) {
396                         &print_tempfile(SWITCH, "hosts:\t$_[0]->{'order'}\n");
397                         }
398                 else {
399                         &print_tempfile(SWITCH, $_);
400                         }
401                 }
402         &close_tempfile(SWITCH);
403         &unlock_file("/etc/nsswitch.conf");
404         }
405 else {
406         # Save to older host.conf
407         &open_lock_tempfile(HOST, ">/etc/host.conf");
408         foreach my $o (split(/\s+/, $_[0]->{'order'})) {
409                 &print_tempfile(HOST, $o,"\n");
410                 }
411         &close_tempfile(HOST);
412         }
413 }
414
415 $max_dns_servers = 3;
416
417 # order_input(&dns)
418 # Returns HTML for selecting the name resolution order
419 sub order_input
420 {
421 if (-r "/etc/nsswitch.conf") {
422         # FreeBSD 5.0 and later use nsswitch.conf with more options
423         return &common_order_input("order", $_[0]->{'order'},
424                 [ [ "files", "Files" ], [ "dns", "DNS" ],
425                   [ "nis", "NIS" ], [ "cache", "NSCD" ] ]);
426         }
427 else {
428         # Older FreeBSD's have fewer options
429         local $dnsopt = $_[0]->{'order'} =~ /dns/ ? 'dns' : 'bind';
430         return &common_order_input("order", $_[0]->{'order'},
431                 [ [ "hosts", "Hosts" ], [ $dnsopt, "DNS" ], [ "nis", "NIS" ] ]);
432         }
433 }
434
435 # parse_order(&dns)
436 # Parses the form created by order_input()
437 sub parse_order
438 {
439 if (defined($in{'order'})) {
440         $in{'order'} =~ /\S/ || &error($text{'dns_eorder'});
441         $_[0]->{'order'} = $in{'order'};
442         }
443 else {
444         local($i, @order);
445         for($i=0; defined($in{"order_$i"}); $i++) {
446                 push(@order, $in{"order_$i"}) if ($in{"order_$i"});
447                 }
448         $_[0]->{'order'} = join(" ", @order);
449         }
450 }
451
452 # get_hostname()
453 sub get_hostname
454 {
455 local %rc = &get_rc_conf();
456 if ($rc{'hostname'}) {
457         return $rc{'hostname'};
458         }
459 return &get_system_hostname();
460 }
461
462 # save_hostname(name)
463 sub save_hostname
464 {
465 &lock_file("/etc/rc.conf");
466 &system_logged("hostname $_[0] >/dev/null 2>&1");
467 &save_rc_conf('hostname', $_[0]);
468 &unlock_file("/etc/rc.conf");
469 undef(@main::get_system_hostname);      # clear cache
470 }
471
472 sub routing_config_files
473 {
474 return ( "/etc/defaults/rc.conf", "/etc/rc.conf" );
475 }
476
477 sub routing_input
478 {
479 local %rc = &get_rc_conf();
480
481 # Default router
482 local $defr = $rc{'defaultrouter'};
483 print &ui_table_row($text{'routes_default'},
484         &ui_opt_textbox("defr", $defr eq 'NO' ? '' : $defr, 20,
485                         $text{'routes_none'}));
486
487 # Act as router?
488 local $gw = $rc{'gateway_enable'};
489 print &ui_table_row($text{'routes_forward'},
490         &ui_radio("gw", $gw || 'NO', [ [ 'YES', $text{'yes'} ],
491                                        [ 'NO', $text{'no'} ] ]));
492
493 # Run route discovery
494 local $rd = $rc{'router_enable'};
495 print &ui_table_row($text{'routes_routed'},
496         &ui_radio("rd", $rd || 'NO', [ [ 'YES', $text{'yes'} ],
497                                        [ 'NO', $text{'no'} ] ]));
498 }
499
500 sub parse_routing
501 {
502 &lock_file("/etc/rc.conf");
503 $in{'defr_def'} || &check_ipaddress($in{'defr'}) ||
504         &error(&text('routes_edefault', $in{'defr'}));
505 &save_rc_conf('defaultrouter', $in{'defr_def'} ? 'NO' : $in{'defr'});
506 &save_rc_conf('gateway_enable', $in{'gw'});
507 &save_rc_conf('router_enable', $in{'rd'});
508 &unlock_file("/etc/rc.conf");
509 }
510
511 # save_rc_conf(name, value)
512 sub save_rc_conf
513 {
514 local $found;
515 &open_readfile(CONF, "/etc/rc.conf");
516 local @conf = <CONF>;
517 close(CONF);
518 &open_tempfile(CONF, ">/etc/rc.conf");
519 foreach (@conf) {
520         if (/^\s*([^=]+)\s*=\s*(.*)/ && $1 eq $_[0]) {
521                 &print_tempfile(CONF, "$_[0]=\"$_[1]\"\n") if (@_ > 1);
522                 $found++;
523                 }
524         else {
525                 &print_tempfile(CONF, $_);
526                 }
527         }
528 if (!$found && @_ > 1) {
529         &print_tempfile(CONF, "$_[0]=\"$_[1]\"\n");
530         }
531 &close_tempfile(CONF);
532 }
533
534 # get_rc_conf()
535 sub get_rc_conf
536 {
537 local ($file, %rv);
538 foreach $file ("/etc/defaults/rc.conf",
539                glob("/etc/rc.conf.d/*"),
540                "/etc/rc.conf") {
541         &open_readfile(FILE, $file);
542         while(<FILE>) {
543                 s/\r|\n//g;
544                 s/#.*$//;
545                 if (/^\s*([^=\s]+)\s*=\s*"(.*)"/ ||
546                     /^\s*([^=\s]+)\s*=\s*(\S+)/) {
547                         $rv{$1} = $2;
548                         }
549                 }
550         close(FILE);
551         }
552 return %rv;
553 }
554
555 # apply_network()
556 # Apply the interface and routing settings
557 sub apply_network
558 {
559 local $oldpwd = &get_current_dir();
560 chdir("/");
561
562 # Take down all active alias interfaces, and any that no longer exist
563 local %boot = map { $_->{'fullname'}, $_ } &boot_interfaces();
564 foreach my $i (&active_interfaces()) {
565         if ($i->{'virtual'} ne '' || !$boot{$i->{'fullname'}}) {
566                 &deactivate_interface($i);
567                 }
568         }
569 # Bring everything up
570 &system_logged("/etc/netstart >/dev/null 2>&1");
571 chdir($oldpwd);
572 }
573
574 sub os_feedback_files
575 {
576 return ( "/etc/rc.conf", "/etc/resolv.conf", "/etc/host.conf",
577          "/etc/resolv.conf" );
578 }
579
580 # supports_address6([&iface])
581 # Returns 1 if managing IPv6 interfaces is supported
582 sub supports_address6
583 {
584 local ($iface) = @_;
585 return 0;
586 }
587
588 1;
589