go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/detector/detector_all.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package detector
     5  
     6  import (
     7  	"bytes"
     8  	"io"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/rs/zerolog/log"
    14  	"github.com/spf13/afero"
    15  	"go.mondoo.com/cnquery/providers-sdk/v1/inventory"
    16  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    17  	win "go.mondoo.com/cnquery/providers/os/detector/windows"
    18  )
    19  
    20  // Operating Systems
    21  var macOS = &PlatformResolver{
    22  	Name:     "macos",
    23  	IsFamily: false,
    24  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
    25  		// when we reach here, we know it is darwin
    26  		// check xml /System/Library/CoreServices/SystemVersion.plist
    27  		f, err := conn.FileSystem().Open("/System/Library/CoreServices/SystemVersion.plist")
    28  		if err != nil {
    29  			return false, nil
    30  		}
    31  		defer f.Close()
    32  
    33  		c, err := io.ReadAll(f)
    34  		if err != nil || len(c) == 0 {
    35  			return false, nil
    36  		}
    37  
    38  		sv, err := ParseMacOSSystemVersion(string(c))
    39  		if err != nil || len(c) == 0 {
    40  			return false, nil
    41  		}
    42  
    43  		pf.Name = "macos"
    44  		pf.Title = sv["ProductName"]
    45  		pf.Version = sv["ProductVersion"]
    46  		pf.Build = sv["ProductBuildVersion"]
    47  
    48  		return true, nil
    49  	},
    50  }
    51  
    52  // is part of the darwin platfrom and fallback for non-known darwin systems
    53  var otherDarwin = &PlatformResolver{
    54  	Name:     "darwin",
    55  	IsFamily: false,
    56  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
    57  		return true, nil
    58  	},
    59  }
    60  
    61  var alpine = &PlatformResolver{
    62  	Name:     "alpine",
    63  	IsFamily: false,
    64  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
    65  		// check if we are on edge
    66  		osrd := NewOSReleaseDetector(conn)
    67  		osr, err := osrd.osrelease()
    68  		if err != nil {
    69  			return false, nil
    70  		}
    71  
    72  		if osr["PRETTY_NAME"] == "Alpine Linux edge" {
    73  			pf.Name = "alpine"
    74  			pf.Version = "edge"
    75  			pf.Build = osr["VERSION_ID"]
    76  		}
    77  
    78  		// if we are on alpine, the release was detected properly from parent check
    79  		if pf.Name == "alpine" {
    80  			return true, nil
    81  		}
    82  
    83  		f, err := conn.FileSystem().Open("/etc/alpine-release")
    84  		if err != nil {
    85  			return false, nil
    86  		}
    87  		defer f.Close()
    88  
    89  		c, err := io.ReadAll(f)
    90  		if err != nil || len(c) == 0 {
    91  			return false, nil
    92  		}
    93  
    94  		pf.Name = "alpine"
    95  		return true, nil
    96  	},
    97  }
    98  
    99  var arch = &PlatformResolver{
   100  	Name:     "arch",
   101  	IsFamily: false,
   102  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   103  		if pf.Name == "arch" {
   104  			return true, nil
   105  		}
   106  		return false, nil
   107  	},
   108  }
   109  
   110  var manjaro = &PlatformResolver{
   111  	Name:     "manjaro",
   112  	IsFamily: false,
   113  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   114  		if pf.Name == "manjaro" {
   115  			return true, nil
   116  		}
   117  		return false, nil
   118  	},
   119  }
   120  
   121  var debian = &PlatformResolver{
   122  	Name:     "debian",
   123  	IsFamily: false,
   124  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   125  		osrd := NewOSReleaseDetector(conn)
   126  
   127  		f, err := conn.FileSystem().Open("/etc/debian_version")
   128  		if err != nil {
   129  			return false, nil
   130  		}
   131  		defer f.Close()
   132  
   133  		c, err := io.ReadAll(f)
   134  		if err != nil || len(c) == 0 {
   135  			return false, nil
   136  		}
   137  
   138  		osr, err := osrd.osrelease()
   139  		if err != nil {
   140  			return false, nil
   141  		}
   142  
   143  		if osr["ID"] != "debian" {
   144  			return false, nil
   145  		}
   146  
   147  		pf.Version = strings.TrimSpace(string(c))
   148  
   149  		unamem, err := osrd.unamem()
   150  		if err == nil {
   151  			pf.Arch = unamem
   152  		}
   153  
   154  		return true, nil
   155  	},
   156  }
   157  
   158  var ubuntu = &PlatformResolver{
   159  	Name:     "ubuntu",
   160  	IsFamily: false,
   161  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   162  		if pf.Name == "ubuntu" {
   163  			return true, nil
   164  		}
   165  		return false, nil
   166  	},
   167  }
   168  
   169  var raspbian = &PlatformResolver{
   170  	Name:     "raspbian",
   171  	IsFamily: false,
   172  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   173  		if pf.Name == "raspbian" {
   174  			return true, nil
   175  		}
   176  		return false, nil
   177  	},
   178  }
   179  
   180  var kali = &PlatformResolver{
   181  	Name:     "kali",
   182  	IsFamily: false,
   183  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   184  		if pf.Name == "kali" {
   185  			return true, nil
   186  		}
   187  		return false, nil
   188  	},
   189  }
   190  
   191  var linuxmint = &PlatformResolver{
   192  	Name:     "linuxmint",
   193  	IsFamily: false,
   194  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   195  		if pf.Name == "linuxmint" {
   196  			return true, nil
   197  		}
   198  		return false, nil
   199  	},
   200  }
   201  
   202  var popos = &PlatformResolver{
   203  	Name:     "pop",
   204  	IsFamily: false,
   205  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   206  		if pf.Name == "pop" {
   207  			return true, nil
   208  		}
   209  		return false, nil
   210  	},
   211  }
   212  
   213  // rhel PlatformResolver only detects redhat and no derivatives
   214  var rhel = &PlatformResolver{
   215  	Name:     "redhat",
   216  	IsFamily: false,
   217  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   218  		// etc redhat release was parsed by the family already,
   219  		// we reuse that information here
   220  		// e.g. Red Hat Linux, Red Hat Enterprise Linux Server
   221  		if strings.Contains(pf.Title, "Red Hat") || pf.Name == "redhat" {
   222  			pf.Name = "redhat"
   223  			return true, nil
   224  		}
   225  
   226  		// fallback to /etc/redhat-release file
   227  		f, err := conn.FileSystem().Open("/etc/redhat-release")
   228  		if err != nil {
   229  			return false, nil
   230  		}
   231  		defer f.Close()
   232  
   233  		c, err := io.ReadAll(f)
   234  		if err != nil || len(c) == 0 {
   235  			return false, nil
   236  		}
   237  
   238  		if strings.Contains(string(c), "Red Hat") {
   239  			pf.Name = "redhat"
   240  			return true, nil
   241  		}
   242  
   243  		return false, nil
   244  	},
   245  }
   246  
   247  var eurolinux = &PlatformResolver{
   248  	Name:     "eurolinux",
   249  	IsFamily: false,
   250  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   251  		if pf.Name == "eurolinux" {
   252  			return true, nil
   253  		}
   254  		return false, nil
   255  	},
   256  }
   257  
   258  // The centos platform resolver finds CentOS and CentOS-like platforms like alma and rocky
   259  var centos = &PlatformResolver{
   260  	Name:     "centos",
   261  	IsFamily: false,
   262  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   263  		// works for centos 5+
   264  		if strings.Contains(pf.Title, "CentOS") || pf.Name == "centos" {
   265  			pf.Name = "centos"
   266  			return true, nil
   267  		}
   268  
   269  		// adapt the name for rocky to align it with amazonlinux, almalinux etc.
   270  		if pf.Name == "rocky" {
   271  			pf.Name = "rockylinux"
   272  		}
   273  
   274  		// newer alma linux do not have /etc/centos-release, check for alma linux
   275  		afs := &afero.Afero{Fs: conn.FileSystem()}
   276  		if pf.Name == "almalinux" {
   277  			if ok, err := afs.Exists("/etc/almalinux-release"); err == nil && ok {
   278  				return true, nil
   279  			}
   280  		}
   281  
   282  		// newer rockylinux do not have /etc/centos-release
   283  		if pf.Name == "rockylinux" {
   284  			if ok, err := afs.Exists("/etc/rocky-release"); err == nil && ok {
   285  				return true, nil
   286  			}
   287  		}
   288  
   289  		// NOTE: CentOS 5 does not have /etc/centos-release
   290  		// fallback to /etc/centos-release file
   291  		if ok, err := afs.Exists("/etc/centos-release"); err != nil || !ok {
   292  			return false, nil
   293  		}
   294  
   295  		if len(pf.Name) == 0 {
   296  			pf.Name = "centos"
   297  		}
   298  
   299  		return true, nil
   300  	},
   301  }
   302  
   303  var fedora = &PlatformResolver{
   304  	Name:     "fedora",
   305  	IsFamily: false,
   306  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   307  		if strings.Contains(pf.Title, "Fedora") || pf.Name == "fedora" {
   308  			pf.Name = "fedora"
   309  			return true, nil
   310  		}
   311  
   312  		// fallback to /etc/fedora-release file
   313  		f, err := conn.FileSystem().Open("/etc/fedora-release")
   314  		if err != nil {
   315  			return false, nil
   316  		}
   317  		defer f.Close()
   318  
   319  		c, err := io.ReadAll(f)
   320  		if err != nil || len(c) == 0 {
   321  			return false, nil
   322  		}
   323  
   324  		if len(pf.Name) == 0 {
   325  			pf.Name = "fedora"
   326  		}
   327  
   328  		return true, nil
   329  	},
   330  }
   331  
   332  var oracle = &PlatformResolver{
   333  	Name:     "oracle",
   334  	IsFamily: false,
   335  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   336  		// works for oracle 7+
   337  		if pf.Name == "ol" {
   338  			pf.Name = "oraclelinux"
   339  			return true, nil
   340  		}
   341  
   342  		// check if we have /etc/centos-release file
   343  		f, err := conn.FileSystem().Open("/etc/oracle-release")
   344  		if err != nil {
   345  			return false, nil
   346  		}
   347  		defer f.Close()
   348  
   349  		c, err := io.ReadAll(f)
   350  		if err != nil || len(c) == 0 {
   351  			return false, nil
   352  		}
   353  
   354  		if len(pf.Name) == 0 {
   355  			pf.Name = "oraclelinux"
   356  		}
   357  
   358  		return true, nil
   359  	},
   360  }
   361  
   362  var scientific = &PlatformResolver{
   363  	Name:     "scientific",
   364  	IsFamily: false,
   365  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   366  		// works for oracle 7+
   367  		if pf.Name == "scientific" {
   368  			return true, nil
   369  		}
   370  
   371  		// we only get here if this is a rhel distribution
   372  		if strings.Contains(pf.Title, "Scientific Linux") {
   373  			pf.Name = "scientific"
   374  			return true, nil
   375  		}
   376  
   377  		return false, nil
   378  	},
   379  }
   380  
   381  var amazonlinux = &PlatformResolver{
   382  	Name:     "amazonlinux",
   383  	IsFamily: false,
   384  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   385  		if pf.Name == "amzn" {
   386  			pf.Name = "amazonlinux"
   387  			return true, nil
   388  		}
   389  		return false, nil
   390  	},
   391  }
   392  
   393  var windriver = &PlatformResolver{
   394  	Name:     "wrlinux",
   395  	IsFamily: false,
   396  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   397  		if pf.Name == "wrlinux" {
   398  			return true, nil
   399  		}
   400  		return false, nil
   401  	},
   402  }
   403  
   404  var opensuse = &PlatformResolver{
   405  	Name:     "opensuse",
   406  	IsFamily: false,
   407  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   408  		if pf.Name == "opensuse" || pf.Name == "opensuse-leap" || pf.Name == "opensuse-tumbleweed" {
   409  			return true, nil
   410  		}
   411  
   412  		return false, nil
   413  	},
   414  }
   415  
   416  var sles = &PlatformResolver{
   417  	Name:     "sles",
   418  	IsFamily: false,
   419  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   420  		if pf.Name == "sles" {
   421  			return true, nil
   422  		}
   423  		return false, nil
   424  	},
   425  }
   426  
   427  var suseMicroOs = &PlatformResolver{
   428  	Name:     "suse-microos",
   429  	IsFamily: false,
   430  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   431  		if pf.Name == "suse-microos" {
   432  			return true, nil
   433  		}
   434  		return false, nil
   435  	},
   436  }
   437  
   438  var gentoo = &PlatformResolver{
   439  	Name:     "gentoo",
   440  	IsFamily: false,
   441  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   442  		f, err := conn.FileSystem().Open("/etc/gentoo-release")
   443  		if err != nil {
   444  			return false, nil
   445  		}
   446  		defer f.Close()
   447  
   448  		c, err := io.ReadAll(f)
   449  		if err != nil || len(c) == 0 {
   450  			log.Debug().Err(err)
   451  			return false, nil
   452  		}
   453  
   454  		content := strings.TrimSpace(string(c))
   455  		name, release, err := ParseRhelVersion(content)
   456  		if err == nil {
   457  			// only set title if not already properly detected by lsb or os-release
   458  			if len(pf.Title) == 0 {
   459  				pf.Title = name
   460  			}
   461  			if len(pf.Version) == 0 {
   462  				pf.Version = release
   463  			}
   464  		}
   465  
   466  		return false, nil
   467  	},
   468  }
   469  
   470  var ubios = &PlatformResolver{
   471  	Name:     "ubios",
   472  	IsFamily: false,
   473  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   474  		if pf.Name == "ubios" {
   475  			return true, nil
   476  		}
   477  		return false, nil
   478  	},
   479  }
   480  
   481  var busybox = &PlatformResolver{
   482  	Name:     "busybox",
   483  	IsFamily: false,
   484  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   485  		busyboxExists, err := afero.Exists(conn.FileSystem(), "/bin/busybox")
   486  		if !busyboxExists || err != nil {
   487  			return false, nil
   488  		}
   489  
   490  		// we need to read this file because all others show up as zero size
   491  		// This fille seems to be the "original"
   492  		// all others are hardlinks
   493  		f, err := conn.FileSystem().Open("/bin/[")
   494  		if err != nil {
   495  			return false, nil
   496  		}
   497  		defer f.Close()
   498  
   499  		content, err := io.ReadAll(f)
   500  		if err != nil {
   501  			return false, err
   502  		}
   503  
   504  		// strings are \0 terminated
   505  		rodataByteStrings := bytes.Split(content, []byte("\x00"))
   506  		if rodataByteStrings == nil {
   507  			return false, nil
   508  		}
   509  
   510  		releaseRegex := regexp.MustCompile(`^(.+)\s(v[\d\.]+)\s*\((.*)\).*$`)
   511  		for _, rodataByteString := range rodataByteStrings {
   512  			rodataString := string(rodataByteString)
   513  			m := releaseRegex.FindStringSubmatch(rodataString)
   514  			if len(m) >= 2 {
   515  				title := m[1]
   516  				release := m[2]
   517  
   518  				if strings.ToLower(title) == "busybox" {
   519  					pf.Name = "busybox"
   520  					pf.Title = title
   521  					pf.Version = release
   522  					return true, nil
   523  				}
   524  			}
   525  		}
   526  
   527  		return false, nil
   528  	},
   529  }
   530  
   531  var photon = &PlatformResolver{
   532  	Name:     "photon",
   533  	IsFamily: false,
   534  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   535  		if pf.Name == "photon" {
   536  			return true, nil
   537  		}
   538  		return false, nil
   539  	},
   540  }
   541  
   542  var openwrt = &PlatformResolver{
   543  	Name:     "openwrt",
   544  	IsFamily: false,
   545  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   546  		// No clue why they are not using either lsb-release or os-release
   547  		f, err := conn.FileSystem().Open("/etc/openwrt_release")
   548  		if err != nil {
   549  			return false, err
   550  		}
   551  		defer f.Close()
   552  
   553  		content, err := io.ReadAll(f)
   554  		if err != nil {
   555  			return false, err
   556  		}
   557  
   558  		lsb, err := ParseLsbRelease(string(content))
   559  		if err == nil {
   560  			if len(lsb["DISTRIB_ID"]) > 0 {
   561  				pf.Name = strings.ToLower(lsb["DISTRIB_ID"])
   562  				pf.Title = lsb["DISTRIB_ID"]
   563  			}
   564  			if len(lsb["DISTRIB_RELEASE"]) > 0 {
   565  				pf.Version = lsb["DISTRIB_RELEASE"]
   566  			}
   567  
   568  			return true, nil
   569  		}
   570  
   571  		return false, nil
   572  	},
   573  }
   574  
   575  var (
   576  	plcnextVersion      = regexp.MustCompile(`(?m)^Arpversion:\s+(.*)$`)
   577  	plcnextBuildVersion = regexp.MustCompile(`(?m)^GIT Commit Hash:\s+(.*)$`)
   578  )
   579  
   580  var plcnext = &PlatformResolver{
   581  	Name:     "plcnext",
   582  	IsFamily: false,
   583  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   584  		// No clue why they are not using either lsb-release or os-release
   585  		f, err := conn.FileSystem().Open("/etc/plcnext/arpversion")
   586  		if err != nil {
   587  			return false, err
   588  		}
   589  		defer f.Close()
   590  
   591  		content, err := io.ReadAll(f)
   592  		if err != nil {
   593  			return false, err
   594  		}
   595  
   596  		m := plcnextVersion.FindStringSubmatch(string(content))
   597  		if len(m) >= 2 {
   598  			pf.Name = "plcnext"
   599  			pf.Title = "PLCnext"
   600  			pf.Version = m[1]
   601  
   602  			bm := plcnextBuildVersion.FindStringSubmatch(string(content))
   603  			if len(bm) >= 2 {
   604  				pf.Build = bm[1]
   605  			}
   606  
   607  			return true, err
   608  		}
   609  
   610  		return false, nil
   611  	},
   612  }
   613  
   614  // fallback linux detection, since we do not know the system, the family detection may not be correct
   615  var defaultLinux = &PlatformResolver{
   616  	Name:     "generic-linux",
   617  	IsFamily: false,
   618  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   619  		// if we reach here, we know that we detected linux already
   620  		log.Debug().Msg("platform> we do not know the linux system, but we do our best in guessing")
   621  		return true, nil
   622  	},
   623  }
   624  
   625  var netbsd = &PlatformResolver{
   626  	Name:     "netbsd",
   627  	IsFamily: false,
   628  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   629  		if strings.Contains(strings.ToLower(pf.Name), "netbsd") == false {
   630  			return false, nil
   631  		}
   632  
   633  		osrd := NewOSReleaseDetector(conn)
   634  		release, err := osrd.unamer()
   635  		if err == nil {
   636  			pf.Version = release
   637  		}
   638  
   639  		return true, nil
   640  	},
   641  }
   642  
   643  var freebsd = &PlatformResolver{
   644  	Name:     "freebsd",
   645  	IsFamily: false,
   646  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   647  		if strings.Contains(strings.ToLower(pf.Name), "freebsd") == false {
   648  			return false, nil
   649  		}
   650  
   651  		osrd := NewOSReleaseDetector(conn)
   652  		release, err := osrd.unamer()
   653  		if err == nil {
   654  			pf.Version = release
   655  		}
   656  
   657  		return true, nil
   658  	},
   659  }
   660  
   661  var openbsd = &PlatformResolver{
   662  	Name:     "openbsd",
   663  	IsFamily: false,
   664  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   665  		if strings.Contains(strings.ToLower(pf.Name), "openbsd") == false {
   666  			return false, nil
   667  		}
   668  
   669  		osrd := NewOSReleaseDetector(conn)
   670  		release, err := osrd.unamer()
   671  		if err == nil {
   672  			pf.Version = release
   673  		}
   674  
   675  		return true, nil
   676  	},
   677  }
   678  
   679  var dragonflybsd = &PlatformResolver{
   680  	Name:     "dragonflybsd",
   681  	IsFamily: false,
   682  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   683  		if strings.Contains(strings.ToLower(pf.Name), "dragonfly") == false {
   684  			return false, nil
   685  		}
   686  
   687  		pf.Name = "dragonflybsd"
   688  		osrd := NewOSReleaseDetector(conn)
   689  		release, err := osrd.unamer()
   690  		if err == nil {
   691  			pf.Version = release
   692  		}
   693  
   694  		return true, nil
   695  	},
   696  }
   697  
   698  var windows = &PlatformResolver{
   699  	Name:     "windows",
   700  	IsFamily: false,
   701  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   702  		data, err := win.GetWmiInformation(conn)
   703  		if err != nil {
   704  			log.Debug().Err(err).Msg("could not gather wmi information")
   705  			return false, nil
   706  		}
   707  
   708  		pf.Name = "windows"
   709  		pf.Title = data.Caption
   710  
   711  		// instead of using windows major.minor.build.ubr we just use build.ubr since
   712  		// major and minor can be derived from the build version
   713  		pf.Version = data.BuildNumber
   714  
   715  		// FIXME: we need to ask wmic cpu get architecture
   716  		pf.Arch = data.OSArchitecture
   717  
   718  		if pf.Labels == nil {
   719  			pf.Labels = map[string]string{}
   720  		}
   721  		pf.Labels["windows.mondoo.com/product-type"] = data.ProductType
   722  
   723  		// optional: try to get the ubr number (win 10 + 2019)
   724  		current, err := win.GetWindowsOSBuild(conn)
   725  		if err == nil && current.UBR > 0 {
   726  			pf.Build = strconv.Itoa(current.UBR)
   727  		} else {
   728  			log.Debug().Err(err).Msg("could not parse windows current version")
   729  		}
   730  
   731  		return true, nil
   732  	},
   733  }
   734  
   735  var slugRe = regexp.MustCompile("[^a-z0-9]+")
   736  
   737  func slugifyDarwin(s string) string {
   738  	s = strings.ToLower(s)
   739  	s = slugRe.ReplaceAllString(s, "_")
   740  	return strings.Trim(s, "_")
   741  }
   742  
   743  // Families
   744  var darwinFamily = &PlatformResolver{
   745  	Name:     inventory.FAMILY_DARWIN,
   746  	IsFamily: true,
   747  	Children: []*PlatformResolver{macOS, otherDarwin},
   748  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   749  		if strings.Contains(strings.ToLower(pf.Name), "darwin") == false {
   750  			return false, nil
   751  		}
   752  		// from here we know it is a darwin system
   753  
   754  		// read information from /usr/bin/sw_vers
   755  		osrd := NewOSReleaseDetector(conn)
   756  		dsv, err := osrd.darwin_swversion()
   757  		// ignore dsv config if we got an error
   758  		if err == nil {
   759  			if len(dsv["ProductName"]) > 0 {
   760  				// name needs to be slugged
   761  				pf.Name = slugifyDarwin(dsv["ProductName"])
   762  				if pf.Name == "mac_os_x" {
   763  					pf.Name = "macos"
   764  				}
   765  				pf.Title = dsv["ProductName"]
   766  			}
   767  			if len(dsv["ProductVersion"]) > 0 {
   768  				pf.Version = dsv["ProductVersion"]
   769  			}
   770  		} else {
   771  			// TODO: we know its darwin, but without swversion support
   772  			log.Error().Err(err)
   773  		}
   774  
   775  		return true, nil
   776  	},
   777  }
   778  
   779  var bsdFamily = &PlatformResolver{
   780  	Name:     inventory.FAMILY_BSD,
   781  	IsFamily: true,
   782  	Children: []*PlatformResolver{darwinFamily, netbsd, freebsd, openbsd, dragonflybsd},
   783  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   784  		osrd := NewOSReleaseDetector(conn)
   785  		unames, err := osrd.unames()
   786  		if err != nil {
   787  			return false, err
   788  		}
   789  
   790  		unamem, err := osrd.unamem()
   791  		if err == nil {
   792  			pf.Arch = unamem
   793  		}
   794  
   795  		if len(unames) > 0 {
   796  			pf.Name = strings.ToLower(unames)
   797  			pf.Title = unames
   798  			return true, nil
   799  		}
   800  		return false, nil
   801  	},
   802  }
   803  
   804  var redhatFamily = &PlatformResolver{
   805  	Name:     "redhat",
   806  	IsFamily: true,
   807  	// NOTE: oracle pretends to be redhat with /etc/redhat-release and Red Hat Linux, therefore we
   808  	// want to check that platform before redhat
   809  	Children: []*PlatformResolver{oracle, rhel, centos, fedora, scientific, eurolinux},
   810  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   811  		f, err := conn.FileSystem().Open("/etc/redhat-release")
   812  		if err != nil {
   813  			log.Debug().Err(err)
   814  			return false, nil
   815  		}
   816  		defer f.Close()
   817  
   818  		c, err := io.ReadAll(f)
   819  		if err != nil || len(c) == 0 {
   820  			log.Debug().Err(err)
   821  			return false, nil
   822  		}
   823  
   824  		content := strings.TrimSpace(string(c))
   825  		title, release, err := ParseRhelVersion(content)
   826  		if err == nil {
   827  			log.Debug().Str("title", title).Str("release", release).Msg("detected rhelish platform")
   828  
   829  			// only set title if not already properly detected by lsb or os-release
   830  			if len(pf.Title) == 0 {
   831  				pf.Title = title
   832  			}
   833  
   834  			// always override the version from the release file, since it is
   835  			// more accurate
   836  			if len(release) > 0 {
   837  				pf.Version = release
   838  			}
   839  
   840  			return true, nil
   841  		}
   842  
   843  		return false, nil
   844  	},
   845  }
   846  
   847  var debianFamily = &PlatformResolver{
   848  	Name:     "debian",
   849  	IsFamily: true,
   850  	Children: []*PlatformResolver{debian, ubuntu, raspbian, kali, linuxmint, popos},
   851  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   852  		return true, nil
   853  	},
   854  }
   855  
   856  var suseFamily = &PlatformResolver{
   857  	Name:     "suse",
   858  	IsFamily: true,
   859  	Children: []*PlatformResolver{opensuse, sles, suseMicroOs},
   860  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   861  		return true, nil
   862  	},
   863  }
   864  
   865  var archFamily = &PlatformResolver{
   866  	Name:     "arch",
   867  	IsFamily: true,
   868  	Children: []*PlatformResolver{arch, manjaro},
   869  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   870  		// if the file exists, we are on arch or one of its derivatives
   871  		f, err := conn.FileSystem().Open("/etc/arch-release")
   872  		if err != nil {
   873  			return false, nil
   874  		}
   875  		defer f.Close()
   876  
   877  		c, err := io.ReadAll(f)
   878  		if err != nil {
   879  			return false, nil
   880  		}
   881  
   882  		// on arch containers, /etc/os-release may not be present
   883  		if len(pf.Name) == 0 && strings.Contains(strings.ToLower(string(c)), "manjaro") {
   884  			pf.Name = "manjaro"
   885  			pf.Title = strings.TrimSpace(string(c))
   886  			return true, nil
   887  		}
   888  
   889  		if len(pf.Name) == 0 {
   890  			// fallback to arch
   891  			pf.Name = "arch"
   892  			pf.Title = "Arch Linux"
   893  		}
   894  		return true, nil
   895  	},
   896  }
   897  
   898  var linuxFamily = &PlatformResolver{
   899  	Name:     inventory.FAMILY_LINUX,
   900  	IsFamily: true,
   901  	Children: []*PlatformResolver{archFamily, redhatFamily, debianFamily, suseFamily, amazonlinux, alpine, gentoo, busybox, photon, windriver, openwrt, ubios, plcnext, defaultLinux},
   902  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
   903  		detected := false
   904  		osrd := NewOSReleaseDetector(conn)
   905  
   906  		pf.Name = ""
   907  		pf.Title = ""
   908  
   909  		lsb, err := osrd.lsbconfig()
   910  		// ignore lsb config if we got an error
   911  		if err == nil {
   912  			if len(lsb["DISTRIB_ID"]) > 0 {
   913  				pf.Name = strings.ToLower(lsb["DISTRIB_ID"])
   914  			}
   915  			if len(lsb["DISTRIB_DESCRIPTION"]) > 0 {
   916  				pf.Title = lsb["DISTRIB_DESCRIPTION"]
   917  			} else if len(lsb["DISTRIB_ID"]) > 0 {
   918  				pf.Title = lsb["DISTRIB_ID"]
   919  			}
   920  			if len(lsb["DISTRIB_RELEASE"]) > 0 {
   921  				pf.Version = lsb["DISTRIB_RELEASE"]
   922  			}
   923  
   924  			detected = true
   925  		} else {
   926  			log.Debug().Err(err).Msg("platform> cannot parse lsb config on this linux system")
   927  		}
   928  
   929  		osr, err := osrd.osrelease()
   930  		// ignore os release if we have an error
   931  		if err != nil {
   932  			log.Debug().Err(err).Msg("platform> cannot parse os-release on this linux system")
   933  		} else {
   934  			if len(osr["ID"]) > 0 {
   935  				pf.Name = osr["ID"]
   936  			}
   937  			if len(osr["PRETTY_NAME"]) > 0 {
   938  				pf.Title = osr["PRETTY_NAME"]
   939  			}
   940  			if len(osr["VERSION_ID"]) > 0 {
   941  				pf.Version = osr["VERSION_ID"]
   942  			}
   943  
   944  			if len(osr["BUILD_ID"]) > 0 {
   945  				pf.Build = osr["BUILD_ID"]
   946  			}
   947  
   948  			detected = true
   949  		}
   950  
   951  		// Centos 6 does not include /etc/os-release or /etc/lsb-release, therefore any static analysis
   952  		// will not be able to detect the system, since the following unamem and unames mechanism is not
   953  		// available there. Instead the system can be identified by the availability of /etc/redhat-release
   954  		// If /etc/redhat-release is available, we know its a linux system.
   955  		f, err := conn.FileSystem().Open("/etc/redhat-release")
   956  		if f != nil {
   957  			f.Close()
   958  		}
   959  
   960  		if err == nil {
   961  			detected = true
   962  		}
   963  
   964  		// BusyBox images do not contain /etc/os-release or /etc/lsb-release, therefore any static analysis
   965  		// will not be able to detect the system, since the following unamem and unames mechanism is not
   966  		// available there. Instead the system can be identified by the availability of /bin/busybox
   967  		// If /bin/busybox is available, we know its a linux system.
   968  		f, err = conn.FileSystem().Open("/bin/busybox")
   969  		if f != nil {
   970  			f.Close()
   971  		}
   972  
   973  		if err == nil {
   974  			detected = true
   975  		}
   976  
   977  		// try to read the architecture, we cannot assume this works if we use the tar backend where we
   978  		// just load the filesystem, therefore we do not fail here
   979  		unamem, err := osrd.unamem()
   980  		if err == nil {
   981  			pf.Arch = unamem
   982  		}
   983  
   984  		// abort if os-release or lsb config was available, we don't need uname -s then
   985  		if detected == true {
   986  			return true, nil
   987  		}
   988  
   989  		// if we reached here, we have a strange linux distro because it does not ship with
   990  		// lsb config and/or os release information, lets use the uname test to verify that this
   991  		// is a linux, it will fail for container images without the ability to run a process
   992  		unames, err := osrd.unames()
   993  		if err != nil {
   994  			return false, err
   995  		}
   996  
   997  		if strings.Contains(strings.ToLower(unames), "linux") == false {
   998  			return false, nil
   999  		}
  1000  
  1001  		return true, nil
  1002  	},
  1003  }
  1004  
  1005  var unixFamily = &PlatformResolver{
  1006  	Name:     inventory.FAMILY_UNIX,
  1007  	IsFamily: true,
  1008  	Children: []*PlatformResolver{bsdFamily, linuxFamily, solaris, aix},
  1009  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
  1010  		// in order to support linux container image detection, we cannot run
  1011  		// processes here, lets just read files to detect a system
  1012  		return true, nil
  1013  	},
  1014  }
  1015  
  1016  var solaris = &PlatformResolver{
  1017  	Name:     "solaris",
  1018  	IsFamily: false,
  1019  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
  1020  		osrd := NewOSReleaseDetector(conn)
  1021  
  1022  		unames, err := osrd.unames()
  1023  		if err != nil {
  1024  			return false, err
  1025  		}
  1026  
  1027  		if strings.Contains(strings.ToLower(unames), "sunos") == false {
  1028  			return false, nil
  1029  		}
  1030  
  1031  		// try to read the architecture
  1032  		unamem, err := osrd.unamem()
  1033  		if err == nil {
  1034  			pf.Arch = unamem
  1035  		}
  1036  
  1037  		pf.Name = "solaris"
  1038  
  1039  		// NOTE: we have only one solaris system here, since we only get here is the family is sunos, we pass
  1040  
  1041  		// try to read "/etc/release" for more details
  1042  		f, err := conn.FileSystem().Open("/etc/release")
  1043  		if err != nil {
  1044  			return false, nil
  1045  		}
  1046  		defer f.Close()
  1047  
  1048  		c, err := io.ReadAll(f)
  1049  		if err != nil {
  1050  			return false, nil
  1051  		}
  1052  
  1053  		release, err := ParseSolarisRelease(string(c))
  1054  		if err == nil {
  1055  			pf.Name = release.ID
  1056  			pf.Title = release.Title
  1057  			pf.Version = release.Release
  1058  		}
  1059  
  1060  		return true, nil
  1061  	},
  1062  }
  1063  
  1064  var aixUnameParser = regexp.MustCompile(`(\d+)\s+(\d+)\s+(.*)`)
  1065  
  1066  var aix = &PlatformResolver{
  1067  	Name:     "aix",
  1068  	IsFamily: false,
  1069  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
  1070  		osrd := NewOSReleaseDetector(conn)
  1071  
  1072  		unames, err := osrd.unames()
  1073  		if err != nil {
  1074  			return false, err
  1075  		}
  1076  
  1077  		if strings.Contains(strings.ToLower(unames), "aix") == false {
  1078  			return false, nil
  1079  		}
  1080  
  1081  		pf.Name = "aix"
  1082  		pf.Title = "AIX"
  1083  
  1084  		// try to read the architecture and version
  1085  		unamervp, err := osrd.command("uname -rvp")
  1086  		if err == nil {
  1087  			m := aixUnameParser.FindStringSubmatch(unamervp)
  1088  			if len(m) == 4 {
  1089  				pf.Version = m[2] + "." + m[1]
  1090  				pf.Version = pf.Version
  1091  				pf.Arch = m[3]
  1092  			}
  1093  		}
  1094  
  1095  		return true, nil
  1096  	},
  1097  }
  1098  
  1099  var esxi = &PlatformResolver{
  1100  	Name:     "esxi",
  1101  	IsFamily: false,
  1102  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
  1103  		log.Debug().Msg("check for esxi system")
  1104  		// at this point, we are already 99% its esxi
  1105  		cmd, err := conn.RunCommand("vmware -v")
  1106  		if err != nil {
  1107  			log.Debug().Err(err).Msg("could not run command")
  1108  			return false, nil
  1109  		}
  1110  		vmware_info, err := io.ReadAll(cmd.Stdout)
  1111  		if err != nil {
  1112  			log.Debug().Err(err).Msg("could not run command")
  1113  			return false, err
  1114  		}
  1115  
  1116  		version, err := ParseEsxiRelease(string(vmware_info))
  1117  		if err != nil {
  1118  			log.Debug().Err(err).Msg("could not run command")
  1119  			return false, err
  1120  		}
  1121  
  1122  		pf.Version = version
  1123  		return true, nil
  1124  	},
  1125  }
  1126  
  1127  var esxFamily = &PlatformResolver{
  1128  	Name:     "esx",
  1129  	IsFamily: true,
  1130  	Children: []*PlatformResolver{esxi},
  1131  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
  1132  		osrd := NewOSReleaseDetector(conn)
  1133  
  1134  		// check if we got vmkernel
  1135  		unames, err := osrd.unames()
  1136  		if err != nil {
  1137  			return false, err
  1138  		}
  1139  
  1140  		if strings.Contains(strings.ToLower(unames), "vmkernel") == false {
  1141  			return false, nil
  1142  		}
  1143  
  1144  		pf.Name = "esxi"
  1145  
  1146  		// try to read the architecture
  1147  		unamem, err := osrd.unamem()
  1148  		if err == nil {
  1149  			pf.Arch = unamem
  1150  		}
  1151  
  1152  		return true, nil
  1153  	},
  1154  }
  1155  
  1156  var WindowsFamily = &PlatformResolver{
  1157  	Name:     inventory.FAMILY_WINDOWS,
  1158  	IsFamily: true,
  1159  	Children: []*PlatformResolver{windows},
  1160  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
  1161  		return true, nil
  1162  	},
  1163  }
  1164  
  1165  var unknownOperatingSystem = &PlatformResolver{
  1166  	Name:     "unknown-os",
  1167  	IsFamily: false,
  1168  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
  1169  		// if we reach here, we really do not know the system
  1170  		log.Debug().Msg("platform> we do not know the operating system, please contact support")
  1171  		return true, nil
  1172  	},
  1173  }
  1174  
  1175  var OperatingSystems = &PlatformResolver{
  1176  	Name:     "os",
  1177  	IsFamily: true,
  1178  	Children: []*PlatformResolver{unixFamily, WindowsFamily, esxFamily, unknownOperatingSystem},
  1179  	Detect: func(r *PlatformResolver, pf *inventory.Platform, conn shared.Connection) (bool, error) {
  1180  		return true, nil
  1181  	},
  1182  }