Handle hostnames with upper-case letters
[webmin.git] / stunnel / stunnel-lib.pl
1 # stunnel-lib.pl
2 # Common functions for accessing inetd or xinetd
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7
8 if ($config{'stunnel_path'} =~ /([^\/]+)$/) {
9         $stunnel_shortname = $1;
10         }
11 $webmin_pem = "$config_directory/miniserv.pem";
12
13 if (&foreign_check("inetd")) {
14         &foreign_require("inetd", "inetd-lib.pl");
15         $has_inetd = 1;
16         }
17 if (&foreign_check("xinetd")) {
18         &foreign_require("xinetd", "xinetd-lib.pl");
19         $has_xinetd = 1;
20         }
21
22 # list_stunnels()
23 # Search the inetd and xinetd configurations for stunnel lines
24 sub list_stunnels
25 {
26 local @rv;
27 if ($has_inetd) {
28         # List tunnels from inetd.conf
29         local %portmap;
30         foreach $s (&foreign_call("inetd", "list_services")) {
31                 $portmap{$s->[1]} = $s;
32                 foreach $a (split(/\s+/, $s->[4])) {
33                         $portmap{$a} = $s;
34                         }
35                 }
36         foreach $i (&foreign_call("inetd", "list_inets")) {
37                 if ($i->[8] eq $config{'stunnel_path'} ||
38                     $i->[8] eq $stunnel_shortname) {
39                         push(@rv, { 'type' => 'inetd',
40                                     'name' => $i->[3],
41                                     'port' => $portmap{$i->[3]}->[2],
42                                     'user' => $i->[7],
43                                     'active' => $i->[1],
44                                     'command' => $i->[8],
45                                     'args' => $i->[9],
46                                     'index' => scalar(@rv),
47                                     'file' => $i->[10],
48                                     'line' => $i->[0] });
49                         }
50                 }
51         }
52 if ($has_xinetd) {
53         # List tunnels from xinetd.conf
54         foreach $x (&foreign_call("xinetd", "get_xinetd_config")) {
55                 next if ($x->{'name'} ne 'service');
56                 local $q = $x->{'quick'};
57                 if ($q->{'server'}->[0] eq $config{'stunnel_path'} ||
58                     $q->{'server'}->[0] eq $stunnel_shortname) {
59                         push(@rv, { 'type' => 'xinetd',
60                                     'name' => $x->{'value'},
61                                     'port' => &xinet_port($x),
62                                     'active' => $q->{'disable'}->[0] ne 'yes',
63                                     'command' => $q->{'server'}->[0],
64                                     'args' => join(" ", $q->{'server'}->[0],
65                                                 @{$q->{'server_args'}}),
66                                     'index' => scalar(@rv),
67                                     'file' => $x->{'file'},
68                                     'xindex' => $x->{'index'} } );
69                         }
70                 }
71         }
72 return @rv;
73 }
74
75 # create_tunnel(&tunnel)
76 sub create_stunnel
77 {
78 local ($pclash, $nclash);
79 if ($has_xinetd) {
80         # Check for xinet clash
81         foreach $x (&foreign_call("xinetd", "get_xinetd_config")) {
82                 next if ($x->{'name'} ne 'service');
83                 local $q = $x->{'quick'};
84                 &error(&text('save_exinetd', $_[0]->{'name'}))
85                         if ($x->{'value'} eq $_[0]->{'name'});
86                 &error(&text('save_export', $_[0]->{'port'}, $x->{'value'}))
87                         if (&xinet_port($x) == $_[0]->{'port'});
88                 }
89         }
90 if ($has_inetd) {
91         # Check if there is already a service for the port or with the name
92         ($pclash, $nclash) = &find_clashes($_[0]->{'name'}, $_[0]->{'port'});
93         if (!$pclash && $nclash) {
94                 # The name is taken, but on a different port
95                 &error(&text('save_enclash', $nclash->[2], $nclash->[1]));
96                 }
97         local $iname = $pclash ? $pclash->[1] : $_[0]->{'name'};
98
99         # Check if there is an inetd entry on the name
100         foreach $i (&foreign_call("inetd", "list_inets")) {
101                 &error(&text('save_einetd', $iname))
102                         if ($i->[3] eq $iname);
103                 }
104         }
105
106 local $addto = $_[0]->{'type'} ? $_[0]->{'type'} :
107                $has_xinetd ? "xinetd" : "inetd";
108 if ($addto eq 'xinetd') {
109         # Just add to xinetd.conf with a custom name and port
110         local $xinet = { 'name' => 'service',
111                          'values' => [ $_[0]->{'name'} ] };
112         &foreign_call("xinetd", "set_member_value", $xinet, "port",
113                       $_[0]->{'port'});
114         &foreign_call("xinetd", "set_member_value", $xinet, "socket_type",
115                       "stream");
116         &foreign_call("xinetd", "set_member_value", $xinet, "protocol",
117                       "tcp");
118         &foreign_call("xinetd", "set_member_value", $xinet, "user",
119                       "root");
120         &foreign_call("xinetd", "set_member_value", $xinet, "wait",
121                       "no");
122         &foreign_call("xinetd", "set_member_value", $xinet, "disable",
123                       $_[0]->{'active'} ? "no" : "yes");
124         &foreign_call("xinetd", "set_member_value", $xinet, "type",
125                       "UNLISTED");
126         &foreign_call("xinetd", "set_member_value", $xinet, "server",
127                       $_[0]->{'command'});
128         local $args = $_[0]->{'args'};
129         $args =~ s/^\S+\s+//;
130         &foreign_call("xinetd", "set_member_value", $xinet, "server_args",
131                       $args);
132         &foreign_call("xinetd", "create_xinet", $xinet);
133         }
134 elsif ($addto eq 'inetd') {
135         if ($pclash) {
136                 # Use existing /etc/services entry
137                 $_[0]->{'name'} = $pclash->[1];
138                 }
139         else {
140                 # Create /etc/services entry
141                 &foreign_call("inetd", "create_service", $_[0]->{'name'},
142                               $_[0]->{'port'}, "tcp", undef);
143                 }
144         # Create inetd.conf entry
145         &foreign_call("inetd", "create_inet", $_[0]->{'active'},
146                       $_[0]->{'name'}, "stream", "tcp", "nowait", "root",
147                       $_[0]->{'command'}, $_[0]->{'args'});
148         }
149 }
150
151 # delete_stunnel(&tunnel)
152 sub delete_stunnel
153 {
154 if ($_[0]->{'type'} eq 'inetd') {
155         # Delete from inetd.conf
156         &foreign_call("inetd", "delete_inet", $_[0]->{'line'}, $_[0]->{'file'});
157         if ($_[0]->{'port'} >= 1024) {
158                 # Delete the /etc/services entry as well
159                 local ($oserv) = &find_clashes($_[0]->{'name'},
160                                                $_[0]->{'port'});
161                 &foreign_call("inetd", "delete_service", $oserv->[0]);
162                 }
163         }
164 elsif ($_[0]->{'type'} eq 'xinetd') {
165         # Delete from xinetd
166         local @xinets = &foreign_call("xinetd", "get_xinetd_config");
167         local $xinet = $xinets[$_[0]->{'xindex'}];
168         &foreign_call("xinetd", "delete_xinet", $xinet);
169         }
170 }
171
172 # modify_stunnel(&oldtunnel, &newtunnel)
173 sub modify_stunnel
174 {
175 if ($_[0]->{'type'} eq 'inetd') {
176         # Check if the name or port has changed
177         local ($pclash, $nclash) =
178                 &find_clashes($_[1]->{'name'}, $_[1]->{'port'});
179         local ($oserv) = &find_clashes($_[0]->{'name'}, $_[0]->{'port'});
180         if ($_[0]->{'name'} ne $_[1]->{'name'}) {
181                 # The name has changed
182                 if ($nclash) {
183                         &error(&text('save_enclash', $nclash->[2],
184                                                      $_[1]->{'name'}));
185                         }
186                 }
187         if ($_[0]->{'port'} != $_[1]->{'port'}) {
188                 # The port has changed ..
189                 if ($pclash) {
190                         &error(&text('save_epclash', $_[1]->{'port'},
191                                                      $pclash->[1]));
192                         }
193                 }
194         &foreign_call("inetd", "modify_service", $oserv->[0],
195                       $_[1]->{'name'}, $_[1]->{'port'}, $oserv->[3],
196                       $oserv->[4]);
197
198         # Update inetd.conf
199         local @inets = &foreign_call("inetd", "list_inets");
200         local ($oi) = grep { $_->[0] eq $_[0]->{'line'} &&
201                              $_->[10] eq $_[0]->{'file'} } @inets;
202         &foreign_call("inetd", "modify_inet", $oi->[0],
203                       $_[1]->{'active'}, $_[1]->{'name'}, $oi->[4],
204                       $oi->[5], $oi->[6], $oi->[7], $oi->[8],
205                       $_[1]->{'args'}, $oi->[10]);
206         }
207 elsif ($_[0]->{'type'} eq 'xinetd') {
208         # Get the old xinetd config
209         local @xinets = &foreign_call("xinetd", "get_xinetd_config");
210         local $xinet = $xinets[$_[0]->{'xindex'}];
211
212         # Check for name clash
213         if ($_[0]->{'name'} ne $_[1]->{'name'}) {
214                 foreach $x (@xinets) {
215                         next if ($x->{'name'} ne 'service');
216                         &error(&text('save_exinetd', $_[1]->{'name'}))
217                                 if ($x->{'value'} eq $_[1]->{'name'});
218                         }
219                 }
220
221         # Check for port clash
222          if ($_[0]->{'port'} != $_[1]->{'port'}) {
223                 foreach $x (@xinets) {
224                         next if ($x->{'name'} ne 'service');
225                         &error(&text('save_export', $_[1]->{'port'},
226                                                     $x->{'value'}))
227                                 if (&xinet_port($x) == $_[1]->{'port'});
228                         }
229                 }
230
231         # If name or port has changed, convert to an UNLISTED service
232         if ($_[0]->{'name'} ne $_[1]->{'name'} ||
233             $_[0]->{'port'} != $_[1]->{'port'}) {
234                 $xinet->{'values'} = [ $_[1]->{'name'} ];
235                 &foreign_call("xinetd", "set_member_value", $xinet, "port",
236                               $_[1]->{'port'});
237                 &foreign_call("xinetd", "set_member_value", $xinet, "type",
238                               "UNLISTED");
239                 }
240
241         &foreign_call("xinetd", "set_member_value", $xinet, "disable",
242                       $_[1]->{'active'} ? "no" : "yes");
243         &foreign_call("xinetd", "set_member_value", $xinet, "server",
244                       $_[1]->{'command'});
245         local $args = $_[1]->{'args'};
246         $args =~ s/^\S+\s+//;
247         &foreign_call("xinetd", "set_member_value", $xinet, "server_args",
248                       $args);
249         &foreign_call("xinetd", "modify_xinet", $xinet);
250         }
251 }
252
253 # find_clashes(name, port)
254 sub find_clashes
255 {
256 local ($pclash, $nclash);
257 foreach $s (&foreign_call("inetd", "list_services")) {
258         local @aliases = split(/\s+/, $s->[4]);
259         $pclash = $s if ($s->[2] == $_[1]);
260         $nclash = $s if ($s->[1] eq $_[0] || &indexof($_[0], @aliases) >= 0);
261         }
262 return ($pclash, $nclash);
263 }
264
265 # xinet_port(&xinet)
266 sub xinet_port
267 {
268 local $q = $_[0]->{'quick'};
269 local $p = $q->{'port'};
270 return $p->[0] if ($p);
271 local @s = getservbyname($_[0]->{'value'}, $q->{'protocol'}->[0]);
272 return $s[2];
273 }
274
275 # lock_create_file()
276 # Lock the file to which new tunnels will be added
277 sub lock_create_file
278 {
279 if ($has_xinetd) {
280         local %iconfig = &foreign_config("xinetd");
281         &lock_file($iconfig{'xinetd_conf'});
282         }
283 elsif ($has_inetd) {
284         local %iconfig = &foreign_config("inetd");
285         &lock_file($iconfig{'inetd_conf_file'});
286         }
287 }
288
289 # get_stunnel_version(&out)
290 sub get_stunnel_version
291 {
292 local $out = `$config{'stunnel_path'} -V 2>&1`;
293 if ($?) {
294         $out = `$config{'stunnel_path'} -version 2>&1`;
295         }
296 ${$_[0]} = $out;
297 return $out =~ /stunnel\s+(\S+)/ ? $1 : undef;
298 }
299
300 # get_stunnel_config(file)
301 # Returns an array of stunnel configuration sections, each of which is a hash
302 # reference containing the actual settings
303 sub get_stunnel_config
304 {
305 local (@rv, $service);
306 push(@rv, $service = { 'line' => 0, 'eline' => 0, 'values' => { } });
307 local $lnum = 0;
308 open(CONF, $_[0]);
309 while(<CONF>) {
310         s/\r|\n//g;
311         s/^\s*#.*$//;
312         if (/^\s*\[(.*)\]/) {
313                 push(@rv, $service = { 'name' => $1,
314                                        'line' => $lnum,
315                                        'eline' => $lnum,
316                                        'values' => { } });
317                 }
318         elsif (/^\s*(\S+)\s*=\s*(.*)/) {
319                 $service->{'eline'} = $lnum;
320                 $service->{'values'}->{lc($1)} = $2;
321                 }
322         $lnum++;
323         }
324 close(CONF);
325 return @rv;
326 }
327
328 # create_stunnel_service(&service, file)
329 # Creates a service in an stunnel config file
330 sub create_stunnel_service
331 {
332 local $lref = &read_file_lines($_[1]);
333 push(@$lref, &stunnel_lines($_[0]));
334 &flush_file_lines();
335 }
336
337 # modify_stunnel_service(&service, file)
338 # Modifies an existing service in an stunnel config file
339 sub modify_stunnel_service
340 {
341 local $lref = &read_file_lines($_[1]);
342 splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'} - $_[0]->{'line'} + 1,
343        &stunnel_lines($_[0]));
344 &flush_file_lines();
345 }
346
347 # stunnel_lines(&service)
348 sub stunnel_lines
349 {
350 local @rv;
351 push(@rv, "[$_[0]->{'name'}]") if ($_[0]->{'name'});
352 foreach $k (keys %{$_[0]->{'values'}}) {
353         push(@rv, $k."=".$_[0]->{'values'}->{$k});
354         }
355 return @rv;
356 }
357
358 # apply_configuration()
359 # Apply the inetd and/or xinetd configuration
360 sub apply_configuration
361 {
362 if ($has_inetd) {
363         local %iconfig = &foreign_config("inetd");
364         &system_logged("$iconfig{'restart_command'} >/dev/null 2>&1 </dev/null");
365         }
366 if ($has_xinetd) {
367         local %xconfig = &foreign_config("xinetd");
368         local $pid;
369         if (open(PID, $xconfig{'pid_file'})) {
370                 chop($pid = <PID>);
371                 close(PID);
372                 kill('USR2', $pid);
373                 }
374         }
375 return undef;
376 }
377
378 1;
379