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