| package Bastille::API::ServiceAdmin; |
| use strict; |
| |
| use Bastille::API; |
| |
| use Bastille::API::HPSpecific; |
| use Bastille::API::FileContent; |
| |
| require Exporter; |
| our @ISA = qw(Exporter); |
| our @EXPORT_OK = qw( |
| B_chkconfig_on |
| B_chkconfig_off |
| B_service_start |
| B_service_stop |
| B_service_restart |
| B_is_service_off |
| checkServiceOnLinux |
| remoteServiceCheck |
| remoteNISPlusServiceCheck |
| B_create_nsswitch_file |
| ); |
| our @EXPORT = @EXPORT_OK; |
| |
| |
| ####### |
| # &B_chkconfig_on and &B_chkconfig_off() are great for systems that didn't use |
| # a more modern init system. This is a bit of a problem on Fedora, though, |
| # which used upstart from Fedora 9 to Fedora 14, then switched to a new |
| # Red Hat-created system called systemd for Fedora 15 and 16 (so far). |
| # OpenSUSE also moved to systemd, starting with 12.1. Version 11.4 did not |
| # use systemd. |
| # It is also a problem on Ubuntu, starting at version 6.10, where they also |
| # used upstart. |
| ##### |
| |
| |
| |
| |
| ########################################################################### |
| # &B_chkconfig_on ($daemon_name) creates the symbolic links that are |
| # named in the "# chkconfig: ___ _ _ " portion of the init.d files. We |
| # need this utility, in place of the distro's chkconfig, because of both |
| # our need to add revert functionality and our need to harden distros that |
| # are not mounted on /. |
| # |
| # It uses the following global variables to find the links and the init |
| # scripts, respectively: |
| # |
| # &getGlobal('DIR', "rcd") -- directory where the rc_.d subdirs can be found |
| # &getGlobal('DIR', "initd") -- directory the rc_.d directories link to |
| # |
| # Here an example of where you might use this: |
| # |
| # You'd like to tell the system to run the firewall at boot: |
| # B_chkconfig_on("bastille-firewall") |
| # |
| ########################################################################### |
| |
| # PW: Blech. Copied B_chkconfig_off() and changed a few things, |
| # then changed a few more things.... |
| |
| sub B_chkconfig_on { |
| |
| my $startup_script=$_[0]; |
| my $retval=1; |
| |
| my $chkconfig_line; |
| my ($runlevelinfo,@runlevels); |
| my ($start_order,$stop_order,$filetolink); |
| |
| &B_log("ACTION","# chkconfig_on enabling $startup_script\n"); |
| |
| # In Debian system there is no chkconfig script, run levels are checked |
| # one by one (jfs) |
| if (&GetDistro =~/^DB.*/) { |
| $filetolink = &getGlobal('DIR', "initd") . "/$startup_script"; |
| if (-x $filetolink) |
| { |
| foreach my $level ("0","1","2","3","4","5","6" ) { |
| my $link = ''; |
| $link = &getGlobal('DIR', "rcd") . "/rc" . "$level" . ".d/K50" . "$startup_script"; |
| $retval=symlink($filetolink,$link); |
| } |
| } |
| return $retval; |
| } |
| # |
| # On SUSE, chkconfig-based rc scripts have been replaced with a whole different |
| # system. chkconfig on SUSE is actually a shell script that does some stuff and then |
| # calls insserv, their replacement. |
| # |
| |
| if (&GetDistro =~ /^SE/) { |
| # only try to chkconfig on if init script is found |
| if ( -e (&getGlobal('DIR', "initd") . "/$startup_script") ) { |
| $chkconfig_line=&getGlobal('BIN','chkconfig'); |
| &B_System("$chkconfig_line $startup_script on", "$chkconfig_line $startup_script off"); |
| # chkconfig doesn't take affect until reboot, need to restart service also |
| B_service_restart("$startup_script"); |
| return 1; #success |
| } |
| return 0; #failure |
| } |
| |
| # |
| # Run through the init script looking for the chkconfig line... |
| # |
| $retval = open CHKCONFIG,&getGlobal('DIR', "initd") . "/$startup_script"; |
| unless ($retval) { |
| &B_log("ACTION","# Didn't chkconfig_on $startup_script because we couldn't open " . &getGlobal('DIR', "initd") . "/$startup_script\n"); |
| } |
| else { |
| |
| READ_LOOP: |
| while (my $line=<CHKCONFIG>) { |
| |
| # We're looking for lines like this one: |
| # # chkconfig: 2345 10 90 |
| # OR this |
| # # chkconfig: - 10 90 |
| |
| if ($line =~ /^#\s*chkconfig:\s*([-\d]+)\s*(\d+)\s*(\d+)/ ) { |
| $runlevelinfo = $1; |
| $start_order = $2; |
| $stop_order = $3; |
| # handle a run levels arg of '-' |
| if ( $runlevelinfo eq '-' ) { |
| &B_log("ACTION","chkconfig_on saw '-' for run levels for \"$startup_script\", is defaulting to levels 3,4,5\n"); |
| $runlevelinfo = '345'; |
| } |
| @runlevels = split(//,$runlevelinfo); |
| # make sure the orders have 2 digits |
| $start_order =~ s/^(\d)$/0$1/; |
| $stop_order =~ s/^(\d)$/0$1/; |
| last READ_LOOP; |
| } |
| } |
| close CHKCONFIG; |
| |
| # Do we have what we need? |
| if ( (scalar(@runlevels) < 1) || (! $start_order =~ /^\d{2}$/) || (! $stop_order =~ /^\d{2}$/) ) { |
| # problem |
| &B_log("ERROR","# B_chkconfig_on $startup_script failed -- no valid run level/start/stop info found\n"); |
| return(-1); |
| } |
| |
| # Now, run through creating symlinks... |
| &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"); |
| |
| $retval=0; |
| # BUG: we really ought to readdir() on &getGlobal('DIR', "rcd") to get all levels |
| foreach my $level ( "0","1","2","3","4","5","6" ) { |
| my $link = ''; |
| # we make K links in run levels not specified in the chkconfig line |
| $link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/K$stop_order" . $startup_script; |
| my $klink = $link; |
| # now we see if this is a specified run level; if so, make an S link |
| foreach my $markedlevel ( @runlevels ) { |
| if ( $level == $markedlevel) { |
| $link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/S$start_order" . $startup_script; |
| } |
| } |
| my $target = &getGlobal('DIR', "initd") ."/" . $startup_script; |
| my $local_return; |
| |
| if ( (-e "$klink") && ($klink ne $link) ) { |
| # there's a K link, but this level needs an S link |
| unless ($GLOBAL_LOGONLY) { |
| $local_return = unlink("$klink"); |
| if ( ! $local_return ) { |
| # unlinking old, bad $klink failed |
| &B_log("ERROR","Unlinking $klink failed\n"); |
| } else { |
| &B_log("ACTION","Removed link $klink\n"); |
| # If we removed the link, add a link command to the revert file |
| &B_revert_log (&getGlobal('BIN','ln') . " -s $target $klink\n"); |
| } # close what to do if unlink works |
| } # if not GLOBAL_LOGONLY |
| } # if $klink exists and ne $link |
| |
| # OK, we've disposed of any old K links, make what we need |
| if ( (! ( -e "$link" )) && ($link ne '') ) { |
| # link doesn't exist and the start/stop number is OK; make it |
| unless ($GLOBAL_LOGONLY) { |
| # create the link |
| $local_return = &B_symlink($target,$link); |
| if ($local_return) { |
| $retval++; |
| &B_log("ACTION","Created link $link\n"); |
| } else { |
| &B_log("ERROR","Couldn't create $link when trying to chkconfig on $startup_script\n"); |
| } |
| } |
| |
| } # link doesn't exist |
| } # foreach level |
| |
| } |
| |
| if ($retval < @runlevels) { |
| $retval=0; |
| } |
| |
| $retval; |
| |
| } |
| |
| |
| ########################################################################### |
| # &B_chkconfig_off ($daemon_name) deletes the symbolic links that are |
| # named in the "# chkconfig: ___ _ _ " portion of the init.d files. We |
| # need this utility, in place of the distro's chkconfig, because of both |
| # our need to add revert functionality and our need to harden distros that |
| # are not mounted on /. |
| # |
| # chkconfig allows for a REVERT of its work by writing to an executable |
| # file &getGlobal('BFILE', "removed-symlinks"). |
| # |
| # It uses the following global variables to find the links and the init |
| # scripts, respectively: |
| # |
| # &getGlobal('DIR', "rcd") -- directory where the rc_.d subdirs can be found |
| # &getGlobal('DIR', "initd") -- directory the rc_.d directories link to |
| # |
| # Here an example of where you might use this: |
| # |
| # You'd like to tell stop running sendmail in daemon mode on boot: |
| # B_chkconfig_off("sendmail") |
| # |
| ########################################################################### |
| |
| |
| |
| sub B_chkconfig_off { |
| |
| my $startup_script=$_[0]; |
| my $retval=1; |
| |
| my $chkconfig_line; |
| my @runlevels; |
| my ($start_order,$stop_order,$filetolink); |
| |
| if (&GetDistro =~/^DB.*/) { |
| $filetolink = &getGlobal('DIR', "initd") . "/$startup_script"; |
| if (-x $filetolink) |
| { |
| # Three ways to do this in Debian: |
| # 1.- have the initd script set to 600 mode |
| # 2.- Remove the links in rcd (re-installing the package |
| # will break it) |
| # 3.- Use update-rc.d --remove (same as 2.) |
| # (jfs) |
| &B_chmod(0600,$filetolink); |
| $retval=6; |
| |
| # The second option |
| #foreach my $level ("0","1","2","3","4","5","6" ) { |
| #my $link = ''; |
| #$link = &getGlobal('DIR', "rcd") . "/rc" . "$level" . ".d/K50" . "$startup_script"; |
| #unlink($link); |
| #} |
| } |
| } |
| |
| # |
| # On SUSE, chkconfig-based rc scripts have been replaced with a whole different |
| # system. chkconfig on SUSE is actually a shell script that does some stuff and then |
| # calls insserv, their replacement. |
| # |
| elsif (&GetDistro =~ /^SE/) { |
| # only try to chkconfig off if init script is found |
| if ( -e (&getGlobal('DIR', "initd") . "/$startup_script") ) { |
| $chkconfig_line=&getGlobal('BIN','chkconfig'); |
| &B_System("$chkconfig_line $startup_script on", "$chkconfig_line $startup_script off"); |
| # chkconfig doesn't take affect until reboot, need to stop service |
| # since expectation is that the daemons are disabled even without a reboot |
| B_service_stop("$startup_script"); |
| return 1; #success |
| } |
| return 0; #failure |
| } |
| else { |
| |
| # Run through the init script looking for the chkconfig line... |
| |
| |
| $retval = open CHKCONFIG,&getGlobal('DIR', "initd") . "/$startup_script"; |
| unless ($retval) { |
| &B_log("ACTION","Didn't chkconfig_off $startup_script because we couldn't open " . &getGlobal('DIR', "initd") . "/$startup_script\n"); |
| } |
| else { |
| |
| READ_LOOP: |
| while (my $line=<CHKCONFIG>) { |
| |
| # We're looking for lines like this one: |
| # # chkconfig: 2345 10 90 |
| |
| if ($line =~ /^#\s*chkconfig:\s*([-\d]+)\s*(\d+)\s*(\d+)/ ) { |
| @runlevels=split //,$1; |
| $start_order=$2; |
| $stop_order=$3; |
| |
| |
| # Change single digit run levels to double digit -- otherwise, |
| # the alphabetic ordering chkconfig depends on fails. |
| if ($start_order =~ /^\d$/ ) { |
| $start_order = "0" . $start_order; |
| &B_log("ACTION","chkconfig_off converted start order to $start_order\n"); |
| } |
| if ($stop_order =~ /^\d$/ ) { |
| $stop_order = "0" . $stop_order; |
| &B_log("ACTION","chkconfig_off converted stop order to $stop_order\n"); |
| } |
| |
| last READ_LOOP; |
| } |
| } |
| close CHKCONFIG; |
| |
| # If we never found a chkconfig line, can we just run through all 5 |
| # rcX.d dirs from 1 to 5...? |
| |
| # unless ( $start_order and $stop_order ) { |
| # @runlevels=("1","2","3","4","5"); |
| # $start_order = "*"; $stop_order="*"; |
| # } |
| |
| # Now, run through removing symlinks... |
| |
| |
| |
| $retval=0; |
| |
| # Handle the special case that the run level specified is solely "-" |
| if ($runlevels[0] =~ /-/) { |
| @runlevels = ( "0","1","2","3","4","5","6" ); |
| } |
| |
| foreach my $level ( @runlevels ) { |
| my $link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/S$start_order" . $startup_script; |
| my $new_link = &getGlobal('DIR', "rcd") . "/rc" . $level . ".d/K$stop_order" . $startup_script; |
| my $target = &getGlobal('DIR', "initd") ."/" . $startup_script; |
| my $local_return; |
| |
| |
| # Replace the S__ link in this level with a K__ link. |
| if ( -e $link ) { |
| unless ($GLOBAL_LOGONLY) { |
| $local_return=unlink $link; |
| if ($local_return) { |
| $local_return=symlink $target,$new_link; |
| unless ($local_return) { |
| &B_log("ERROR","Linking $target to $new_link failed.\n"); |
| } |
| } |
| else { # unlinking failed |
| &B_log("ERROR","Unlinking $link failed\n"); |
| } |
| |
| } |
| if ($local_return) { |
| $retval++; |
| &B_log("ACTION","Removed link $link\n"); |
| |
| # |
| # If we removed the link, add a link command to the revert file |
| # Write out the revert information for recreating the S__ |
| # symlink and deleting the K__ symlink. |
| &B_revert_log(&getGlobal('BIN',"ln") . " -s $target $link\n"); |
| &B_revert_log(&getGlobal('BIN',"rm") . " -f $new_link\n"); |
| } |
| else { |
| &B_log("ERROR","B_chkconfig_off $startup_script failed\n"); |
| } |
| |
| } |
| } # foreach |
| |
| } # else-unless |
| |
| } # else-DB |
| if ($retval < @runlevels) { |
| $retval=0; |
| } |
| |
| $retval; |
| |
| } |
| |
| |
| ########################################################################### |
| # &B_service_start ($daemon_name) |
| # Starts service on RedHat/SUSE-based Linux distributions which have the |
| # service command: |
| # |
| # service $daemon_name start |
| # |
| # Other Linux distros that also support this method of starting |
| # services can be added to use this function. |
| # |
| # Here an example of where you might use this: |
| # |
| # You'd like to tell the system to start the vsftpd daemon: |
| # &B_service_start("vsftpd") |
| # |
| # Uses &B_System in HP_API.pm |
| # To match how the &B_System command works this method: |
| # returns 1 on success |
| # returns 0 on failure |
| ########################################################################### |
| |
| sub B_service_start { |
| |
| my $daemon=$_[0]; |
| |
| if ( (&GetDistro !~ /^SE/) and (&GetDistro !~ /^RH/) and |
| (&GetDistro !~ /^RHFC/) and (&GetDistro !~ /^MN/) ) { |
| &B_log("ERROR","Tried to call service_start on a system lacking a service command! Internal Bastille error."); |
| return undef; |
| } |
| |
| # only start service if init script is found |
| if ( -e (&getGlobal('DIR', 'initd') . "/$daemon") ) { |
| &B_log("ACTION","# service_start enabling $daemon\n"); |
| |
| my $service_cmd=&getGlobal('BIN', 'service'); |
| if ($service_cmd) { |
| # Start the service, |
| # Also provide &B_System revert command |
| |
| return (&B_System("$service_cmd $daemon start", |
| "$service_cmd $daemon stop")); |
| } |
| } |
| |
| # init script not found, do not try to start, return failure |
| return 0; |
| } |
| |
| ########################################################################### |
| # &B_service_stop ($daemon_name) |
| # Stops service on RedHat/SUSE-based Linux distributions which have the |
| # service command: |
| # |
| # service $daemon_name stop |
| # |
| # Other Linux distros that also support this method of starting |
| # services can be added to use this function. |
| # Stops service. |
| # |
| # |
| # Here an example of where you might use this: |
| # |
| # You'd like to tell the system to stop the vsftpd daemon: |
| # &B_service_stop("vsftpd") |
| # |
| # Uses &B_System in HP_API.pm |
| # To match how the &B_System command works this method: |
| # returns 1 on success |
| # returns 0 on failure |
| ########################################################################### |
| |
| sub B_service_stop { |
| |
| my $daemon=$_[0]; |
| |
| if ( (&GetDistro !~ /^SE/) and (&GetDistro !~ /^RH/) and |
| (&GetDistro !~ /^RHFC/) and (&GetDistro !~ /^MN/) ) { |
| &B_log("ERROR","Tried to call service_stop on a system lacking a service command! Internal Bastille error."); |
| return undef; |
| } |
| |
| # only stop service if init script is found |
| if ( -e (&getGlobal('DIR', 'initd') . "/$daemon") ) { |
| &B_log("ACTION","# service_stop disabling $daemon\n"); |
| |
| my $service_cmd=&getGlobal('BIN', 'service'); |
| if ($service_cmd) { |
| |
| # Stop the service, |
| # Also provide &B_System revert command |
| |
| return (&B_System("$service_cmd $daemon stop", |
| "$service_cmd $daemon start")); |
| } |
| } |
| |
| # init script not found, do not try to stop, return failure |
| return 0; |
| } |
| |
| |
| ########################################################################### |
| # &B_service_restart ($daemon_name) |
| # Restarts service on RedHat/SUSE-based Linux distributions which have the |
| # service command: |
| # |
| # service $daemon_name restart |
| # |
| # Other Linux distros that also support this method of starting |
| # services can be added to use this function. |
| # |
| # Here an example of where you might use this: |
| # |
| # You'd like to tell the system to restart the vsftpd daemon: |
| # &B_service_restart("vsftpd") |
| # |
| # Uses &B_System in HP_API.pm |
| # To match how the &B_System command works this method: |
| # returns 1 on success |
| # returns 0 on failure |
| ########################################################################### |
| |
| sub B_service_restart { |
| |
| my $daemon=$_[0]; |
| |
| if ( (&GetDistro !~ /^SE/) and (&GetDistro !~ /^RH/) and |
| (&GetDistro !~ /^RHFC/) and (&GetDistro !~ /^MN/) ) { |
| &B_log("ERROR","Tried to call service_restart on a system lacking a service command! Internal Bastille error."); |
| return undef; |
| } |
| |
| # only restart service if init script is found |
| if ( -e (&getGlobal('DIR', 'initd') . "/$daemon") ) { |
| &B_log("ACTION","# service_restart re-enabling $daemon\n"); |
| |
| my $service_cmd=&getGlobal('BIN', 'service'); |
| if ($service_cmd) { |
| |
| # Restart the service |
| return (&B_System("$service_cmd $daemon restart", |
| "$service_cmd $daemon restart")); |
| } |
| } |
| |
| # init script not found, do not try to restart, return failure |
| return 0; |
| } |
| |
| ########################################################################### |
| # &B_is_service_off($;$) |
| # |
| # Runs the specified test to determine whether or not the question should |
| # be answered. |
| # |
| # return values: |
| # NOTSECURE_CAN_CHANGE()/0: service is on |
| # SECURE_CANT_CHANGE()/1: service is off |
| # undef: test is not defined |
| ########################################################################### |
| |
| sub B_is_service_off ($){ |
| my $service=$_[0]; |
| |
| if(&GetDistro =~ "^HP-UX"){ |
| #die "Why do I think I'm on HPUX?!\n"; |
| return &checkServiceOnHPUX($service); |
| } |
| elsif ( (&GetDistro =~ "^RH") || (&GetDistro =~ "^SE") ) { |
| return &checkServiceOnLinux($service); |
| } |
| else { |
| &B_log("DEBUG","B_is_service off called for unsupported OS"); |
| # not yet implemented for other distributions of Linux |
| # when GLOBAL_SERVICE, GLOBAL_SERVTYPE and GLOBAL_PROCESS are filled |
| # in for Linux, then |
| # at least inetd and inittab services should be similar to the above, |
| # whereas chkconfig would be used on some Linux distros to determine |
| # if non-inetd/inittab services are running at boot time. Looking at |
| # processes should be similar. |
| return undef; |
| } |
| } |
| |
| ########################################################################### |
| # &checkServiceOnLinux($service); |
| # |
| # Checks if the given service is running on a Linux system. This is |
| # called by B_is_Service_Off(), which is the function that Bastille |
| # modules should call. |
| # |
| # Return values: |
| # NOTSECURE_CAN_CHANGE() if the service is on |
| # SECURE_CANT_CHANGE() if the service is off |
| # undef if the state of the service cannot be determined |
| # |
| ########################################################################### |
| sub checkServiceOnLinux($) { |
| my $service=$_[0]; |
| |
| # get the list of parameters which could be used to initiate the service |
| # (could be in /etc/rc.d/rc?.d, /etc/inetd.conf, or /etc/inittab, so we |
| # check all of them) |
| |
| my @params = @{ &getGlobal('SERVICE', $service) }; |
| my $chkconfig = &getGlobal('BIN', 'chkconfig'); |
| my $grep = &getGlobal('BIN', 'grep'); |
| my $inittab = &getGlobal('FILE', 'inittab'); |
| my $serviceType = &getGlobal('SERVTYPE', $service);; |
| |
| # A kludge to get things running because &getGlobal('SERVICE' doesn't |
| # return the expected values. |
| @params = (); |
| push (@params, $service); |
| |
| foreach my $param (@params) { |
| &B_log("DEBUG","Checking to see if service $service is off.\n"); |
| |
| if ($serviceType =~ /rc/) { |
| my $on = &B_Backtick("$chkconfig --list $param 2>&1"); |
| if ($on =~ /^$param:\s+unknown/) { |
| # This service isn't installed on the system |
| return NOT_INSTALLED(); |
| } |
| if ($on =~ /^error reading information on service $param: No such file or directory/) { |
| # This service isn't installed on the system |
| return NOT_INSTALLED(); |
| } |
| if ($on =~ /^error/) { |
| # This probably |
| &B_log("DEBUG","chkconfig returned: $param=$on\n"); |
| return undef; |
| } |
| $on =~ s/^$param\s+//; # remove the service name and spaces |
| $on =~ s/[0-6]:off\s*//g; # remove any runlevel:off entries |
| $on =~ s/:on\s*//g; # remove the :on from the runlevels |
| # what remains is a list of runlevels in which the service is on, |
| # or a null string if it is never turned on |
| chomp $on; # newline should be gone already (\s) |
| &B_log("DEBUG","chkconfig returned: $param=$on\n"); |
| |
| if ($on =~ /^\d+$/) { |
| # service is not off |
| ########################### BREAK out, don't skip question |
| return NOTSECURE_CAN_CHANGE(); |
| } |
| } |
| elsif ($serviceType =~ /inet/) { |
| my $on = &B_Backtick("$chkconfig --list $param 2>&1"); |
| if ($on =~ /^$param:\s+unknown/) { |
| # This service isn't installed on the system |
| return NOT_INSTALLED(); |
| } |
| if ($on =~ /^error reading information on service $param: No such file or directory/) { |
| # This service isn't installed on the system |
| return NOT_INSTALLED(); |
| } |
| if ($on =~ /^error/ ) { |
| # Something else is wrong? |
| # return undef |
| return undef; |
| } |
| if ($on =~ tr/\n// > 1) { |
| $on =~ s/^xinetd.+\n//; |
| } |
| $on =~ s/^\s*$param:?\s+//; # remove the service name and spaces |
| chomp $on; # newline should be gone already (\s) |
| &B_log("DEBUG","chkconfig returned: $param=$on\n"); |
| |
| if ($on =~ /^on$/) { |
| # service is not off |
| ########################### BREAK out, don't skip question |
| return NOTSECURE_CAN_CHANGE(); |
| } |
| } |
| else { |
| # perhaps the service is started by inittab |
| my $inittabline = &B_Backtick("$grep -E '^[^#].{0,3}:.*:.+:.*$param' $inittab"); |
| if ($inittabline =~ /.+/) { # . matches anything except newlines |
| # service is not off |
| &B_log("DEBUG","Checking inittab; found $inittabline\n"); |
| ########################### BREAK out, don't skip question |
| return NOTSECURE_CAN_CHANGE(); |
| } |
| } |
| } # foreach my $param |
| |
| |
| # boot-time parameters are not set; check processes |
| # Note the checkProcsforService returns INCONSISTENT() if a process is found |
| # assuming the checks above |
| return &checkProcsForService($service); |
| } |
| |
| 1; |
| |
| |