go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/services/sysv.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package services
     5  
     6  import (
     7  	"bufio"
     8  	"fmt"
     9  	"io"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"github.com/rs/zerolog/log"
    14  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    15  	"go.mondoo.com/cnquery/utils/stringx"
    16  )
    17  
    18  type SysVServiceManager struct {
    19  	conn shared.Connection
    20  }
    21  
    22  func (s *SysVServiceManager) Name() string {
    23  	return "SysV Service Manager"
    24  }
    25  
    26  func (s *SysVServiceManager) List() ([]*Service, error) {
    27  	// 1. gather all services
    28  	services, err := s.services()
    29  	if err != nil {
    30  		return nil, err
    31  	}
    32  
    33  	// 2. gather all run levels
    34  	rl, err := s.serviceRunLevel()
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	// eg. we ignore the following run levels since `service halt status` may shutdown the system
    40  	ignored := []string{"boot", "boot.local", "functions", "halt", "halt.local", "killall", "rc", "reboot", "shutdown", "single", "skeleton", ".depend.boot", ".depend.start", ".depend.stop"}
    41  	statusServices := []string{}
    42  	for i := range services {
    43  		service := services[i]
    44  		if stringx.Contains(ignored, service) {
    45  			continue
    46  		}
    47  		statusServices = append(statusServices, service)
    48  	}
    49  
    50  	// 3. mimic `service --status-all` by running `service x status` for each detected service
    51  	running, err := s.running(statusServices)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  
    56  	// aggregate data into service struct
    57  	res := []*Service{}
    58  
    59  	for i := range statusServices {
    60  		service := statusServices[i]
    61  
    62  		srv := &Service{
    63  			Name:      service,
    64  			Enabled:   len(rl[service]) > 0,
    65  			Installed: true,
    66  			Running:   running[service] == true,
    67  			Type:      "sysv",
    68  		}
    69  
    70  		if srv.Running {
    71  			srv.State = ServiceRunning
    72  		} else {
    73  			srv.State = ServiceStopped
    74  		}
    75  		res = append(res, srv)
    76  	}
    77  
    78  	return res, nil
    79  }
    80  
    81  func (s *SysVServiceManager) services() ([]string, error) {
    82  	c, err := s.conn.RunCommand("ls -1 /etc/init.d/")
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	services := ParseSysvServices(c.Stdout)
    88  	return services, nil
    89  }
    90  
    91  func (s *SysVServiceManager) serviceRunLevel() (map[string][]SysVServiceRunlevel, error) {
    92  	c, _ := s.conn.RunCommand("find /etc/rc*.d -name 'S*'")
    93  	// it may happen that /etc/init.d/rc does not exist, eg on centos 6
    94  	return ParseSysVRunlevel(c.Stdout)
    95  }
    96  
    97  func (s *SysVServiceManager) running(services []string) (map[string]bool, error) {
    98  	res := map[string]bool{}
    99  
   100  	for i := range services {
   101  		service := services[i]
   102  		running := true
   103  
   104  		serviceStatusCmd, err := s.conn.RunCommand(fmt.Sprintf("service %s status", service))
   105  		if err != nil || serviceStatusCmd.ExitStatus != 0 {
   106  			running = false
   107  		}
   108  		res[service] = running
   109  	}
   110  
   111  	return res, nil
   112  }
   113  
   114  func ParseSysvServices(r io.Reader) []string {
   115  	services := []string{}
   116  	scanner := bufio.NewScanner(r)
   117  	for scanner.Scan() {
   118  		line := scanner.Text()
   119  		service := strings.TrimSpace(line)
   120  		if service == "" {
   121  			continue
   122  		}
   123  		services = append(services, service)
   124  	}
   125  	return services
   126  }
   127  
   128  var runlevelRegex = regexp.MustCompile(`rc([0-6])\.d\/S(\d+)(.*)$`)
   129  
   130  type SysVServiceRunlevel struct {
   131  	Level string
   132  	Order string
   133  }
   134  
   135  func ParseSysVRunlevel(r io.Reader) (map[string][]SysVServiceRunlevel, error) {
   136  	res := map[string][]SysVServiceRunlevel{}
   137  	scanner := bufio.NewScanner(r)
   138  	for scanner.Scan() {
   139  		line := scanner.Text()
   140  		m := runlevelRegex.FindStringSubmatch(line)
   141  		if len(m) != 4 {
   142  			log.Error().Str("line", line).Msg("cannot parse sysv runlevel")
   143  			continue
   144  		}
   145  
   146  		service := m[3]
   147  		srl := SysVServiceRunlevel{
   148  			Level: m[1],
   149  			Order: m[2],
   150  		}
   151  
   152  		entry, ok := res[service]
   153  		if !ok {
   154  			entry = []SysVServiceRunlevel{}
   155  		}
   156  
   157  		entry = append(entry, srl)
   158  		res[service] = entry
   159  	}
   160  	return res, nil
   161  }