1package Bastille::API::ServiceAdmin;
2use strict;
3
4use Bastille::API;
5
6use Bastille::API::HPSpecific;
7use Bastille::API::FileContent;
8
9require Exporter;
10our @ISA = qw(Exporter);
11our @EXPORT_OK = qw(
12B_chkconfig_on
13B_chkconfig_off
14B_service_start
15B_service_stop
16B_service_restart
17B_is_service_off
18checkServiceOnLinux
19remoteServiceCheck
20remoteNISPlusServiceCheck
21B_create_nsswitch_file
22);
23our @EXPORT = @EXPORT_OK;
24
25
26#######
27# &B_chkconfig_on and &B_chkconfig_off() are great for systems that didn't use
28# a more modern init system.  This is a bit of a problem on Fedora, though,
29# which used upstart from Fedora 9 to Fedora 14, then switched to a new
30# Red Hat-created system called systemd for Fedora 15 and 16 (so far).
31# OpenSUSE also moved to systemd, starting with 12.1.  Version 11.4 did not
32# use systemd.
33# It is also a problem on Ubuntu, starting at version 6.10, where they also
34# used upstart.
35#####
36
37
38
39
40###########################################################################
41# &B_chkconfig_on ($daemon_name) creates the symbolic links that are
42# named in the "# chkconfig: ___ _ _ " portion of the init.d files.  We
43# need this utility, in place of the distro's chkconfig, because of both
44# our need to add revert functionality and our need to harden distros that
45# are not mounted on /.
46#
47# It uses the following global variables to find the links and the init
48# scripts, respectively:
49#
50#   &getGlobal('DIR', "rcd")    -- directory where the rc_.d subdirs can be found
51#   &getGlobal('DIR', "initd")  -- directory the rc_.d directories link to
52#
53# Here an example of where you might use this:
54#
55# You'd like to tell the system to run the firewall at boot:
56#       B_chkconfig_on("bastille-firewall")
57#
58###########################################################################
59
60# PW: Blech. Copied B_chkconfig_off() and changed a few things,
61#               then changed a few more things....
62
63sub B_chkconfig_on {
64
65    my $startup_script=$_[0];
66    my $retval=1;
67
68    my $chkconfig_line;
69    my ($runlevelinfo,@runlevels);
70    my ($start_order,$stop_order,$filetolink);
71
72    &B_log("ACTION","# chkconfig_on enabling $startup_script\n");
73
74    # In Debian system there is no chkconfig script, run levels are checked
75    # one by one (jfs)
76    if (&GetDistro =~/^DB.*/) {
77            $filetolink = &getGlobal('DIR', "initd") . "/$startup_script";
78            if (-x $filetolink)
79            {
80                    foreach my $level ("0","1","2","3","4","5","6" ) {
81                            my $link = '';
82                            $link = &getGlobal('DIR', "rcd") . "/rc" . "$level" . ".d/K50" . "$startup_script";
83                            $retval=symlink($filetolink,$link);
84                    }
85            }
86            return $retval;
87    }
88    #
89    # On SUSE, chkconfig-based rc scripts have been replaced with a whole different
90    # system.  chkconfig on SUSE is actually a shell script that does some stuff and then
91    # calls insserv, their replacement.
92    #
93
94    if (&GetDistro =~ /^SE/) {
95        # only try to chkconfig on if init script is found
96        if ( -e (&getGlobal('DIR', "initd") . "/$startup_script") ) {
97            $chkconfig_line=&getGlobal('BIN','chkconfig');
98            &B_System("$chkconfig_line $startup_script on", "$chkconfig_line $startup_script off");
99            # chkconfig doesn't take affect until reboot, need to restart service also
100            B_service_restart("$startup_script");
101            return 1; #success
102        }
103        return 0; #failure
104    }
105
106    #
107    # Run through the init script looking for the chkconfig line...
108    #
109    $retval = open CHKCONFIG,&getGlobal('DIR', "initd") . "/$startup_script";
110    unless ($retval) {
111        &B_log("ACTION","# Didn't chkconfig_on $startup_script because we couldn't open " . &getGlobal('DIR', "initd") . "/$startup_script\n");
112    }
113    else {
114
115      READ_LOOP:
116        while (my $line=<CHKCONFIG>) {
117
118            # We're looking for lines like this one:
119            #      # chkconfig: 2345 10 90
120            # OR this
121            #      # chkconfig: - 10 90
122
123            if ($line =~ /^#\s*chkconfig:\s*([-\d]+)\s*(\d+)\s*(\d+)/ ) {
124                $runlevelinfo = $1;
125                $start_order = $2;
126                $stop_order = $3;
127                # handle a run levels arg of '-'
128                if ( $runlevelinfo eq '-' ) {
129                    &B_log("ACTION","chkconfig_on saw '-' for run levels for \"$startup_script\", is defaulting to levels 3,4,5\n");
130                    $runlevelinfo = '345';
131                }
132                @runlevels = split(//,$runlevelinfo);
133                # make sure the orders have 2 digits
134                $start_order =~ s/^(\d)$/0$1/;
135                $stop_order =~ s/^(\d)$/0$1/;
136                last READ_LOOP;
137            }
138        }
139        close CHKCONFIG;
140
141        # Do we have what we need?
142        if ( (scalar(@runlevels) < 1) || (! $start_order =~ /^\d{2}$/) || (! $stop_order =~ /^\d{2}$/) ) {
143                # problem
144                &B_log("ERROR","# B_chkconfig_on $startup_script failed -- no valid run level/start/stop info found\n");
145                return(-1);
146        }
147
148        # Now, run through creating symlinks...
149        &B_log("ACTION","# chkconfig_on will use run levels ".join(",",@runlevels)." for \"$startup_script\" with S order $start_order and K order $stop_order\n");
150
151        $retval=0;
152        # BUG: we really ought to readdir() on &getGlobal('DIR', "rcd") to get all levels
153        foreach my $level ( "0","1","2","3","4","5","6" ) {
154                my $link = '';
155                # we make K links in run levels not specified in the chkconfig line
156                $link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/K$stop_order" . $startup_script;
157                my $klink = $link;
158                # now we see if this is a specified run level; if so, make an S link
159                foreach my $markedlevel ( @runlevels ) {
160                        if ( $level == $markedlevel) {
161                                $link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/S$start_order" . $startup_script;
162                        }
163                }
164                my $target = &getGlobal('DIR', "initd") ."/" . $startup_script;
165                my $local_return;
166
167                if ( (-e "$klink") && ($klink ne $link) ) {
168                    # there's a K link, but this level needs an S link
169                    unless ($GLOBAL_LOGONLY) {
170                        $local_return = unlink("$klink");
171                        if ( ! $local_return ) {
172                            # unlinking old, bad $klink failed
173                            &B_log("ERROR","Unlinking $klink failed\n");
174                        } else {
175                            &B_log("ACTION","Removed link $klink\n");
176                            # If we removed the link, add a link command to the revert file
177                            &B_revert_log (&getGlobal('BIN','ln') . " -s $target $klink\n");
178                        } # close what to do if unlink works
179                    }   # if not GLOBAL_LOGONLY
180                }       # if $klink exists and ne $link
181
182                # OK, we've disposed of any old K links, make what we need
183                if ( (! ( -e "$link" )) && ($link ne '') ) {
184                    # link doesn't exist and the start/stop number is OK; make it
185                    unless ($GLOBAL_LOGONLY) {
186                        # create the link
187                        $local_return = &B_symlink($target,$link);
188                        if ($local_return) {
189                            $retval++;
190                            &B_log("ACTION","Created link $link\n");
191                        } else {
192                            &B_log("ERROR","Couldn't create $link when trying to chkconfig on $startup_script\n");
193                        }
194                    }
195
196                } # link doesn't exist
197            } # foreach level
198
199    }
200
201    if ($retval < @runlevels) {
202        $retval=0;
203    }
204
205    $retval;
206
207}
208
209
210###########################################################################
211# &B_chkconfig_off ($daemon_name) deletes the symbolic links that are
212# named in the "# chkconfig: ___ _ _ " portion of the init.d files.  We
213# need this utility, in place of the distro's chkconfig, because of both
214# our need to add revert functionality and our need to harden distros that
215# are not mounted on /.
216#
217# chkconfig allows for a REVERT of its work by writing to an executable
218# file &getGlobal('BFILE', "removed-symlinks").
219#
220# It uses the following global variables to find the links and the init
221# scripts, respectively:
222#
223#   &getGlobal('DIR', "rcd")    -- directory where the rc_.d subdirs can be found
224#   &getGlobal('DIR', "initd")  -- directory the rc_.d directories link to
225#
226# Here an example of where you might use this:
227#
228# You'd like to tell stop running sendmail in daemon mode on boot:
229#       B_chkconfig_off("sendmail")
230#
231###########################################################################
232
233
234
235sub B_chkconfig_off {
236
237    my $startup_script=$_[0];
238    my $retval=1;
239
240    my $chkconfig_line;
241    my @runlevels;
242    my ($start_order,$stop_order,$filetolink);
243
244    if (&GetDistro =~/^DB.*/) {
245            $filetolink = &getGlobal('DIR', "initd") . "/$startup_script";
246            if (-x $filetolink)
247            {
248                    # Three ways to do this in Debian:
249                    # 1.- have the initd script set to 600 mode
250                    # 2.- Remove the links in rcd (re-installing the package
251                    # will break it)
252                    # 3.- Use update-rc.d --remove (same as 2.)
253                    # (jfs)
254                    &B_chmod(0600,$filetolink);
255                    $retval=6;
256
257                    # The second option
258                    #foreach my $level ("0","1","2","3","4","5","6" ) {
259                    #my $link = '';
260                    #$link = &getGlobal('DIR', "rcd") . "/rc" . "$level" . ".d/K50" . "$startup_script";
261                    #unlink($link);
262                    #}
263            }
264    }
265
266    #
267    # On SUSE, chkconfig-based rc scripts have been replaced with a whole different
268    # system.  chkconfig on SUSE is actually a shell script that does some stuff and then
269    # calls insserv, their replacement.
270    #
271    elsif (&GetDistro =~ /^SE/) {
272        # only try to chkconfig off if init script is found
273        if ( -e (&getGlobal('DIR', "initd") . "/$startup_script") ) {
274            $chkconfig_line=&getGlobal('BIN','chkconfig');
275            &B_System("$chkconfig_line $startup_script on", "$chkconfig_line $startup_script off");
276            # chkconfig doesn't take affect until reboot, need to stop service
277            # since expectation is that the daemons are disabled even without a reboot
278            B_service_stop("$startup_script");
279            return 1; #success
280        }
281        return 0; #failure
282    }
283    else {
284
285            # Run through the init script looking for the chkconfig line...
286
287
288            $retval = open CHKCONFIG,&getGlobal('DIR', "initd") . "/$startup_script";
289            unless ($retval) {
290                    &B_log("ACTION","Didn't chkconfig_off $startup_script because we couldn't open " . &getGlobal('DIR', "initd") . "/$startup_script\n");
291            }
292            else {
293
294                    READ_LOOP:
295                    while (my $line=<CHKCONFIG>) {
296
297                            # We're looking for lines like this one:
298                            #      # chkconfig: 2345 10 90
299
300                            if ($line =~ /^#\s*chkconfig:\s*([-\d]+)\s*(\d+)\s*(\d+)/ ) {
301                                    @runlevels=split //,$1;
302                                    $start_order=$2;
303                                    $stop_order=$3;
304
305
306                                    # Change single digit run levels to double digit -- otherwise,
307                                    # the alphabetic ordering chkconfig depends on fails.
308                                    if ($start_order =~ /^\d$/ ) {
309                                            $start_order = "0" . $start_order;
310                                            &B_log("ACTION","chkconfig_off converted start order to $start_order\n");
311                                    }
312                                    if ($stop_order =~ /^\d$/ ) {
313                                            $stop_order = "0" . $stop_order;
314                                            &B_log("ACTION","chkconfig_off converted stop order to $stop_order\n");
315                                    }
316
317                                    last READ_LOOP;
318                            }
319                    }
320                    close CHKCONFIG;
321
322                    # If we never found a chkconfig line, can we just run through all 5
323                    # rcX.d dirs from 1 to 5...?
324
325                    # unless ( $start_order and $stop_order ) {
326                    #    @runlevels=("1","2","3","4","5");
327                    #    $start_order = "*"; $stop_order="*";
328                    # }
329
330                    # Now, run through removing symlinks...
331
332
333
334                    $retval=0;
335
336                    # Handle the special case that the run level specified is solely "-"
337                    if ($runlevels[0] =~ /-/) {
338                            @runlevels = ( "0","1","2","3","4","5","6" );
339                    }
340
341                    foreach my $level ( @runlevels ) {
342                            my $link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/S$start_order" . $startup_script;
343                            my $new_link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/K$stop_order" . $startup_script;
344                            my $target = &getGlobal('DIR', "initd") ."/" . $startup_script;
345                            my $local_return;
346
347
348                            # Replace the S__ link in this level with a K__ link.
349                            if ( -e $link ) {
350                                    unless ($GLOBAL_LOGONLY) {
351                                            $local_return=unlink $link;
352                                            if ($local_return) {
353                                                    $local_return=symlink $target,$new_link;
354                                                    unless ($local_return) {
355                                                            &B_log("ERROR","Linking $target to $new_link failed.\n");
356                                                    }
357                                            }
358                                            else {  # unlinking failed
359                                                    &B_log("ERROR","Unlinking $link failed\n");
360                                            }
361
362                                    }
363                                    if ($local_return) {
364                                            $retval++;
365                                            &B_log("ACTION","Removed link $link\n");
366
367                                            #
368                                            # If we removed the link, add a link command to the revert file
369                                            # Write out the revert information for recreating the S__
370                                            # symlink and deleting the K__ symlink.
371                                            &B_revert_log(&getGlobal('BIN',"ln") . " -s $target $link\n");
372                                            &B_revert_log(&getGlobal('BIN',"rm") . " -f $new_link\n");
373                                    }
374                                    else {
375                                            &B_log("ERROR","B_chkconfig_off $startup_script failed\n");
376                                    }
377
378                            }
379                    } # foreach
380
381            } # else-unless
382
383    } # else-DB
384    if ($retval < @runlevels) {
385            $retval=0;
386    }
387
388    $retval;
389
390}
391
392
393###########################################################################
394# &B_service_start ($daemon_name)
395# Starts service on RedHat/SUSE-based Linux distributions which have the
396# service command:
397#
398#       service $daemon_name start
399#
400# Other Linux distros that also support this method of starting
401# services can be added to use this function.
402#
403# Here an example of where you might use this:
404#
405# You'd like to tell the system to start the vsftpd daemon:
406#       &B_service_start("vsftpd")
407#
408# Uses &B_System in HP_API.pm
409# To match how the &B_System command works this method:
410# returns 1 on success
411# returns 0 on failure
412###########################################################################
413
414sub B_service_start {
415
416    my $daemon=$_[0];
417
418    if ( (&GetDistro !~ /^SE/) and (&GetDistro !~ /^RH/) and
419        (&GetDistro !~ /^RHFC/) and (&GetDistro !~ /^MN/) ) {
420        &B_log("ERROR","Tried to call service_start on a system lacking a service command! Internal Bastille error.");
421       return undef;
422    }
423
424    # only start service if init script is found
425    if ( -e (&getGlobal('DIR', 'initd') . "/$daemon") ) {
426        &B_log("ACTION","# service_start enabling $daemon\n");
427
428        my $service_cmd=&getGlobal('BIN', 'service');
429        if ($service_cmd) {
430            # Start the service,
431            # Also provide &B_System revert command
432
433            return (&B_System("$service_cmd $daemon start",
434                              "$service_cmd $daemon stop"));
435        }
436    }
437
438    # init script not found, do not try to start, return failure
439    return 0;
440}
441
442###########################################################################
443# &B_service_stop ($daemon_name)
444# Stops service on RedHat/SUSE-based Linux distributions which have the
445# service command:
446#
447#       service $daemon_name stop
448#
449# Other Linux distros that also support this method of starting
450# services can be added to use this function.
451# Stops service.
452#
453#
454# Here an example of where you might use this:
455#
456# You'd like to tell the system to stop the vsftpd daemon:
457#       &B_service_stop("vsftpd")
458#
459# Uses &B_System in HP_API.pm
460# To match how the &B_System command works this method:
461# returns 1 on success
462# returns 0 on failure
463###########################################################################
464
465sub B_service_stop {
466
467    my $daemon=$_[0];
468
469    if ( (&GetDistro !~ /^SE/) and (&GetDistro !~ /^RH/) and
470        (&GetDistro !~ /^RHFC/) and (&GetDistro !~ /^MN/) ) {
471        &B_log("ERROR","Tried to call service_stop on a system lacking a service command! Internal Bastille error.");
472       return undef;
473    }
474
475    # only stop service if init script is found
476    if ( -e (&getGlobal('DIR', 'initd') . "/$daemon") ) {
477        &B_log("ACTION","# service_stop disabling $daemon\n");
478
479        my $service_cmd=&getGlobal('BIN', 'service');
480        if ($service_cmd) {
481
482        # Stop the service,
483        # Also provide &B_System revert command
484
485           return (&B_System("$service_cmd $daemon stop",
486                             "$service_cmd $daemon start"));
487        }
488    }
489
490    # init script not found, do not try to stop, return failure
491    return 0;
492}
493
494
495###########################################################################
496# &B_service_restart ($daemon_name)
497# Restarts service on RedHat/SUSE-based Linux distributions which have the
498# service command:
499#
500#       service $daemon_name restart
501#
502# Other Linux distros that also support this method of starting
503# services can be added to use this function.
504#
505# Here an example of where you might use this:
506#
507# You'd like to tell the system to restart the vsftpd daemon:
508#       &B_service_restart("vsftpd")
509#
510# Uses &B_System in HP_API.pm
511# To match how the &B_System command works this method:
512# returns 1 on success
513# returns 0 on failure
514###########################################################################
515
516sub B_service_restart {
517
518    my $daemon=$_[0];
519
520    if ( (&GetDistro !~ /^SE/) and (&GetDistro !~ /^RH/) and
521        (&GetDistro !~ /^RHFC/) and (&GetDistro !~ /^MN/) ) {
522        &B_log("ERROR","Tried to call service_restart on a system lacking a service command! Internal Bastille error.");
523       return undef;
524    }
525
526    # only restart service if init script is found
527    if ( -e (&getGlobal('DIR', 'initd') . "/$daemon") ) {
528        &B_log("ACTION","# service_restart re-enabling $daemon\n");
529
530        my $service_cmd=&getGlobal('BIN', 'service');
531        if ($service_cmd) {
532
533            # Restart the service
534            return (&B_System("$service_cmd $daemon restart",
535                              "$service_cmd $daemon restart"));
536        }
537    }
538
539    # init script not found, do not try to restart, return failure
540    return 0;
541}
542
543###########################################################################
544# &B_is_service_off($;$)
545#
546# Runs the specified test to determine whether or not the question should
547# be answered.
548#
549# return values:
550# NOTSECURE_CAN_CHANGE()/0:     service is on
551# SECURE_CANT_CHANGE()/1:     service is off
552# undef: test is not defined
553###########################################################################
554
555sub B_is_service_off ($){
556   my $service=$_[0];
557
558   if(&GetDistro =~ "^HP-UX"){
559     #die "Why do I think I'm on HPUX?!\n";
560     return &checkServiceOnHPUX($service);
561   }
562   elsif ( (&GetDistro =~ "^RH") || (&GetDistro =~ "^SE") ) {
563     return &checkServiceOnLinux($service);
564   }
565   else {
566    &B_log("DEBUG","B_is_service off called for unsupported OS");
567     # not yet implemented for other distributions of Linux
568     # when GLOBAL_SERVICE, GLOBAL_SERVTYPE and GLOBAL_PROCESS are filled
569     # in for Linux, then
570     # at least inetd and inittab services should be similar to the above,
571     # whereas chkconfig would be used on some Linux distros to determine
572     # if non-inetd/inittab services are running at boot time.  Looking at
573     # processes should be similar.
574     return undef;
575   }
576}
577
578###########################################################################
579# &checkServiceOnLinux($service);
580#
581# Checks if the given service is running on a Linux system.  This is
582# called by B_is_Service_Off(), which is the function that Bastille
583# modules should call.
584#
585# Return values:
586# NOTSECURE_CAN_CHANGE() if the service is on
587# SECURE_CANT_CHANGE() if the service is off
588# undef if the state of the service cannot be determined
589#
590###########################################################################
591sub checkServiceOnLinux($) {
592  my $service=$_[0];
593
594  # get the list of parameters which could be used to initiate the service
595  # (could be in /etc/rc.d/rc?.d, /etc/inetd.conf, or /etc/inittab, so we
596  # check all of them)
597
598  my @params = @{ &getGlobal('SERVICE', $service) };
599  my $chkconfig = &getGlobal('BIN', 'chkconfig');
600  my $grep = &getGlobal('BIN', 'grep');
601  my $inittab = &getGlobal('FILE', 'inittab');
602  my $serviceType = &getGlobal('SERVTYPE', $service);;
603
604  # A kludge to get things running because &getGlobal('SERVICE' doesn't
605  # return the expected values.
606  @params = ();
607  push (@params, $service);
608
609  foreach my $param (@params) {
610    &B_log("DEBUG","Checking to see if service $service is off.\n");
611
612    if ($serviceType =~ /rc/) {
613      my $on = &B_Backtick("$chkconfig --list $param 2>&1");
614      if ($on =~ /^$param:\s+unknown/) {
615          # This service isn't installed on the system
616          return NOT_INSTALLED();
617      }
618      if ($on =~ /^error reading information on service $param: No such file or directory/) {
619          # This service isn't installed on the system
620          return NOT_INSTALLED();
621      }
622      if ($on =~ /^error/) {
623          # This probably
624          &B_log("DEBUG","chkconfig returned: $param=$on\n");
625          return undef;
626      }
627      $on =~ s/^$param\s+//;            # remove the service name and spaces
628      $on =~ s/[0-6]:off\s*//g;         # remove any runlevel:off entries
629      $on =~ s/:on\s*//g;               # remove the :on from the runlevels
630      # what remains is a list of runlevels in which the service is on,
631      # or a null string if it is never turned on
632      chomp $on;                        # newline should be gone already (\s)
633      &B_log("DEBUG","chkconfig returned: $param=$on\n");
634
635      if ($on =~ /^\d+$/) {
636        # service is not off
637        ###########################   BREAK out, don't skip question
638        return NOTSECURE_CAN_CHANGE();
639      }
640  }
641    elsif ($serviceType =~ /inet/) {
642        my $on = &B_Backtick("$chkconfig --list $param 2>&1");
643        if ($on =~ /^$param:\s+unknown/) {
644            # This service isn't installed on the system
645            return NOT_INSTALLED();
646        }
647        if ($on =~ /^error reading information on service $param: No such file or directory/) {
648            # This service isn't installed on the system
649            return NOT_INSTALLED();
650        }
651        if ($on =~ /^error/ ) {
652         # Something else is wrong?
653         # return undef
654         return undef;
655     }
656      if ($on =~ tr/\n// > 1) {
657        $on =~ s/^xinetd.+\n//;
658      }
659      $on =~ s/^\s*$param:?\s+//;       # remove the service name and spaces
660      chomp $on;                        # newline should be gone already (\s)
661      &B_log("DEBUG","chkconfig returned: $param=$on\n");
662
663      if ($on =~ /^on$/) {
664        # service is not off
665        ###########################   BREAK out, don't skip question
666        return NOTSECURE_CAN_CHANGE();
667      }
668    }
669    else {
670      # perhaps the service is started by inittab
671      my $inittabline = &B_Backtick("$grep -E '^[^#].{0,3}:.*:.+:.*$param' $inittab");
672      if ($inittabline =~ /.+/) {  # . matches anything except newlines
673        # service is not off
674        &B_log("DEBUG","Checking inittab; found $inittabline\n");
675        ###########################   BREAK out, don't skip question
676        return NOTSECURE_CAN_CHANGE();
677      }
678    }
679  }  # foreach my $param
680
681
682  # boot-time parameters are not set; check processes
683  # Note the checkProcsforService returns INCONSISTENT() if a process is found
684  # assuming the checks above
685  return &checkProcsForService($service);
686}
687
6881;
689
690
691