Handle hostnames with upper-case letters
[webmin.git] / net / cygwin-lib.pl
1 # cygwin-lib.pl
2 # Networking functions for cygwin
3 #
4 # TODO:
5 # * detect when netsh isn't available
6 # * save domain list
7 # * save domainname
8
9 my $logfile = "/dev/null";
10 #my $logfile = "/tmp/debugwb";
11
12 #define variables that modify the behavior of the .cgi scripts
13 #that are different than any other OS.
14 $noos_support_add_ifcs = 1; #Windows doesn't supporting adding interfaces
15 $noos_support_delete_ifcs = 1; #Windows doesn't supporting deleting interfaces
16 $always_apply_ifcs = 1; #Changes made to interfaces are always applied
17 $routes_active_now = 1; #Changes made to routes are always applied
18 #Note: some changes Windows requires a reboot, some don't.
19 #TODO2: determine which changes require a reboot.
20
21 # active_interfaces()
22 # Returns a list of currently ifconfig'd interfaces
23 # ifc keys: 'name','fullname','virtual','address','netmask','broadcast',
24 #           'ether','mtu','up','edit','index','dhcp'
25 sub active_interfaces
26 {
27     local(@rv, @lines, $line);
28     &open_execute_command(IFC, "ipconfig /all", 1, 1);
29     while (<IFC>) {
30         s/\r|\n//g;
31         push(@lines, $_);
32     }
33     close(IFC);
34     #Need to get the list of boottime interfaces, because ipconfig /all
35     #doesn't return ipaddr if cable is disconnected
36     my @bootifs = boot_interfaces();
37     my %ifc = ();
38     foreach $line (@lines) {
39         if ($line =~ /Ethernet adapter (.*):/) {
40             my $name = $1;
41             if (defined($ifc{'name'})) {
42                 #save the previous one
43                 $ifc{'index'} = scalar(@rv);
44                 local ($a1, $a2, $a3, $a4) = ($1, $2, $3, $4);
45                 if ($ifc{'address'}) {
46                     $ifc{'netmask'} =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
47                     $ifc{'broadcast'} = sprintf("%d.%d.%d.%d",
48                                                 ($a1 | ~int($1))&0xff,
49                                                 ($a2 | ~int($2))&0xff,
50                                                 ($a3 | ~int($3))&0xff,
51                                                 ($a4 | ~int($4))&0xff);
52                 }
53                 my %tmp = %ifc;
54                 push(@rv, \%tmp);
55             }
56             %ifc = ();
57             $ifc{'name'} = $ifc{'fullname'} = $name;
58         }
59         elsif ($line =~ /Media State.*: (.*)/) {
60             $ifc{'up'} = ($1 !~ /Disconnected/);
61             foreach (@bootifs) {
62                 if ($_->{'name'} eq $ifc{'name'}) {
63                     $ifc{'dhcp'} = $_->{'dhcp'};
64                     $ifc{'address'} = $_->{'address'};
65                     $ifc{'netmask'} = $_->{'netmask'};
66                 }
67             }
68         }
69         elsif ($line =~ /Description.*: (.*) \#(\d+)\s*$/) {
70             $ifc{'desc'} = $1;
71             $ifc{'index'} = $2;
72         }
73         elsif ($line =~ /Description.*: (.*)$/) {
74             $ifc{'desc'} = $1;
75             chop($ifc{'desc'});
76             $ifc{'num'} = 1;
77         }
78         elsif ($line =~ /Physical Address.*: (.+)$/) {
79             $ifc{'ether'} = $1;
80             $ifc{'ether'} =~ s/-/:/g;
81         }
82         elsif ($line =~ /IP Address.*: (.+)$/) {
83             $ifc{'address'} = $1;
84             $ifc{'up'} = 1 if ! defined $ifc{'up'};
85         }
86         elsif ($line =~ /Subnet Mask.*: (.+)$/) {
87             $ifc{'netmask'} = $1;
88         }
89         elsif ($line =~ /Default Gateway.*: (.+)$/) {
90             #this is used for the router subroutines below
91             $ifc{'gateway'} = $1;
92         }
93         elsif ($line =~ /DHCP Enabled.*: (.+)$/) {
94             $ifc{'dhcp'} = ($1 =~ /Yes/);
95         }
96     }
97     if (defined($ifc{'name'})) {
98         #save the last one
99         $ifc{'index'} = scalar(@rv);
100         local ($a1, $a2, $a3, $a4) = ($1, $2, $3, $4);
101         if ($ifc{'address'}) {
102             $ifc{'netmask'} =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
103             $ifc{'broadcast'} = sprintf("%d.%d.%d.%d",
104                                         ($a1 | ~int($1))&0xff,
105                                         ($a2 | ~int($2))&0xff,
106                                         ($a3 | ~int($3))&0xff,
107                                         ($a4 | ~int($4))&0xff);
108         }
109         my %tmp = %ifc;
110         push(@rv, \%tmp);
111     }
112     return @rv;
113 }
114
115 # activate_interface(&details)
116 # Create or modify an interface
117 sub activate_interface
118 {
119     save_interface($@);
120     #Windows doesn't support adding or removing interfaces
121 }
122
123 # apply_interface(&details)
124 # Save changes to an interface active now
125 sub apply_interface
126 {
127     save_interface($@);
128 }
129
130 # deactivate_interface(&details)
131 # Deactive an interface
132 sub deactivate_interface
133 {
134     #TODO2: determine how to deactivate an interface
135 }
136
137 # boot_interfaces()
138 # Returns a list of interfaces brought up at boot time
139 sub boot_interfaces
140 {
141     my @rv = ();
142     #It doesn't seem to really help to display the loopback since
143     #there's no mechanism in Windows to edit it.
144 #    push(@rv, { 'name' => 'lo0',
145 #               'fullname' => 'lo0',
146 #               'address' => '127.0.0.1',
147 #               'netmask' => '255.0.0.0',
148 #               'up' => 1,
149 #               'edit' => 0 });
150     my (@lines, $l);
151     &open_execute_command(IFC, "netsh interface ip dump", 1);
152     while (<IFC>) {
153         s/\r|\n//g;
154         push(@lines, $_);
155     }
156     close(IFC);
157     #my %ifc = ();
158     foreach $l (@lines) {
159         #TODO2: handle this message:
160         #"Cannot access configuration.
161         # Connection UI or someone else is accessing it."
162         if ($l =~ /^set address name = "(.*)" source = dhcp/) {
163             local %ifc;
164             $ifc{'fullname'} = $ifc{'name'} = $1;
165             $ifc{'index'} = scalar(@rv);
166             $ifc{'edit'}++;
167             $ifc{'dhcp'} = 1;
168             $ifc{'up'} = 1;
169             push(@rv, \%ifc);
170         } elsif ($l =~ /^set address name = "(.*)" source = static addr = ([\d\.]+) mask = ([\d\.]+)/) {
171             local %ifc;
172             $ifc{'fullname'} = $ifc{'name'} = $1;
173             $ifc{'address'} = $2;
174             $ifc{'netmask'} = $3;
175             $ifc{'index'} = scalar(@rv);
176             $ifc{'edit'}++;
177             $ifc{'address'} =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
178             local ($a1, $a2, $a3, $a4) = ($1, $2, $3, $4);
179             $ifc{'netmask'} =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/;
180             $ifc{'broadcast'} = sprintf("%d.%d.%d.%d",
181                                         ($a1 | ~int($1))&0xff,
182                                         ($a2 | ~int($2))&0xff,
183                                         ($a3 | ~int($3))&0xff,
184                                         ($a4 | ~int($4))&0xff);
185             $ifc{'dhcp'} = 0;
186             $ifc{'up'} = 1;
187             push(@rv, \%ifc);
188         } elsif ($l =~ /^set address name = "(.*)" gateway = ([\d\.]+) gwmetric = (\d)/) {
189             foreach (@rv) {
190                 if ($_->{'name'} eq $1) {
191                     $_->{'gateway'} = $2;
192                     $_->{'gwmetric'} = $3;
193                 }
194             }
195         }
196     }
197     return @rv;
198 }
199
200 # save_interface(&details)
201 # Create or update a boot-time interface
202 sub save_interface
203 {
204     my $ifc = $_[0];
205     my $cmd = "netsh interface ip set address name = \"" .
206         "$ifc->{'name'}\" source = ";
207     if ($ifc->{'dhcp'}) {
208         $cmd .= "dhcp";
209     } else {
210         $cmd .= "static addr = $ifc->{'address'} mask = $ifc->{'netmask'}";
211     }
212     system_logged("$cmd >$logfile 2>&1");
213 }
214
215 # delete_interface(&details)
216 # Delete a boot-time interface
217 sub delete_interface
218 {
219     #Windows doesn't support adding or removing interfaces
220 }
221
222 # iface_type(name)
223 # Returns a human-readable interface type name
224 sub iface_type
225 {
226 #TODO2
227 #return "Fast Ethernet" if
228 #return "Token Ring" if
229 #return "PPP" if
230 return "Loopback" if $_[0] =~ /^lo0$/;
231 return "Ethernet";
232 }
233
234 # iface_hardware(name)
235 # Does some interface have an editable hardware address
236 sub iface_hardware
237 {
238 #TODO2: PPP
239 return $_[0] !~ /^(lo\d)$/;
240 }
241
242 # can_edit(what)
243 # Can some boot-time interface parameter be edited?
244 sub can_edit
245 {
246 return $_[0] =~ /^(dhcp|netmask)$/;
247 }
248
249 # valid_boot_address(address)
250 # Is some address valid for a bootup interface
251 sub valid_boot_address
252 {
253 return &to_ipaddress($_[0]) ? 1 : 0;
254 }
255
256 # get_dns_config()
257 # Returns a hashtable containing keys nameserver, domain, order
258 sub get_dns_config
259 {
260     my @lines = ();
261     my $dns = {'domain' => []};
262     my $i = 0;
263     if (&open_execute_command(CMD, "ipconfig /all", 1)) {
264         my $doing_domain = 0;
265         while (<CMD>) {
266             s/[\n\r]//g;
267             if ($doing_domain) {
268                 if (/(Ethernet adapter|:)/) {
269                     $doing_domain = 0;
270                 } elsif (/^\s*([^:]+\.[^:]+)$/) {
271                     push(@{$dns->{"domain"}}, $1);
272                 }
273             }
274             if (/Primary DNS Suffix.*: (.*)/) {
275                 push(@{$dns->{"domain"}}, $1);
276             } elsif (/DNS Suffix Search List.*: (.*)/) {
277                 $doing_domain = 1;
278                 push(@{$dns->{"domain"}}, $1);
279             } elsif (/^Ethernet adapter (.*):/) {
280                 $dns->{"name"}[$i++] = $1;
281             }
282         }
283         close(CMD);
284     }
285     if (&open_execute_command(CMD, "netsh interface ip show dns", 1)) {
286         my $doing_nameserver = 0;
287         my $i = -1;
288         my $key = "nameserver";
289         while (<CMD>) {
290             s/\r|\n//g;
291             if ($doing_nameserver) {
292                 if (/(Configuration for interface|:)/) {
293                     $doing_nameserver = 0;
294                 } elsif (/^\s*([\d\.]+)/) {
295                     push(@{$dns->{$key}}, $1);
296                 }
297             }
298             if (/Configuration for interface "(.*)"/) {
299                 $dns->{'name'}[++$i] = $1;
300                 $key = "nameserver";
301                 $key .= $i if $i > 0;
302             } elsif (/Statically Configured DNS Servers:\s*([\d\.]+)/) {
303                 push(@{$dns->{$key}}, $1);
304                 $doing_nameserver = 1;
305             }
306         }
307         close(CMD);
308     }
309     return $dns;
310 }
311
312 # save_dns_config(&config)
313 # Configures the DNS settings
314 sub save_dns_config
315 {
316     my $dns = $_[0];
317     for ($i=0; $i < @{$dns->{'name'}}; $i++) {
318         my $key = "nameserver";
319         $key .= $i if $i > 0;
320         if (@{$dns->{$key}}) {
321             my $cmd_fmt = "netsh interface ip %s dns name = \"" .
322                 $dns->{'name'}[$i] . "\"%s addr = %s";
323             my $addr = pop(@{$dns->{$key}});
324             my $cmd = sprintf($cmd_fmt, "set", " source = static", $addr);
325             &system_logged("$cmd >$logfile 2>&1");
326             #add the new ones (any old list of adds was erased by the set cmd)
327             foreach (@{$dns->{$key}}) {
328                 $cmd = sprintf($cmd_fmt, "add", "", $_);
329                 &system_logged("$cmd >$logfile 2>&1");
330             }
331         } else {
332             #set it to be obtained automatically
333             my $cmd = "netsh interface ip set dns name = \"" .
334                 $dns->{'name'}[$i] . "\" source = dhcp";
335             &system_logged("$cmd >$logfile 2>&1");
336             #any old list of adds was erased by the set cmd
337         }
338     }
339     #TODO: support saving the domain list
340     #if ($_[0]->{'domain'}) {
341 }
342
343 $max_dns_servers = 16; #more is possible, but this is realistic
344
345 # order_input(&dns)
346 # Returns HTML for selecting the name resolution order
347 sub order_input
348 {
349 #TODO2
350 }
351
352 # parse_order(&dns)
353 # Parses the form created by order_input()
354 sub parse_order
355 {
356 #TODO2
357 }
358
359 # get_hostname()
360 sub get_hostname
361 {
362 return &get_system_hostname();
363 }
364
365 # save_hostname(name)
366 sub save_hostname
367 {
368 &system_logged("hostname $_[0] >/dev/null 2>&1");
369 undef(@main::get_system_hostname);      # clear cache
370 }
371
372 # get_domainname()
373 sub get_domainname
374 {
375 #TODO: determine how to get
376 return "";
377 }
378
379 # save_domainname(domain)
380 sub save_domainname
381 {
382 #TODO: determine how to set
383 }
384
385 sub routing_config_files
386 {
387 return map { $_->{'file'} } &boot_interfaces();
388 }
389
390 sub routing_input
391 {
392     # show default router(s) input
393     my @if = boot_interfaces();
394     my $i = 0;
395     foreach (@if) {
396         next if $_->{'address'} eq "127.0.0.1";
397         my $none_or_dhcp = defined($ifc{'gateway'}) ? 0 : 1;
398         my $desc = $_->{'name'} . ($_->{'dhcp'}? "" : " ($_->{'address'})");
399         print &ui_table_row("$desc $text{'routes_default'}",
400                 &ui_radio("gateway${i}_def", $none_or_dhcp,
401                   [ [ 1, $text{'routes_none'} ],
402                     [ 0, $text{'routes_gateway'}." ".
403                          &ui_textbox("gateway$i", $_->{'gateway'}, 15)." ".
404                          $text{'routes_gwmetric'}." ".
405                          &ui_textbox("gwmetric$i", $_->{'gwmetric'}, 4) ] ]).
406                 &ui_hidden("ifname${i}", $_->{'name'}));
407         $i++;
408     }
409 }
410
411 sub parse_routing
412 {
413     my $i = 0;
414     my @if = boot_interfaces();
415     while (defined($in{"gateway${i}_def"})) {
416         my $name = $in{"ifname$i"};
417         my $gateway = $in{"gateway$i"};
418         my $gwmetric = $in{"gwmetric$i"};
419         foreach (@if) {
420             if ($_->{'name'} eq $name) {
421                 if (! $in{"gateway${i}_def"}) {
422                     if ($gateway != $_->{'gateway'} ||
423                         $gwmetric != $_->{'gwmetric'}) {
424                         &check_ipaddress($gateway) ||
425                             &error(&text('routes_egateway', $gateway));
426                         my $cmd = "netsh interface ip set address name = \"" .
427                             $_->{'name'} . "\" gateway = $gateway " .
428                                 "gwmetric = $gwmetric";
429                         system_logged("$cmd > $logfile 2>&1");
430                     }
431                 } else {
432                     if (defined($_->{'gateway'})) {
433                         my $cmd = "netsh interface ip delete address name = \""
434                             . $_->{'name'} . "\" gateway = $_->{'gateway'}";
435                         system_logged("$cmd > $logfile 2>&1");
436                     }
437                 }
438             }
439         }
440         $i++;
441     }
442 }
443
444 # supports_address6([&iface])
445 # Returns 1 if managing IPv6 interfaces is supported
446 sub supports_address6
447 {
448 local ($iface) = @_;
449 return 0;
450 }
451
452 1;
453