Handle hostnames with upper-case letters
[webmin.git] / sshd / sshd-lib.pl
1 #!/usr/local/bin/perl
2 # sshd-lib.pl
3 # Common functions for the ssh daemon config file
4
5 BEGIN { push(@INC, ".."); };
6 use WebminCore;
7 &init_config();
8
9 # Get version information
10 if (!&read_file("$module_config_directory/version", \%version)) {
11         %version = &get_sshd_version();
12         }
13
14 # get_sshd_version()
15 # Returns a hash containing the version type, number and full version
16 sub get_sshd_version
17 {
18 local %version;
19 local $out = &backquote_command(
20         &quote_path($config{'sshd_path'})." -h 2>&1 </dev/null");
21 if ($config{'sshd_version'}) {
22         # Forced version
23         $version{'type'} = 'openssh';
24         $version{'number'} = $version{'full'} = $config{'sshd_version'};
25         }
26 elsif ($out =~ /(sshd\s+version\s+([0-9\.]+))/i ||
27     $out =~ /(ssh\s+secure\s+shell\s+([0-9\.]+))/i) {
28         # Classic commercial SSH
29         $version{'type'} = 'ssh';
30         $version{'number'} = $2;
31         $version{'full'} = $1;
32         }
33 elsif ($out =~ /(OpenSSH.([0-9\.]+))/i) {
34         # OpenSSH .. assume all versions are supported
35         $version{'type'} = 'openssh';
36         $version{'number'} = $2;
37         $version{'full'} = $1;
38         }
39 elsif ($out =~ /(Sun_SSH_([0-9\.]+))/i) {
40         # Solaris 9 SSH is actually OpenSSH 2.x
41         $version{'type'} = 'openssh';
42         $version{'number'} = 2.0;
43         $version{'full'} = $1;
44         }
45 elsif (($out = $config{'sshd_version'}) && ($out =~ /(Sun_SSH_([0-9\.]+))/i)) {
46         # Probably Solaris 10 SSHD that didn't display version.  Use it.
47         $version{'type'} = 'openssh';
48         $version{'number'} = 2.0;
49         $version{'full'} = $1;
50         }
51 return %version;
52 }
53
54 # get_sshd_config()
55 # Returns a reference to an array of SSHD config file options
56 sub get_sshd_config
57 {
58 local @rv = ( { 'dummy' => 1,
59                 'indent' => 0,
60                 'file' => $config{'sshd_config'},
61                 'line' => -1,
62                 'eline' => -1 } );
63 local $lnum = 0;
64 open(CONF, $config{'sshd_config'});
65 while(<CONF>) {
66         s/\r|\n//g;
67         s/^\s*#.*$//g;
68         local ($name, @values) = split(/\s+/, $_);
69         if ($name) {
70                 local $dir = { 'name' => $name,
71                                'values' => \@values,
72                                'file' => $config{'sshd_config'},
73                                'line' => $lnum };
74                 push(@rv, $dir);
75                 }
76         $lnum++;
77         }
78 close(CONF);
79 return \@rv;
80 }
81
82 # find_value(name, &config)
83 sub find_value
84 {
85 foreach $c (@{$_[1]}) {
86         if (lc($c->{'name'}) eq lc($_[0])) {
87                 return wantarray ? @{$c->{'values'}} : $c->{'values'}->[0];
88                 }
89         }
90 return wantarray ? ( ) : undef;
91 }
92
93 # find(value, &config)
94 sub find
95 {
96 local @rv;
97 foreach $c (@{$_[1]}) {
98         if (lc($c->{'name'}) eq lc($_[0])) {
99                 push(@rv, $c);
100                 }
101         }
102 return wantarray ? @rv : $rv[0];
103 }
104
105 # save_directive(name, &config, [value*|&values], [before])
106 sub save_directive
107 {
108 local @o = &find($_[0], $_[1]);
109 local @n = ref($_[2]) ?
110                 grep { defined($_) } @{$_[2]} :
111                 grep { defined($_) } @_[2..@_-1];
112 local $lref = &read_file_lines($_[1]->[0]->{'file'});
113 local $id = ("\t" x $_[1]->[0]->{'indent'});
114 local $i;
115 local $before = $_[3] && ref($_[2]) ? &find($_[3], $_[1]) : undef;
116 for($i=0; $i<@o || $i<@n; $i++) {
117         if ($o[$i] && $n[$i]) {
118                 # Replacing a line
119                 $lref->[$o[$i]->{'line'}] = "$id$_[0] $n[$i]";
120                 }
121         elsif ($o[$i]) {
122                 # Removing a line
123                 splice(@$lref, $o[$i]->{'line'}, 1);
124                 foreach $c (@{$_[1]}) {
125                         if ($c->{'line'} > $o[$i]->{'line'}) {
126                                 $c->{'line'}--;
127                                 }
128                         }
129                 }
130         elsif ($n[$i] && !$before) {
131                 # Adding a line at the end, but before the last Match directive
132                 local $ll = $_[1]->[@{$_[1]}-1]->{'line'};
133                 foreach my $m (&find("Match", $_[1])) {
134                         $ll = $m->{'line'} - 1;
135                         }
136                 splice(@$lref, $ll+1, 0, "$id$_[0] $n[$i]");
137                 }
138         elsif ($n[$i] && $before) {
139                 # Adding a line before the first instance of some directive
140                 splice(@$lref, $before->{'line'}, 0, "$id$_[0] $n[$i]");
141                 foreach $c (@{$_[1]}) {
142                         if ($c->{'line'} >= $before->{'line'}) {
143                                 $c->{'line'}--;
144                                 }
145                         }
146                 }
147         }
148 }
149
150 # scmd(double)
151 sub scmd
152 {
153 if ($cmd_count % 2 == 0) {
154         print "<tr>\n";
155         }
156 elsif ($_[0]) {
157         print "<td colspan=2></td> </tr>\n";
158         print "<tr>\n";
159         $cmd_count = 0;
160         }
161 $cmd_count += ($_[0] ? 2 : 1);
162 }
163
164 # ecmd()
165 sub ecmd
166 {
167 if ($cmd_count % 2 == 0) {
168         print "</tr>\n";
169         }
170 }
171
172 # get_client_config()
173 # Returns a list of structures, one for each host
174 sub get_client_config
175 {
176 local @rv = ( { 'dummy' => 1,
177                 'indent' => 0,
178                 'file' => $config{'client_config'},
179                 'line' => -1,
180                 'eline' => -1 } );
181 local $host;
182 local $lnum = 0;
183 open(CLIENT, $config{'client_config'});
184 while(<CLIENT>) {
185         s/\r|\n//g;
186         s/^\s*#.*$//g;
187         s/^\s*//g;
188         local ($name, @values) = split(/\s+/, $_);
189         if (lc($name) eq 'host') {
190                 # Start of new host
191                 $host = { 'name' => $name,
192                           'values' => \@values,
193                           'file' => $config{'client_config'},
194                           'line' => $lnum,
195                           'eline' => $lnum,
196                           'members' => [ { 'dummy' => 1,
197                                            'indent' => 1,
198                                            'file' => $config{'client_config'},
199                                            'line' => $lnum } ] };
200                 push(@rv, $host);
201                 }
202         elsif ($name) {
203                 # A directive inside a host
204                 local $dir = { 'name' => $name,
205                                'values' => \@values,
206                                'file' => $config{'client_config'},
207                                'line' => $lnum };
208                 push(@{$host->{'members'}}, $dir);
209                 $host->{'eline'} = $lnum;
210                 }
211         $lnum++;
212         }
213 close(CLIENT);
214 return \@rv;
215 }
216
217 # create_host(&host)
218 sub create_host
219 {
220 local $lref = &read_file_lines($config{'client_config'});
221 $_[0]->{'line'} = $_[0]->{'eline'} = scalar(@$lref);
222 push(@$lref, "Host ".join(" ", @{$_[0]->{'values'}}));
223 $_[0]->{'members'} = [ { 'dummy' => 1,
224                          'indent' => 1,
225                          'file' => $config{'client_config'},
226                          'line' => $_[0]->{'line'} } ];
227 }
228
229 # modify_host(&host)
230 sub modify_host
231 {
232 local $lref = &read_file_lines($config{'client_config'});
233 $lref->[$_[0]->{'line'}] = "Host ".join(" ", @{$_[0]->{'values'}});
234 }
235
236 # delete_host(&host)
237 sub delete_host
238 {
239 local $lref = &read_file_lines($config{'client_config'});
240 splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'} - $_[0]->{'line'} + 1);
241 }
242
243 # restart_sshd()
244 # Re-starts the SSH server, and returns an error message on failure or
245 # undef on success
246 sub restart_sshd
247 {
248 if ($config{'restart_cmd'}) {
249         local $out = `$config{'restart_cmd'} 2>&1 </dev/null`;
250         return "<pre>$out</pre>" if ($?);
251         }
252 else {
253         local $pid = &get_sshd_pid();
254         $pid || return $text{'apply_epid'};
255         &kill_logged('HUP', $pid);
256         }
257 return undef;
258 }
259
260 # stop_sshd()
261 # Kills the SSH server, and returns an error message on failure or
262 # undef on success
263 sub stop_sshd
264 {
265 if ($config{'stop_cmd'}) {
266         local $out = `$config{'stop_cmd'} 2>&1 </dev/null`;
267         return "<pre>$out</pre>" if ($?);
268         }
269 else {
270         local $pid = &get_sshd_pid();
271         $pid || return $text{'apply_epid'};
272         &kill_logged('TERM', $pid);
273         }
274 return undef;
275 }
276
277 # start_sshd()
278 # Attempts to start the SSH server, returning undef on success or an error
279 # message on failure.
280 sub start_sshd
281 {
282 # Remove PID file if invalid
283 if (-f $config{'pid_file'} && !&check_pid_file($config{'pid_file'})) {
284         &unlink_file($config{'pid_file'});
285         }
286
287 if ($config{'start_cmd'}) {
288         $out = &backquote_logged("$config{'start_cmd'} 2>&1 </dev/null");
289         if ($?) { return "<pre>$out</pre>"; }
290         }
291 else {
292         $out = &backquote_logged("$config{'sshd_path'} 2>&1 </dev/null");
293         if ($?) { return "<pre>$out</pre>"; }
294         }
295 return undef;
296 }
297
298 # get_pid_file()
299 # Returns the SSH server PID file
300 sub get_pid_file
301 {
302 local $conf = &get_sshd_config();
303 local $pidfile = &find_value("PidFile", $conf);
304 $pidfile ||= $config{'pid_file'};
305 return $pidfile;
306 }
307
308 # get_sshd_pid()
309 # Returns the PID of the running SSHd process
310 sub get_sshd_pid
311 {
312 local $file = &get_pid_file();
313 if ($file) {
314         return &check_pid_file($file);
315         }
316 else {
317         local ($rv) = &find_byname("sshd");
318         return $rv;
319         }
320 }
321
322 # get_mlvalues(file, id, [splitchar])
323 # Return an array with values from a file, where the
324 # values are one per line with an id preceeding them
325 sub get_mlvalues
326 {
327 local @rv;
328 local $_;
329 local $split = defined($_[2]) ? $_[2] : " ";
330 local $realfile = &translate_filename($_[0]);
331 &open_readfile(ARFILE, $_[0]) || return 0;
332 while(<ARFILE>) {
333         chomp;
334         local $hash = index($_, "#");
335         local $eq = index($_, $split);
336         if ($hash != 0 && $eq >= 0) {
337                 local $n = substr($_, 0, $eq);
338                 local $v = substr($_, $eq+1);
339                 chomp($v);
340                 if ($n eq $_[1]) {
341                         push(@rv, $v);
342                                                 }
343                 }
344         }
345 close(ARFILE);
346 return @rv;
347 }
348
349 # list_syslog_facilities()
350 # Returns an upper-case list of syslog facility names
351 sub list_syslog_facilities
352 {
353 local @facils;
354 if (&foreign_check("syslog")) {
355         local %sconfig = &foreign_config("syslog");
356         @facils = map { uc($_) } split(/\s+/, $sconfig{'facilities'});
357         }
358 if (!@facils) {
359         @facils = ( 'DAEMON', 'USER', 'AUTH', 'LOCAL0', 'LOCAL1', 'LOCAL2',
360                     'LOCAL3', 'LOCAL4', 'LOCAL5', 'LOCAL6', 'LOCAL7' );
361         }
362 return @facils;
363 }
364
365 sub list_logging_levels
366 {
367 return ('QUIET', 'FATAL', 'ERROR', 'INFO', 'VERBOSE', 'DEBUG');
368 }
369
370 sub yes_no_default_radio
371 {
372 local ($name, $value) = @_;
373 return &ui_radio($name, lc($value) eq 'yes' ? 1 :
374                         lc($value) eq 'no' ? 0 : 2,
375                  [ [ 1, $text{'yes'} ], [ 0, $text{'no'} ],
376                    [ 2, $text{'default'} ] ]);
377 }
378
379 1;
380