Handle hostnames with upper-case letters
[webmin.git] / net / openbsd-lib.pl
1 # freebsd-lib.pl
2 # Networking functions for FreeBSD
3
4 # active_interfaces()
5 # Returns a list of currently ifconfig'd interfaces
6 sub active_interfaces
7   {
8       local(@rv, @lines, $l);
9       &open_execute_command(IFC, "ifconfig -A", 1, 1);
10       while(<IFC>) {
11           s/\r|\n//g;
12           if (/^\S+:/) { push(@lines, $_); }
13           else { $lines[$#lines] .= $_; }
14       }
15       close(IFC);
16       foreach $l (@lines) {
17           local %ifc;
18           $l =~ /^([^:\s]+):/;
19           $ifc{'name'} = $ifc{'fullname'} = $1;
20           if ($l =~ /^(\S+):(\d+):\s/) { $ifc{'virtual'} = $2; }
21           if ($l =~ s/inet\s+(\S+)\s+netmask\s+(\S+)\s+broadcast\s+(\S+)//) {
22               $ifc{'address'} = $1;
23               $ifc{'netmask'} = &parse_hex($2);
24               $ifc{'broadcast'} = $3;
25           }
26           elsif ($l =~ s/inet\s+(\S+)\s+netmask\s+(\S+)//) {
27               $ifc{'address'} = $1;
28               $ifc{'netmask'} = &parse_hex($2);
29           }
30           else { next; }
31           if ($l =~ /ether\s+(\S+)/) { $ifc{'ether'} = $1; }
32           if ($l =~ /mtu\s+(\S+)/) { $ifc{'mtu'} = $1; }
33           $ifc{'up'}++ if ($l =~ /\<UP/);
34           $ifc{'edit'} = &iface_type($ifc{'name'}) =~ /ethernet|loopback/i;
35           $ifc{'index'} = scalar(@rv);
36           if ($ifc{'ether'}) {
37               $ifc{'ether'} = join(":", map { sprintf "%2.2d", $_ }
38                                    split(/:/, $ifc{'ether'}));
39           }
40           push(@rv, \%ifc);
41           
42           # Add aliases as virtual interfaces
43           local $v = 0;
44           while($l =~ s/inet\s+(\S+)\s+netmask\s+(\S+)\s+broadcast\s+(\S+)//) {
45               local %vifc = %ifc;
46               $vifc{'address'} = $1;
47               $vifc{'netmask'} = &parse_hex($2);
48               $vifc{'broadcast'} = $3;
49               $vifc{'up'} = 1;
50               $vifc{'edit'} = $ifc{'edit'};
51               $vifc{'virtual'} = $v++;
52               $vifc{'fullname'} = $vifc{'name'}.':'.$vifc{'virtual'};
53               $vifc{'index'} = scalar(@rv);
54               push(@rv, \%vifc);
55           }
56       }
57       return @rv;
58   }
59
60 # activate_interface(&details)
61 # Create or modify an interface
62 sub activate_interface
63   {
64       local %act;
65       map { $act{$_->{'fullname'}} = $_ } &active_interfaces();
66       local $old = $act{$_[0]->{'fullname'}};
67       $act{$_[0]->{'fullname'}} = $_[0];
68       &interface_sync(\%act, $_[0]->{'name'});
69   }
70
71 # deactivate_interface(&details)
72 # Deactive an interface
73 sub deactivate_interface
74   {
75       local %act;
76       local @act = &active_interfaces();
77       if ($_[0]->{'virtual'} eq '') {
78           @act = grep { $_->{'name'} ne $_[0]->{'name'} } @act;
79       }
80       else {
81           @act = grep { $_->{'fullname'} ne $_[0]->{'fullname'} } @act;
82       }
83       map { $act{$_->{'fullname'}} = $_ } @act;
84       &interface_sync(\%act, $_[0]->{'name'});
85   }
86
87 # interface_sync(interfaces, name)
88 sub interface_sync
89   {
90       while(&backquote_command("ifconfig $_[1]") =~ /\s+inet\s+/) {
91           &system_logged("ifconfig $_[1] delete >/dev/null 2>&1");
92       }
93       foreach $a (sort { $a->{'fullname'} cmp $b->{'fullname'} }
94                   grep { $_->{'name'} eq $_[1] } values(%{$_[0]})) {
95           local $cmd = "ifconfig $a->{'name'}";
96           if ($a->{'virtual'} ne '') {
97               $cmd .= " alias $a->{'address'}";
98           }
99           else {
100               $cmd .= " $a->{'address'}";
101           }
102           if ($a->{'netmask'}) { $cmd .= " netmask $a->{'netmask'}"; }
103           if ($a->{'broadcast'}) { $cmd .= " broadcast $a->{'broadcast'}"; }
104           if ($a->{'mtu'}) { $cmd .= " mtu $a->{'mtu'}"; }
105           $msg .= "running $cmd<br>\n";
106           local $out = &backquote_logged("$cmd 2>&1");
107           if ($? && $out !~ /file exists/i) {
108               &error($out);
109           }
110           if ($a->{'virtual'} eq '') {
111               if ($a->{'up'}) { $out = &backquote_command("ifconfig $a->{'name'} up 2>&1"); }
112               else { $out = &backquote_logged("ifconfig $a->{'name'} down 2>&1"); }
113               &error($out) if ($?);
114           }
115       }
116   }
117
118
119 # boot_interfaces()
120 # Returns a list of interfaces brought up at boot time
121 sub boot_interfaces 
122   {
123       local @rv;
124       local @if_list = split(" ",
125         &backquote_command("echo -n /etc/hostname.*[!~]"));
126       
127       if ( $if_list[0] eq "/etc/hostname.*[!~]" )
128         { return @rv; }
129       
130       foreach $r (@if_list) {
131           local $if;
132           local $alias_cnt = 0;
133           
134           ($if = $r) =~ s/\/etc\/hostname\.//;
135           
136           &open_readfile( IF_FILE, $r)
137             or die "Could not open: $r";
138           
139           while(<IF_FILE>) {
140               local %ifc;
141               
142               if( ! /^inet .*|^dhcp/ )
143                 { next; }
144               
145               if( /^dhcp/ ) {
146                 %ifc = ( 'name' => $if,
147                          'fullname' => $if,
148                          'dhcp' => 1 );
149               } 
150               elsif( /alias/ ) {
151                   $_ =~ s/alias//;
152                   # Virtual interface
153                   %ifc = ( 'name' => $if,
154                            'virtual' => $alias_cnt,
155                            'fullname' => "$if:$alias_cnt" );
156                   $alias_cnt++;
157               }
158               else {
159                   # Non-virtual interface
160                   %ifc = ( 'name' => $if,
161                            'fullname' => $if );
162               }
163               
164               @_ = split;
165               
166               $ifc{'address'} = $_[1] if( $_[1] ne 'NONE' );
167               $ifc{'netmask'} = $_[2] if( $_[2] ne 'NONE' );
168               $ifc{'broadcast'} = $_[3] if( $_[3] ne 'NONE' );
169               $ifc{'up'} = 1;
170               $ifc{'edit'} = 1;
171               $ifc{'index'} = scalar(@rv);
172               $ifc{'file'} = $r;
173               push(@rv, \%ifc);
174           }
175           close( IF_FILE );
176       }
177       return @rv;
178   }
179
180 # save_interface(&details)
181 # Create or update a boot-time interface
182 sub save_interface 
183 {
184     local ($str, $lines, $found = 0);
185     local $if = $_[0]->{'name'};
186     local $alias_nr = $_[0]->{'virtual'};
187     
188     &lock_file("/etc/hostname.$if");
189     if ( $_[0]->{'dhcp'} ){ 
190       &open_tempfile( IF_FILE, ">/etc/hostname.".$if );
191       &print_tempfile(IF_FILE, "dhcp\n");
192       &close_tempfile( IF_FILE );
193       &unlock_file("/etc/hostname.$if");
194       return;
195     }
196
197     if ( $alias_nr eq '' ) {
198         $str = "inet ";
199         $alias_nr = 0;
200     }
201     else {
202         $str = "inet alias "; 
203         $alias_nr += 1;
204     }
205
206     $str .= $_[0]->{'address'};
207     $str .= " $_[0]->{'netmask'}" if ($_[0]->{'netmask'});
208     $str .= " $_[0]->{'broadcast'}" if ($_[0]->{'broadcast'});
209
210     $_ = &backquote_command("echo -n /etc/hostname.*");
211
212     if( /hostname.$if/ ) {
213         $lines = &read_file_lines( "/etc/hostname.".$if );
214         foreach $l (@$lines) {
215             $_ = $l;
216             if( ! /^inet |^dhcp/ )
217               { next; }
218             if( $alias_nr == 0 ) {
219                 $l = $str;
220                 $found = 1;
221                 last;
222             }
223             if( ! /alias/ )
224               { next; }
225             if( $alias_nr == 1 ) {
226                 $l = $str;
227                 $found = 1;
228                 last;
229             }
230             $alias_nr--;
231         }
232         if( $found == 0 ) {
233             push @$lines, ($str);
234         }
235         &flush_file_lines();
236     } 
237     else {
238         &open_tempfile( IF_FILE, ">/etc/hostname.".$if );
239         &print_tempfile(IF_FILE, $str, "\n");
240         &close_tempfile( IF_FILE );
241     }
242     &unlock_file("/etc/hostname.$if");
243
244 }
245
246 # delete_interface(&details)
247 # Delete a boot-time interface
248 sub delete_interface
249 {
250     local ($cnt = 0, $lines, $found = 0);
251     local $if = $_[0]->{'name'};
252     local $addr = $_[0]->{'address'};
253     
254     $_ = &backquote_command("echo -n /etc/hostname.*");
255
256     &lock_file("/etc/hostname.$if");
257     if( /hostname.$if/ ) {
258         $lines = &read_file_lines( "/etc/hostname.".$if );
259         foreach $l (@$lines) {
260             $_ = $l;
261             $cnt++;
262             if ( /^\#/ )
263               { next; }
264             $found++;
265             if( ! /^inet / )
266               { next; }
267             if( /$addr/ )
268               { splice @$lines, $cnt-1, 1; }
269         }
270         &flush_file_lines();
271         # check if we deleted the only entry in the file
272         # if so delete the file (otherwise dhcp will be used for the interface)
273         if( $found == 1 ) {
274             &unlink_logged("/etc/hostname.".$if);
275         }
276     } 
277     &unlock_file("/etc/hostname.$if");
278 }
279
280 # iface_type(name)
281 # Returns a human-readable interface type name
282 sub iface_type
283   {
284       return    $_[0] =~ /^tun/ ? "Loopback tunnel" :
285         $_[0] =~ /^sl/ ? "SLIP" :
286           $_[0] =~ /^ppp/ ? "PPP" :
287             $_[0] =~ /^lo/ ? "Loopback" :
288               $_[0] =~ /^ar/ ? "Arnet" :
289                 $_[0] =~ /^(aue|cue|kue)/ ? "USB ethernet" :
290                   $_[0] =~ /^(sk|ti|wx)/ ? "Gigabit ethernet" :
291                     $_[0] =~ /^(al|ax|be|mx|qe|qec|rl|sf|sis|ste|tx|wb)/ ? "Fast ethernet" :
292                       $_[0] =~ /^(ae|cs|dc|de|ec|ed|eg|el|en|ep|es|ex|fxp|hme|ie|il|ix|iy|le|mc|ne|np|qn|sm|sn|tl|vr|vx|we|xe|xl|ze|zp)/ ? "Ethernet" : $text{'ifcs_unknown'};
293   }
294
295 # iface_hardware(name)
296 # Does some interface have an editable hardware address
297 sub iface_hardware
298   {
299       return 0;
300   }
301
302 # can_edit(what)
303 # Can some boot-time interface parameter be edited?
304 sub can_edit
305   {
306       return $_[0] =~ /netmask|broadcast|dhcp/;
307   }
308
309 # valid_boot_address(address)
310 # Is some address valid for a bootup interface
311 sub valid_boot_address
312   {
313       return &check_ipaddress($_[0]);
314   }
315
316 # get_dns_config()
317 # Returns a hashtable containing keys nameserver, domain, search & order
318 sub get_dns_config
319   {
320       local $dns;
321       &open_readfile(RESOLV, "/etc/resolv.conf");
322       while(<RESOLV>) {
323           s/\r|\n//g;
324           s/#.*$//g;            
325           if (/nameserver\s+(.*)/) {
326               push(@{$dns->{'nameserver'}}, split(/\s+/, $1));
327           }
328           elsif (/search\s+(.*)/) {
329               $dns->{'domain'} = [ split(/\s+/, $1) ];
330           }
331           elsif (/lookup\s+(.*)/) {
332               $dns->{'order'} = [ split(/\s+/, $1) ];
333           }
334       }
335       close(RESOLV);
336       
337       $dns->{'files'} = [ "/etc/resolv.conf" ];
338       return $dns;
339   }
340
341 # save_dns_config(&config)
342 # Writes out the resolv.conf file
343 sub save_dns_config
344   {
345       &lock_file("/etc/resolv.conf");
346       &open_readfile(RESOLV, "/etc/resolv.conf");
347       local @resolv = <RESOLV>;
348       close(RESOLV);
349       &open_tempfile(RESOLV, ">/etc/resolv.conf");
350       foreach (@{$_[0]->{'nameserver'}}) {
351           &print_tempfile(RESOLV, "nameserver $_\n");
352       }
353       if ($_[0]->{'domain'}) {
354           &print_tempfile(RESOLV, "search ",join(" ", @{$_[0]->{'domain'}}),"\n");
355       }
356       foreach (@resolv) {
357           &print_tempfile(RESOLV, $_) if (!/^\s*(nameserver|search|lookup)\s+/);
358       }
359       &print_tempfile(RESOLV, "lookup ");
360       foreach (@{$_[0]->{'order'}}) {
361           &print_tempfile(RESOLV, $_," ");
362       }
363       &print_tempfile(RESOLV, "\n");
364       &close_tempfile(RESOLV);
365       &unlock_file("/etc/resolv.conf");
366       
367   }
368
369 $max_dns_servers = 3;
370
371 # order_input(&dns)
372 # Returns HTML for selecting the name resolution order
373 sub order_input
374 {
375 return &common_order_input("order", $_[0]->{'order'},
376         [ [ "file", "Hosts" ], [ "bind", "DNS" ], [ "yp", "NIS" ] ]);
377 }
378
379 # parse_order(&dns)
380 # Parses the form created by order_input()
381 sub parse_order
382   {
383       local($i, @order);
384       for($i=0; defined($in{"order_$i"}); $i++) {
385           push(@order, $in{"order_$i"}) if ($in{"order_$i"});
386       }
387       $_[0]->{'order'} = \@order;
388   }
389
390 # get_hostname()
391 sub get_hostname
392 {
393 local $hn = &read_file_contents("/etc/myname");
394 $hn =~ s/\r|\n//g;
395 if ($hn) {
396         return $hn;
397         }
398 return &get_system_hostname();
399 }
400
401 # save_hostname(name)
402 sub save_hostname
403   {
404       &system_logged("hostname $_[0] >/dev/null 2>&1");
405       &open_lock_tempfile(MYNAME, ">/etc/myname");
406       &print_tempfile(MYNAME, $_[0],"\n");
407       &close_tempfile(MYNAME);
408         undef(@main::get_system_hostname);      # clear cache
409   }
410
411 sub set_line {
412     local ($l, $lines, $found = 0);
413     local $pat = $_[1];
414     local $news = $_[2];
415
416     $lines = read_file_lines($_[0]);
417     foreach $l (@$lines) {
418         $_ = $l;
419         if( /$pat/ ) {
420             $found = 1;
421             $l = $news;
422         }
423     } 
424     if( ! $found ) {
425         push @$lines, ($news);
426     }
427
428     &flush_file_lines();
429 }
430
431 sub read_routing {
432     $defr = '';
433     &open_readfile(DEFR, "/etc/mygate");
434     while(<DEFR>) {
435         $defr .= $_;
436     }
437     close(DEFR);
438
439     local %sysctl;
440     read_file("/etc/sysctl.conf", \%sysctl);
441     $gw = "$sysctl{'net.inet.ip.forwarding'}";
442     $gw =~ s/\s*\#.*//; 
443     $gw = "0" if( $gw eq '' );
444
445     local %rc;
446     read_file("/etc/rc.conf",\%rc);
447     $rd = $rc{'routed_flags'};
448     $rd =~ s/\s*\#.*//; 
449     $rd = "NO" if( $rd eq '' );
450 }
451
452 sub routing_config_files
453 {
454 return ( "/etc/mygate", "/etc/sysctl.conf", "/etc/rc.conf" );
455 }
456
457 sub routing_input
458 {
459 &read_routing;
460
461 # Default router
462 print &ui_table_row($text{'routes_default'},
463         &ui_opt_textbox("defr", $defr eq 'NO' ? '' : $defr, 20,
464                         $text{'routes_none'}));
465
466 # Act as router?
467 print &ui_table_row($text{'routes_forward'},
468         &ui_radio("gw", $gw || 0, [ [ 1, $text{'yes'} ],
469                                     [ 0, $text{'no'} ] ]));
470
471 # Run route discovery
472 print &ui_table_row($text{'routes_routed'},
473         &ui_radio("rd", $rd || '-q', [ [ '-q', $text{'yes'} ],
474                                        [ 'NO', $text{'no'} ] ]));
475 }
476
477 sub parse_routing
478   {
479       $in{'defr_def'} || &check_ipaddress($in{'defr'}) ||
480         &error(&text('routes_edefault', $in{'defr'}));
481
482       &read_routing;
483
484       &lock_file("/etc/mygate");
485       if ( $in{'defr_def'} && -f "/etc/mygate" ) {
486           &unlink_file("/etc/mygate");
487       }
488       else {
489           if( $in{'defr'} ne $defr ) {
490                 &open_tempfile(MYGATE, ">/etc/mygate");
491                 &print_tempfile(MYGATE, $in{'defr'},"\n");
492                 &close_tempfile(MYDATE);
493           }
494       }
495       &unlock_file("/etc/mygate");
496       
497       if( $in{'gw'} ne $gw ) {
498           &set_line( "/etc/sysctl.conf", "^net.inet.ip.forwarding", "net.inet.ip.forwarding=$in{'gw'}" );
499       }
500
501       if( $in{'rd'} ne $rd ) {
502           &set_line( "/etc/rc.conf", "^routed_flags", "routed_flags=$in{'rd'}" );
503       }
504   }
505
506 sub os_feedback_files
507 {
508 return ( split(" ", `echo -n /etc/hostname.*[!~]`),
509          "/etc/resolv.conf", "/etc/myname", "/etc/mygate", "/etc/rc.conf",
510          "/etc/sysctl.conf" );
511 }
512
513 # supports_address6([&iface])
514 # Returns 1 if managing IPv6 interfaces is supported
515 sub supports_address6
516 {
517 local ($iface) = @_;
518 return 0;
519 }
520
521 1;
522