1340 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1340 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package scanner
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"crypto/rand"
 | 
						|
	"encoding/binary"
 | 
						|
	"fmt"
 | 
						|
	"regexp"
 | 
						|
	"slices"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/future-architect/vuls/cache"
 | 
						|
	"github.com/future-architect/vuls/config"
 | 
						|
	"github.com/future-architect/vuls/constant"
 | 
						|
	"github.com/future-architect/vuls/logging"
 | 
						|
	"github.com/future-architect/vuls/models"
 | 
						|
	"github.com/future-architect/vuls/util"
 | 
						|
	version "github.com/knqyf263/go-deb-version"
 | 
						|
	"golang.org/x/xerrors"
 | 
						|
)
 | 
						|
 | 
						|
// inherit OsTypeInterface
 | 
						|
type debian struct {
 | 
						|
	base
 | 
						|
}
 | 
						|
 | 
						|
// NewDebian is constructor
 | 
						|
func newDebian(c config.ServerInfo) *debian {
 | 
						|
	d := &debian{
 | 
						|
		base: base{
 | 
						|
			osPackages: osPackages{
 | 
						|
				Packages:  models.Packages{},
 | 
						|
				VulnInfos: models.VulnInfos{},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
	d.log = logging.NewNormalLogger()
 | 
						|
	d.setServerInfo(c)
 | 
						|
	return d
 | 
						|
}
 | 
						|
 | 
						|
// Ubuntu, Debian, Raspbian
 | 
						|
// https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/debian.rb
 | 
						|
func detectDebian(c config.ServerInfo) (bool, osTypeInterface) {
 | 
						|
	if r := exec(c, "ls /etc/debian_version", noSudo); !r.isSuccess() {
 | 
						|
		logging.Log.Debugf("Not Debian like Linux. %s", r)
 | 
						|
		return false, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Raspbian
 | 
						|
	// lsb_release in Raspbian Jessie returns 'Distributor ID: Raspbian'.
 | 
						|
	// However, lsb_release in Raspbian Wheezy returns 'Distributor ID: Debian'.
 | 
						|
	if r := exec(c, "cat /etc/issue", noSudo); r.isSuccess() {
 | 
						|
		//  e.g.
 | 
						|
		//  Raspbian GNU/Linux 7 \n \l
 | 
						|
		result := strings.Fields(r.Stdout)
 | 
						|
		if len(result) > 2 && result[0] == constant.Raspbian {
 | 
						|
			deb := newDebian(c)
 | 
						|
			deb.setDistro(strings.ToLower(trim(result[0])), trim(result[2]))
 | 
						|
			return true, deb
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if r := exec(c, "lsb_release -ir", noSudo); r.isSuccess() {
 | 
						|
		//  e.g.
 | 
						|
		//  root@fa3ec524be43:/# lsb_release -ir
 | 
						|
		//  Distributor ID:	Ubuntu
 | 
						|
		//  Release:	14.04
 | 
						|
		re := regexp.MustCompile(`(?s)^Distributor ID:\s*(.+?)\n*Release:\s*(.+?)$`)
 | 
						|
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
						|
 | 
						|
		deb := newDebian(c)
 | 
						|
		if len(result) == 0 {
 | 
						|
			deb.setDistro("debian/ubuntu", "unknown")
 | 
						|
			logging.Log.Warnf("Unknown Debian/Ubuntu version. lsb_release -ir: %s", r)
 | 
						|
		} else {
 | 
						|
			distro := strings.ToLower(trim(result[1]))
 | 
						|
			deb.setDistro(distro, trim(result[2]))
 | 
						|
		}
 | 
						|
		return true, deb
 | 
						|
	}
 | 
						|
 | 
						|
	if r := exec(c, "cat /etc/lsb-release", noSudo); r.isSuccess() {
 | 
						|
		//  e.g.
 | 
						|
		//  DISTRIB_ID=Ubuntu
 | 
						|
		//  DISTRIB_RELEASE=14.04
 | 
						|
		//  DISTRIB_CODENAME=trusty
 | 
						|
		//  DISTRIB_DESCRIPTION="Ubuntu 14.04.2 LTS"
 | 
						|
		re := regexp.MustCompile(`(?s)^DISTRIB_ID=(.+?)\n*DISTRIB_RELEASE=(.+?)\n.*$`)
 | 
						|
		result := re.FindStringSubmatch(trim(r.Stdout))
 | 
						|
		deb := newDebian(c)
 | 
						|
		if len(result) == 0 {
 | 
						|
			logging.Log.Warnf(
 | 
						|
				"Unknown Debian/Ubuntu. cat /etc/lsb-release: %s", r)
 | 
						|
			deb.setDistro("debian/ubuntu", "unknown")
 | 
						|
		} else {
 | 
						|
			distro := strings.ToLower(trim(result[1]))
 | 
						|
			deb.setDistro(distro, trim(result[2]))
 | 
						|
		}
 | 
						|
		return true, deb
 | 
						|
	}
 | 
						|
 | 
						|
	// Debian
 | 
						|
	cmd := "cat /etc/debian_version"
 | 
						|
	if r := exec(c, cmd, noSudo); r.isSuccess() {
 | 
						|
		deb := newDebian(c)
 | 
						|
		deb.setDistro(constant.Debian, trim(r.Stdout))
 | 
						|
		return true, deb
 | 
						|
	}
 | 
						|
 | 
						|
	logging.Log.Debugf("Not Debian like Linux: %s", c.ServerName)
 | 
						|
	return false, nil
 | 
						|
}
 | 
						|
 | 
						|
func trim(str string) string {
 | 
						|
	return strings.TrimSpace(str)
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) checkScanMode() error {
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) checkIfSudoNoPasswd() error {
 | 
						|
	if o.getServerInfo().Mode.IsFast() {
 | 
						|
		o.log.Infof("sudo ... No need")
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	cmds := []string{
 | 
						|
		"checkrestart",
 | 
						|
		"stat /proc/1/exe",
 | 
						|
		"ls -l /proc/1/exe",
 | 
						|
		"cat /proc/1/maps",
 | 
						|
		"lsof -i -P -n",
 | 
						|
	}
 | 
						|
 | 
						|
	if !o.getServerInfo().Mode.IsOffline() {
 | 
						|
		cmds = append(cmds, "apt-get update")
 | 
						|
	}
 | 
						|
 | 
						|
	for _, cmd := range cmds {
 | 
						|
		cmd = util.PrependProxyEnv(cmd)
 | 
						|
		o.log.Infof("Checking... sudo %s", cmd)
 | 
						|
		r := o.exec(cmd, sudo)
 | 
						|
		if !r.isSuccess() {
 | 
						|
			o.log.Errorf("sudo error on %s", r)
 | 
						|
			return xerrors.Errorf("Failed to sudo: %s", r)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	initName, err := o.detectInitSystem()
 | 
						|
	if initName == upstart && err == nil {
 | 
						|
		cmd := util.PrependProxyEnv("initctl status --help")
 | 
						|
		o.log.Infof("Checking... sudo %s", cmd)
 | 
						|
		r := o.exec(cmd, sudo)
 | 
						|
		if !r.isSuccess() {
 | 
						|
			o.log.Errorf("sudo error on %s", r)
 | 
						|
			return xerrors.Errorf("Failed to sudo: %s", r)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	o.log.Infof("Sudo... Pass")
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
type dep struct {
 | 
						|
	packName      string
 | 
						|
	required      bool
 | 
						|
	logFunc       func(string, ...interface{})
 | 
						|
	additionalMsg string
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) checkDeps() error {
 | 
						|
	deps := []dep{}
 | 
						|
	if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() {
 | 
						|
		// checkrestart
 | 
						|
		deps = append(deps, dep{
 | 
						|
			packName: "debian-goodies",
 | 
						|
			required: true,
 | 
						|
			logFunc:  o.log.Errorf,
 | 
						|
		})
 | 
						|
	}
 | 
						|
 | 
						|
	if o.Distro.Family == constant.Debian {
 | 
						|
		// https://askubuntu.com/a/742844
 | 
						|
		if !o.ServerInfo.IsContainer() {
 | 
						|
			deps = append(deps, dep{
 | 
						|
				packName:      "reboot-notifier",
 | 
						|
				required:      false,
 | 
						|
				logFunc:       o.log.Warnf,
 | 
						|
				additionalMsg: ". Install it if you want to detect whether not rebooted after kernel update. To install `reboot-notifier` on Debian, see https://feeding.cloud.geek.nz/posts/introducing-reboot-notifier/",
 | 
						|
			})
 | 
						|
		}
 | 
						|
 | 
						|
		// Changelogs will be fetched only in deep scan mode
 | 
						|
		if o.getServerInfo().Mode.IsDeep() {
 | 
						|
			// Debian needs aptitude to get changelogs.
 | 
						|
			// Because unable to get changelogs via `apt-get changelog` on Debian.
 | 
						|
			deps = append(deps, dep{
 | 
						|
				packName: "aptitude",
 | 
						|
				required: true,
 | 
						|
				logFunc:  o.log.Errorf,
 | 
						|
			})
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for _, dep := range deps {
 | 
						|
		cmd := fmt.Sprintf("%s %s", dpkgQuery, dep.packName)
 | 
						|
		msg := fmt.Sprintf("%s is not installed", dep.packName)
 | 
						|
		r := o.exec(cmd, noSudo)
 | 
						|
		if !r.isSuccess() {
 | 
						|
			if dep.additionalMsg != "" {
 | 
						|
				msg += dep.additionalMsg
 | 
						|
			}
 | 
						|
			dep.logFunc(msg)
 | 
						|
			if dep.required {
 | 
						|
				return xerrors.New(msg)
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		_, status, _, _, _, _ := o.parseScannedPackagesLine(r.Stdout)
 | 
						|
		if status != "ii" {
 | 
						|
			if dep.additionalMsg != "" {
 | 
						|
				msg += dep.additionalMsg
 | 
						|
			}
 | 
						|
			dep.logFunc(msg)
 | 
						|
			if dep.required {
 | 
						|
				return xerrors.New(msg)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
	}
 | 
						|
	o.log.Infof("Dependencies... Pass")
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) preCure() error {
 | 
						|
	if err := o.detectIPAddr(); err != nil {
 | 
						|
		o.log.Warnf("Failed to detect IP addresses: %s", err)
 | 
						|
		o.warns = append(o.warns, err)
 | 
						|
	}
 | 
						|
	// Ignore this error as it just failed to detect the IP addresses
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) postScan() error {
 | 
						|
	if o.getServerInfo().Mode.IsDeep() || o.getServerInfo().Mode.IsFastRoot() {
 | 
						|
		if err := o.pkgPs(o.getOwnerPkgs); err != nil {
 | 
						|
			err = xerrors.Errorf("Failed to dpkg-ps: %w", err)
 | 
						|
			o.log.Warnf("err: %+v", err)
 | 
						|
			o.warns = append(o.warns, err)
 | 
						|
			// Only warning this error
 | 
						|
		}
 | 
						|
 | 
						|
		if err := o.checkrestart(); err != nil {
 | 
						|
			err = xerrors.Errorf("Failed to scan need-restarting processes: %w", err)
 | 
						|
			o.log.Warnf("err: %+v", err)
 | 
						|
			o.warns = append(o.warns, err)
 | 
						|
			// Only warning this error
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) detectIPAddr() (err error) {
 | 
						|
	o.ServerInfo.IPv4Addrs, o.ServerInfo.IPv6Addrs, err = o.ip()
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) scanPackages() error {
 | 
						|
	o.log.Infof("Scanning OS pkg in %s", o.getServerInfo().Mode)
 | 
						|
	// collect the running kernel information
 | 
						|
	release, version, err := o.runningKernel()
 | 
						|
	if err != nil {
 | 
						|
		o.log.Errorf("Failed to scan the running kernel version: %s", err)
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	rebootRequired, err := o.rebootRequired()
 | 
						|
	if err != nil {
 | 
						|
		o.log.Warnf("Failed to detect the kernel reboot required: %s", err)
 | 
						|
		o.warns = append(o.warns, err)
 | 
						|
		// Only warning this error
 | 
						|
	}
 | 
						|
	o.Kernel = models.Kernel{
 | 
						|
		Version:        version,
 | 
						|
		Release:        release,
 | 
						|
		RebootRequired: rebootRequired,
 | 
						|
	}
 | 
						|
 | 
						|
	installed, updatable, srcPacks, err := o.scanInstalledPackages()
 | 
						|
	if err != nil {
 | 
						|
		o.log.Errorf("Failed to scan installed packages: %s", err)
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	o.Packages = installed
 | 
						|
	o.SrcPackages = srcPacks
 | 
						|
 | 
						|
	if o.getServerInfo().Mode.IsOffline() {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if !o.getServerInfo().Mode.IsDeep() && o.Distro.Family == constant.Raspbian {
 | 
						|
		raspbianPacks := o.grepRaspbianPackages(updatable)
 | 
						|
		unsecures, err := o.scanUnsecurePackages(raspbianPacks)
 | 
						|
		if err != nil {
 | 
						|
			o.log.Errorf("Failed to scan vulnerable packages: %s", err)
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		o.VulnInfos = unsecures
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	if o.getServerInfo().Mode.IsDeep() {
 | 
						|
		unsecures, err := o.scanUnsecurePackages(updatable)
 | 
						|
		if err != nil {
 | 
						|
			o.log.Errorf("Failed to scan vulnerable packages: %s", err)
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		o.VulnInfos = unsecures
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// https://askubuntu.com/a/742844
 | 
						|
func (o *debian) rebootRequired() (bool, error) {
 | 
						|
	r := o.exec("test -f /var/run/reboot-required", noSudo)
 | 
						|
	switch r.ExitStatus {
 | 
						|
	case 0:
 | 
						|
		return true, nil
 | 
						|
	case 1:
 | 
						|
		return false, nil
 | 
						|
	default:
 | 
						|
		return false, xerrors.Errorf("Failed to check reboot required: %s", r)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
const dpkgQuery = `dpkg-query -W -f="\${binary:Package},\${db:Status-Abbrev},\${Version},\${source:Package},\${source:Version}\n"`
 | 
						|
 | 
						|
func (o *debian) scanInstalledPackages() (models.Packages, models.Packages, models.SrcPackages, error) {
 | 
						|
	updatable := models.Packages{}
 | 
						|
	r := o.exec(dpkgQuery, noSudo)
 | 
						|
	if !r.isSuccess() {
 | 
						|
		return nil, nil, nil, xerrors.Errorf("Failed to SSH: %s", r)
 | 
						|
	}
 | 
						|
 | 
						|
	installed, srcPacks, err := o.parseInstalledPackages(r.Stdout)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	if o.getServerInfo().Mode.IsOffline() || o.getServerInfo().Mode.IsFast() {
 | 
						|
		return installed, updatable, srcPacks, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if err := o.aptGetUpdate(); err != nil {
 | 
						|
		return nil, nil, nil, err
 | 
						|
	}
 | 
						|
	updatableNames, err := o.getUpdatablePackNames()
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, nil, err
 | 
						|
	}
 | 
						|
	for _, name := range updatableNames {
 | 
						|
		for _, pack := range installed {
 | 
						|
			if pack.Name == name {
 | 
						|
				updatable[name] = pack
 | 
						|
				break
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Fill the candidate versions of upgradable packages
 | 
						|
	err = o.fillCandidateVersion(updatable)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, nil, xerrors.Errorf("Failed to fill candidate versions. err: %w", err)
 | 
						|
	}
 | 
						|
	installed.MergeNewVersion(updatable)
 | 
						|
 | 
						|
	return installed, updatable, srcPacks, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) parseInstalledPackages(stdout string) (models.Packages, models.SrcPackages, error) {
 | 
						|
	installed, srcPacks := models.Packages{}, []models.SrcPackage{}
 | 
						|
	runningKernelSrcPacks := []models.SrcPackage{}
 | 
						|
 | 
						|
	// e.g.
 | 
						|
	// curl,ii ,7.38.0-4+deb8u2,,7.38.0-4+deb8u2
 | 
						|
	// openssh-server,ii ,1:6.7p1-5+deb8u3,openssh,1:6.7p1-5+deb8u3
 | 
						|
	// tar,ii ,1.27.1-2+b1,tar (1.27.1-2),1.27.1-2
 | 
						|
	lines := strings.Split(stdout, "\n")
 | 
						|
	for _, line := range lines {
 | 
						|
		if trimmed := strings.TrimSpace(line); len(trimmed) != 0 {
 | 
						|
			name, status, version, srcName, srcVersion, err := o.parseScannedPackagesLine(trimmed)
 | 
						|
			if err != nil || len(status) < 2 {
 | 
						|
				return nil, nil, xerrors.Errorf(
 | 
						|
					"Debian: Failed to parse package line: %s", line)
 | 
						|
			}
 | 
						|
 | 
						|
			packageStatus := status[1]
 | 
						|
			// Package status:
 | 
						|
			//     n = Not-installed
 | 
						|
			//     c = Config-files
 | 
						|
			//     H = Half-installed
 | 
						|
			//     U = Unpacked
 | 
						|
			//     F = Half-configured
 | 
						|
			//     W = Triggers-awaiting
 | 
						|
			//     t = Triggers-pending
 | 
						|
			//     i = Installed
 | 
						|
			if packageStatus != 'i' {
 | 
						|
				o.log.Debugf("%s package status is '%c', ignoring", name, packageStatus)
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			installed[name] = models.Package{
 | 
						|
				Name:    name,
 | 
						|
				Version: version,
 | 
						|
			}
 | 
						|
 | 
						|
			srcPacks = append(srcPacks, models.SrcPackage{
 | 
						|
				Name:        srcName,
 | 
						|
				Version:     srcVersion,
 | 
						|
				BinaryNames: []string{name},
 | 
						|
			})
 | 
						|
 | 
						|
			if models.IsKernelSourcePackage(o.getDistro().Family, srcName) {
 | 
						|
				switch o.getDistro().Family {
 | 
						|
				case constant.Debian, constant.Raspbian:
 | 
						|
					switch name {
 | 
						|
					case fmt.Sprintf("linux-image-%s", o.Kernel.Release), fmt.Sprintf("linux-headers-%s", o.Kernel.Release):
 | 
						|
						runningKernelSrcPacks = append(runningKernelSrcPacks, models.SrcPackage{
 | 
						|
							Name:    srcName,
 | 
						|
							Version: srcVersion,
 | 
						|
						})
 | 
						|
					default:
 | 
						|
					}
 | 
						|
				case constant.Ubuntu:
 | 
						|
					switch name {
 | 
						|
					case fmt.Sprintf("linux-image-%s", o.Kernel.Release), fmt.Sprintf("linux-image-unsigned-%s", o.Kernel.Release), fmt.Sprintf("linux-signed-image-%s", o.Kernel.Release), fmt.Sprintf("linux-image-uc-%s", o.Kernel.Release),
 | 
						|
						fmt.Sprintf("linux-buildinfo-%s", o.Kernel.Release), fmt.Sprintf("linux-cloud-tools-%s", o.Kernel.Release), fmt.Sprintf("linux-headers-%s", o.Kernel.Release), fmt.Sprintf("linux-lib-rust-%s", o.Kernel.Release), fmt.Sprintf("linux-modules-%s", o.Kernel.Release), fmt.Sprintf("linux-modules-extra-%s", o.Kernel.Release), fmt.Sprintf("linux-modules-ipu6-%s", o.Kernel.Release), fmt.Sprintf("linux-modules-ivsc-%s", o.Kernel.Release), fmt.Sprintf("linux-modules-iwlwifi-%s", o.Kernel.Release), fmt.Sprintf("linux-tools-%s", o.Kernel.Release):
 | 
						|
						runningKernelSrcPacks = append(runningKernelSrcPacks, models.SrcPackage{
 | 
						|
							Name:    srcName,
 | 
						|
							Version: srcVersion,
 | 
						|
						})
 | 
						|
					default:
 | 
						|
						if (strings.HasPrefix(name, "linux-modules-nvidia-") || strings.HasPrefix(name, "linux-objects-nvidia-") || strings.HasPrefix(name, "linux-signatures-nvidia-")) && strings.HasSuffix(name, o.Kernel.Release) {
 | 
						|
							runningKernelSrcPacks = append(runningKernelSrcPacks, models.SrcPackage{
 | 
						|
								Name:    srcName,
 | 
						|
								Version: srcVersion,
 | 
						|
							})
 | 
						|
						}
 | 
						|
					}
 | 
						|
				default:
 | 
						|
					return nil, nil, xerrors.Errorf("unknown distro: %s", o.getDistro().Family)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	srcs := models.SrcPackages{}
 | 
						|
	for _, p := range srcPacks {
 | 
						|
		if models.IsKernelSourcePackage(o.getDistro().Family, p.Name) && !slices.ContainsFunc(runningKernelSrcPacks, func(e models.SrcPackage) bool {
 | 
						|
			return p.Name == e.Name && p.Version == e.Version
 | 
						|
		}) {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if pack, ok := srcs[p.Name]; ok {
 | 
						|
			for _, bn := range pack.BinaryNames {
 | 
						|
				p.AddBinaryName(bn)
 | 
						|
			}
 | 
						|
		}
 | 
						|
		srcs[p.Name] = p
 | 
						|
	}
 | 
						|
 | 
						|
	bins := models.Packages{}
 | 
						|
	for _, sp := range srcs {
 | 
						|
		for _, bn := range sp.BinaryNames {
 | 
						|
			bins[bn] = installed[bn]
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return bins, srcs, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) parseScannedPackagesLine(line string) (name, status, version, srcName, srcVersion string, err error) {
 | 
						|
	ss := strings.Split(line, ",")
 | 
						|
	if len(ss) == 5 {
 | 
						|
		// remove :amd64, i386...
 | 
						|
		name = ss[0]
 | 
						|
		if i := strings.IndexRune(name, ':'); i >= 0 {
 | 
						|
			name = name[:i]
 | 
						|
		}
 | 
						|
		status = strings.TrimSpace(ss[1])
 | 
						|
		version = ss[2]
 | 
						|
		// remove version. ex: tar (1.27.1-2)
 | 
						|
 | 
						|
		// Source name and version are computed from binary package name and version in dpkg.
 | 
						|
		// Source package name:
 | 
						|
		// https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/pkg-format.c#n338
 | 
						|
		srcName = strings.Split(ss[3], " ")[0]
 | 
						|
		if srcName == "" {
 | 
						|
			srcName = name
 | 
						|
		}
 | 
						|
		// Source package version:
 | 
						|
		// https://git.dpkg.org/cgit/dpkg/dpkg.git/tree/lib/dpkg/pkg-show.c#n428
 | 
						|
		srcVersion = ss[4]
 | 
						|
		if srcVersion == "" {
 | 
						|
			srcVersion = version
 | 
						|
		}
 | 
						|
		return
 | 
						|
	}
 | 
						|
 | 
						|
	return "", "", "", "", "", xerrors.Errorf("Unknown format: %s", line)
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) aptGetUpdate() error {
 | 
						|
	o.log.Infof("apt-get update...")
 | 
						|
	cmd := util.PrependProxyEnv("apt-get update")
 | 
						|
	if r := o.exec(cmd, sudo); !r.isSuccess() {
 | 
						|
		return xerrors.Errorf("Failed to apt-get update: %s", r)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) grepRaspbianPackages(updatables models.Packages) models.Packages {
 | 
						|
	raspbianPacks := models.Packages{}
 | 
						|
 | 
						|
	for _, pack := range updatables {
 | 
						|
		if models.IsRaspbianPackage(pack.Name, pack.Version) {
 | 
						|
			raspbianPacks[pack.Name] = pack
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return raspbianPacks
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) scanUnsecurePackages(updatable models.Packages) (models.VulnInfos, error) {
 | 
						|
	// Setup changelog cache
 | 
						|
	current := cache.Meta{
 | 
						|
		Name:   o.getServerInfo().GetServerName(),
 | 
						|
		Distro: o.getServerInfo().Distro,
 | 
						|
		Packs:  updatable,
 | 
						|
	}
 | 
						|
 | 
						|
	o.log.Debugf("Ensure changelog cache: %s", current.Name)
 | 
						|
	meta, err := o.ensureChangelogCache(current)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Make a directory for saving changelog to get changelog in Raspbian
 | 
						|
	tmpClogPath := ""
 | 
						|
	if o.Distro.Family == constant.Raspbian {
 | 
						|
		tmpClogPath, err = o.makeTempChangelogDir()
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Collect CVE information of upgradable packages
 | 
						|
	vulnInfos, err := o.scanChangelogs(updatable, meta, tmpClogPath)
 | 
						|
	if err != nil {
 | 
						|
		return nil, xerrors.Errorf("Failed to scan unsecure packages. err: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Delete a directory for saving changelog to get changelog in Raspbian
 | 
						|
	if o.Distro.Family == constant.Raspbian {
 | 
						|
		err := o.deleteTempChangelogDir(tmpClogPath)
 | 
						|
		if err != nil {
 | 
						|
			return nil, xerrors.Errorf("Failed to delete directory to save changelog for Raspbian. err: %w", err)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return vulnInfos, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) ensureChangelogCache(current cache.Meta) (*cache.Meta, error) {
 | 
						|
	// Search from cache
 | 
						|
	cached, found, err := cache.DB.GetMeta(current.Name)
 | 
						|
	if err != nil {
 | 
						|
		return nil, xerrors.Errorf(
 | 
						|
			"Failed to get meta. Please remove cache.db and then try again. err: %w", err)
 | 
						|
	}
 | 
						|
 | 
						|
	if !found {
 | 
						|
		o.log.Debugf("Not found in meta: %s", current.Name)
 | 
						|
		err = cache.DB.EnsureBuckets(current)
 | 
						|
		if err != nil {
 | 
						|
			return nil, xerrors.Errorf("Failed to ensure buckets. err: %w", err)
 | 
						|
		}
 | 
						|
		return ¤t, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if current.Distro.Family != cached.Distro.Family ||
 | 
						|
		current.Distro.Release != cached.Distro.Release {
 | 
						|
		o.log.Debugf("Need to refresh meta: %s", current.Name)
 | 
						|
		err = cache.DB.EnsureBuckets(current)
 | 
						|
		if err != nil {
 | 
						|
			return nil, xerrors.Errorf("Failed to ensure buckets. err: %w", err)
 | 
						|
		}
 | 
						|
		return ¤t, nil
 | 
						|
 | 
						|
	}
 | 
						|
 | 
						|
	o.log.Debugf("Reuse meta: %s", current.Name)
 | 
						|
	if config.Conf.Debug {
 | 
						|
		cache.DB.PrettyPrint(current)
 | 
						|
	}
 | 
						|
	return &cached, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) fillCandidateVersion(updatables models.Packages) (err error) {
 | 
						|
	names := []string{}
 | 
						|
	for name := range updatables {
 | 
						|
		names = append(names, name)
 | 
						|
	}
 | 
						|
	cmd := fmt.Sprintf("LANGUAGE=en_US.UTF-8 apt-cache policy %s", strings.Join(names, " "))
 | 
						|
	r := o.exec(cmd, noSudo)
 | 
						|
	if !r.isSuccess() {
 | 
						|
		return xerrors.Errorf("Failed to SSH: %s", r)
 | 
						|
	}
 | 
						|
	packAptPolicy := o.splitAptCachePolicy(r.Stdout)
 | 
						|
	for k, v := range packAptPolicy {
 | 
						|
		ver, err := o.parseAptCachePolicy(v, k)
 | 
						|
		if err != nil {
 | 
						|
			return xerrors.Errorf("Failed to parse %w", err)
 | 
						|
		}
 | 
						|
		pack, ok := updatables[k]
 | 
						|
		if !ok {
 | 
						|
			return xerrors.Errorf("Not found: %s", k)
 | 
						|
		}
 | 
						|
		pack.NewVersion = ver.Candidate
 | 
						|
		pack.Repository = ver.Repo
 | 
						|
		updatables[k] = pack
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) getUpdatablePackNames() (packNames []string, err error) {
 | 
						|
	cmd := util.PrependProxyEnv("LANGUAGE=en_US.UTF-8 apt-get dist-upgrade --dry-run")
 | 
						|
	r := o.exec(cmd, noSudo)
 | 
						|
	if r.isSuccess(0, 1) {
 | 
						|
		return o.parseAptGetUpgrade(r.Stdout)
 | 
						|
	}
 | 
						|
	return packNames, xerrors.Errorf(
 | 
						|
		"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
						|
		cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) parseAptGetUpgrade(stdout string) (updatableNames []string, err error) {
 | 
						|
	startRe := regexp.MustCompile(`The following packages will be upgraded:`)
 | 
						|
	stopRe := regexp.MustCompile(`^(\d+) upgraded.*`)
 | 
						|
	startLineFound, stopLineFound := false, false
 | 
						|
 | 
						|
	lines := strings.Split(stdout, "\n")
 | 
						|
	for _, line := range lines {
 | 
						|
		if !startLineFound {
 | 
						|
			if matche := startRe.MatchString(line); matche {
 | 
						|
				startLineFound = true
 | 
						|
			}
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		result := stopRe.FindStringSubmatch(line)
 | 
						|
		if len(result) == 2 {
 | 
						|
			nUpdatable, err := strconv.Atoi(result[1])
 | 
						|
			if err != nil {
 | 
						|
				return nil, xerrors.Errorf(
 | 
						|
					"Failed to scan upgradable packages number. line: %s", line)
 | 
						|
			}
 | 
						|
			if nUpdatable != len(updatableNames) {
 | 
						|
				return nil, xerrors.Errorf(
 | 
						|
					"Failed to scan upgradable packages, expected: %s, detected: %d",
 | 
						|
					result[1], len(updatableNames))
 | 
						|
			}
 | 
						|
			stopLineFound = true
 | 
						|
			break
 | 
						|
		}
 | 
						|
		updatableNames = append(updatableNames, strings.Fields(line)...)
 | 
						|
	}
 | 
						|
	if !startLineFound {
 | 
						|
		// no upgrades
 | 
						|
		return
 | 
						|
	}
 | 
						|
	if !stopLineFound {
 | 
						|
		// There are upgrades, but not found the stop line.
 | 
						|
		return nil, xerrors.New("Failed to scan upgradable packages")
 | 
						|
	}
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) makeTempChangelogDir() (string, error) {
 | 
						|
	suffix, err := generateSuffix()
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	path := "/tmp/vuls-" + suffix
 | 
						|
	cmd := fmt.Sprintf(`mkdir -p %s`, path)
 | 
						|
	cmd = util.PrependProxyEnv(cmd)
 | 
						|
	r := o.exec(cmd, noSudo)
 | 
						|
	if !r.isSuccess() {
 | 
						|
		return "", xerrors.Errorf("Failed to create directory to save changelog for Raspbian. cmd: %s, status: %d, stdout: %s, stderr: %s", cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
						|
	}
 | 
						|
	return path, nil
 | 
						|
}
 | 
						|
 | 
						|
func generateSuffix() (string, error) {
 | 
						|
	var n uint64
 | 
						|
	if err := binary.Read(rand.Reader, binary.LittleEndian, &n); err != nil {
 | 
						|
		return "", xerrors.Errorf("Failed to generate Suffix. err: %w", err)
 | 
						|
	}
 | 
						|
	return strconv.FormatUint(n, 36), nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) deleteTempChangelogDir(tmpClogPath string) error {
 | 
						|
	cmd := fmt.Sprintf(`rm -rf %s`, tmpClogPath)
 | 
						|
	cmd = util.PrependProxyEnv(cmd)
 | 
						|
	r := o.exec(cmd, noSudo)
 | 
						|
	if !r.isSuccess() {
 | 
						|
		return xerrors.Errorf("Failed to delete directory to save changelog for Raspbian. cmd: %s, status: %d, stdout: %s, stderr: %s", cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// DetectedCveID has CveID, Confidence and DetectionMethod fields
 | 
						|
// LenientMatching will be true if this vulnerability is not detected by accurate version matching.
 | 
						|
// see https://github.com/future-architect/vuls/pull/328
 | 
						|
type DetectedCveID struct {
 | 
						|
	CveID      string
 | 
						|
	Confidence models.Confidence
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) scanChangelogs(updatablePacks models.Packages, meta *cache.Meta, tmpClogPath string) (models.VulnInfos, error) {
 | 
						|
	type response struct {
 | 
						|
		pack           *models.Package
 | 
						|
		DetectedCveIDs []DetectedCveID
 | 
						|
	}
 | 
						|
	resChan := make(chan response, len(updatablePacks))
 | 
						|
	errChan := make(chan error, len(updatablePacks))
 | 
						|
	reqChan := make(chan models.Package, len(updatablePacks))
 | 
						|
	defer close(resChan)
 | 
						|
	defer close(errChan)
 | 
						|
	defer close(reqChan)
 | 
						|
 | 
						|
	go func() {
 | 
						|
		for _, pack := range updatablePacks {
 | 
						|
			reqChan <- pack
 | 
						|
		}
 | 
						|
	}()
 | 
						|
 | 
						|
	timeout := time.After(30 * 60 * time.Second)
 | 
						|
	concurrency := 10
 | 
						|
	tasks := util.GenWorkers(concurrency)
 | 
						|
	for range updatablePacks {
 | 
						|
		tasks <- func() {
 | 
						|
			select {
 | 
						|
			case pack := <-reqChan:
 | 
						|
				func(p models.Package) {
 | 
						|
					changelog := o.getChangelogCache(meta, p)
 | 
						|
					if 0 < len(changelog) {
 | 
						|
						cveIDs, pack := o.getCveIDsFromChangelog(changelog, p.Name, p.Version)
 | 
						|
						resChan <- response{pack, cveIDs}
 | 
						|
						return
 | 
						|
					}
 | 
						|
 | 
						|
					// if the changelog is not in cache or failed to get from local cache,
 | 
						|
					// get the changelog of the package via internet.
 | 
						|
					// After that, store it in the cache.
 | 
						|
					if cveIDs, pack, err := o.fetchParseChangelog(p, tmpClogPath); err != nil {
 | 
						|
						errChan <- err
 | 
						|
					} else {
 | 
						|
						resChan <- response{pack, cveIDs}
 | 
						|
					}
 | 
						|
				}(pack)
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// { DetectedCveID{} : [package] }
 | 
						|
	cvePackages := make(map[DetectedCveID][]string)
 | 
						|
	errs := []error{}
 | 
						|
	for i := 0; i < len(updatablePacks); i++ {
 | 
						|
		select {
 | 
						|
		case response := <-resChan:
 | 
						|
			if response.pack == nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			o.Packages[response.pack.Name] = *response.pack
 | 
						|
			cves := response.DetectedCveIDs
 | 
						|
			for _, cve := range cves {
 | 
						|
				packNames, ok := cvePackages[cve]
 | 
						|
				if ok {
 | 
						|
					packNames = append(packNames, response.pack.Name)
 | 
						|
				} else {
 | 
						|
					packNames = []string{response.pack.Name}
 | 
						|
				}
 | 
						|
				cvePackages[cve] = packNames
 | 
						|
			}
 | 
						|
			o.log.Infof("(%d/%d) Scanned %s: %s",
 | 
						|
				i+1, len(updatablePacks), response.pack.Name, cves)
 | 
						|
		case err := <-errChan:
 | 
						|
			errs = append(errs, err)
 | 
						|
		case <-timeout:
 | 
						|
			errs = append(errs, xerrors.New("Timeout scanPackageCveIDs"))
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if 0 < len(errs) {
 | 
						|
		return nil, xerrors.Errorf("errs: %w", errs)
 | 
						|
	}
 | 
						|
 | 
						|
	cveIDs := []DetectedCveID{}
 | 
						|
	for k := range cvePackages {
 | 
						|
		cveIDs = append(cveIDs, k)
 | 
						|
	}
 | 
						|
	o.log.Debugf("%d Cves are found. cves: %v", len(cveIDs), cveIDs)
 | 
						|
	vinfos := models.VulnInfos{}
 | 
						|
	for cveID, names := range cvePackages {
 | 
						|
		affected := models.PackageFixStatuses{}
 | 
						|
		for _, n := range names {
 | 
						|
			affected = append(affected, models.PackageFixStatus{Name: n})
 | 
						|
		}
 | 
						|
 | 
						|
		vinfos[cveID.CveID] = models.VulnInfo{
 | 
						|
			CveID:            cveID.CveID,
 | 
						|
			Confidences:      models.Confidences{cveID.Confidence},
 | 
						|
			AffectedPackages: affected,
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Update meta package information of changelog cache to the latest one.
 | 
						|
	meta.Packs = updatablePacks
 | 
						|
	if err := cache.DB.RefreshMeta(*meta); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return vinfos, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) getChangelogCache(meta *cache.Meta, pack models.Package) string {
 | 
						|
	cachedPack, found := meta.Packs[pack.Name]
 | 
						|
	if !found {
 | 
						|
		o.log.Debugf("Not found in cache: %s", pack.Name)
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
 | 
						|
	if cachedPack.NewVersion != pack.NewVersion {
 | 
						|
		o.log.Debugf("Expired: %s, cache: %s, new: %s",
 | 
						|
			pack.Name, cachedPack.NewVersion, pack.NewVersion)
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	changelog, err := cache.DB.GetChangelog(meta.Name, pack.Name)
 | 
						|
	if err != nil {
 | 
						|
		o.log.Warnf("Failed to get changelog. bucket: %s, key:%s, err: %+v",
 | 
						|
			meta.Name, pack.Name, err)
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	if len(changelog) == 0 {
 | 
						|
		o.log.Debugf("Empty string: %s", pack.Name)
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
 | 
						|
	o.log.Debugf("Hit: %s, %s, cache: %s, new: %s len: %d, %s...",
 | 
						|
		meta.Name, pack.Name, cachedPack.NewVersion, pack.NewVersion, len(changelog), util.Truncate(changelog, 30))
 | 
						|
	return changelog
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) fetchParseChangelog(pack models.Package, tmpClogPath string) ([]DetectedCveID, *models.Package, error) {
 | 
						|
	cmd := ""
 | 
						|
 | 
						|
	switch o.Distro.Family {
 | 
						|
	case constant.Ubuntu:
 | 
						|
		cmd = fmt.Sprintf(`PAGER=cat apt-get -q=2 changelog %s`, pack.Name)
 | 
						|
	case constant.Debian:
 | 
						|
		cmd = fmt.Sprintf(`PAGER=cat aptitude -q=2 changelog %s`, pack.Name)
 | 
						|
	case constant.Raspbian:
 | 
						|
		changelogPath, err := o.getChangelogPath(pack.Name, tmpClogPath)
 | 
						|
		if err != nil {
 | 
						|
			// Ignore this Error.
 | 
						|
			o.log.Warnf("Failed to get Path to Changelog for Package: %s, err: %+v", pack.Name, err)
 | 
						|
			return nil, nil, nil
 | 
						|
		}
 | 
						|
		cmd = fmt.Sprintf(`gzip -cd %s | cat`, changelogPath)
 | 
						|
	}
 | 
						|
	cmd = util.PrependProxyEnv(cmd)
 | 
						|
 | 
						|
	r := o.exec(cmd, noSudo)
 | 
						|
	if !r.isSuccess() {
 | 
						|
		o.log.Warnf("Failed to SSH: %s", r)
 | 
						|
		// Ignore this Error.
 | 
						|
		return nil, nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	stdout := strings.Replace(r.Stdout, "\r", "", -1)
 | 
						|
	cveIDs, clogFilledPack := o.getCveIDsFromChangelog(stdout, pack.Name, pack.Version)
 | 
						|
 | 
						|
	if clogFilledPack.Changelog.Method != models.FailedToGetChangelog {
 | 
						|
		err := cache.DB.PutChangelog(
 | 
						|
			o.getServerInfo().GetServerName(), pack.Name, stdout)
 | 
						|
		if err != nil {
 | 
						|
			return nil, nil, xerrors.New("Failed to put changelog into cache")
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// No error will be returned. Only logging.
 | 
						|
	return cveIDs, clogFilledPack, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) getChangelogPath(packName, tmpClogPath string) (string, error) {
 | 
						|
	// `apt download` downloads deb package to current directory
 | 
						|
	cmd := fmt.Sprintf(`cd %s && apt download %s`, tmpClogPath, packName)
 | 
						|
	cmd = util.PrependProxyEnv(cmd)
 | 
						|
	r := o.exec(cmd, noSudo)
 | 
						|
	if !r.isSuccess() {
 | 
						|
		return "", xerrors.Errorf("Failed to Fetch deb package. cmd: %s, status: %d, stdout: %s, stderr: %s", cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
						|
	}
 | 
						|
 | 
						|
	cmd = fmt.Sprintf(`find %s -name "%s_*.deb"`, tmpClogPath, packName)
 | 
						|
	cmd = util.PrependProxyEnv(cmd)
 | 
						|
	r = o.exec(cmd, noSudo)
 | 
						|
	if !r.isSuccess() || r.Stdout == "" {
 | 
						|
		return "", xerrors.Errorf("Failed to find deb package. cmd: %s, status: %d, stdout: %s, stderr: %s", cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
						|
	}
 | 
						|
 | 
						|
	// e.g. <tmpPath>/ffmpeg_7%3a4.1.6-1~deb10u1+rpt1_armhf.deb\n => <tmpPath>/ffmpeg_7%3a4.1.6-1~deb10u1+rpt1_armhf
 | 
						|
	packChangelogDir := strings.Split(r.Stdout, ".deb")[0]
 | 
						|
	cmd = fmt.Sprintf(`dpkg-deb -x %s.deb %s`, packChangelogDir, packChangelogDir)
 | 
						|
	cmd = util.PrependProxyEnv(cmd)
 | 
						|
	r = o.exec(cmd, noSudo)
 | 
						|
	if !r.isSuccess() {
 | 
						|
		return "", xerrors.Errorf("Failed to dpkg-deb. cmd: %s, status: %d, stdout: %s, stderr: %s", cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
						|
	}
 | 
						|
 | 
						|
	// recurse if doc/packName is symbolic link
 | 
						|
	changelogDocDir := fmt.Sprintf("%s/usr/share/doc/%s", packChangelogDir, packName)
 | 
						|
	cmd = fmt.Sprintf(`test -L %s && readlink --no-newline %s`, changelogDocDir, changelogDocDir)
 | 
						|
	cmd = util.PrependProxyEnv(cmd)
 | 
						|
	r = o.exec(cmd, noSudo)
 | 
						|
	if r.isSuccess() {
 | 
						|
		return o.getChangelogPath(r.Stdout, tmpClogPath)
 | 
						|
	}
 | 
						|
 | 
						|
	var results = make(map[string]execResult, 2)
 | 
						|
	packChangelogPath := fmt.Sprintf("%s/changelog.Debian.gz", changelogDocDir)
 | 
						|
	cmd = fmt.Sprintf(`test -e %s`, packChangelogPath)
 | 
						|
	cmd = util.PrependProxyEnv(cmd)
 | 
						|
	r = o.exec(cmd, noSudo)
 | 
						|
	if r.isSuccess() {
 | 
						|
		return packChangelogPath, nil
 | 
						|
	}
 | 
						|
	results["changelog.Debian.gz"] = r
 | 
						|
 | 
						|
	packChangelogPath = fmt.Sprintf("%s/changelog.gz", changelogDocDir)
 | 
						|
	cmd = fmt.Sprintf(`test -e %s`, packChangelogPath)
 | 
						|
	cmd = util.PrependProxyEnv(cmd)
 | 
						|
	r = o.exec(cmd, noSudo)
 | 
						|
	if r.isSuccess() {
 | 
						|
		return packChangelogPath, nil
 | 
						|
	}
 | 
						|
	results["changelog.gz"] = r
 | 
						|
 | 
						|
	return "", xerrors.Errorf(
 | 
						|
		"Failed to get changelog.\nresult(changelog.Debian.gz):%v\nresult(changelog.Debian.gz):%v",
 | 
						|
		results["changelog.Debian.gz"], results["changelog.gz"])
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) getCveIDsFromChangelog(
 | 
						|
	changelog, name, ver string) ([]DetectedCveID, *models.Package) {
 | 
						|
 | 
						|
	if cveIDs, pack, err := o.parseChangelog(
 | 
						|
		changelog, name, ver, models.ChangelogExactMatch); err == nil {
 | 
						|
		return cveIDs, pack
 | 
						|
	}
 | 
						|
 | 
						|
	var verAfterColon string
 | 
						|
 | 
						|
	splittedByColon := strings.Split(ver, ":")
 | 
						|
	if 1 < len(splittedByColon) {
 | 
						|
		verAfterColon = splittedByColon[1]
 | 
						|
		if cveIDs, pack, err := o.parseChangelog(
 | 
						|
			changelog, name, verAfterColon, models.ChangelogRoughMatch); err == nil {
 | 
						|
			return cveIDs, pack
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	delim := []string{"+", "~", "build"}
 | 
						|
	switch o.Distro.Family {
 | 
						|
	case constant.Ubuntu:
 | 
						|
		delim = append(delim, constant.Ubuntu)
 | 
						|
	case constant.Debian:
 | 
						|
	case constant.Raspbian:
 | 
						|
	}
 | 
						|
 | 
						|
	for _, d := range delim {
 | 
						|
		ss := strings.Split(ver, d)
 | 
						|
		if 1 < len(ss) {
 | 
						|
			if cveIDs, pack, err := o.parseChangelog(
 | 
						|
				changelog, name, ss[0], models.ChangelogRoughMatch); err == nil {
 | 
						|
				return cveIDs, pack
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		ss = strings.Split(verAfterColon, d)
 | 
						|
		if 1 < len(ss) {
 | 
						|
			if cveIDs, pack, err := o.parseChangelog(
 | 
						|
				changelog, name, ss[0], models.ChangelogRoughMatch); err == nil {
 | 
						|
				return cveIDs, pack
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Only logging the error.
 | 
						|
	o.log.Warnf("Failed to find the version in changelog: %s-%s", name, ver)
 | 
						|
	o.log.Debugf("Changelog of %s-%s: %s", name, ver, changelog)
 | 
						|
 | 
						|
	// If the version is not in changelog, return entire changelog to put into cache
 | 
						|
	pack := o.Packages[name]
 | 
						|
	pack.Changelog = &models.Changelog{
 | 
						|
		Contents: changelog,
 | 
						|
		Method:   models.FailedToFindVersionInChangelog,
 | 
						|
	}
 | 
						|
 | 
						|
	return []DetectedCveID{}, &pack
 | 
						|
}
 | 
						|
 | 
						|
var cveRe = regexp.MustCompile(`(CVE-\d{4}-\d{4,})`)
 | 
						|
 | 
						|
// Collect CVE-IDs included in the changelog.
 | 
						|
// The version specified in argument(versionOrLater) is used to compare.
 | 
						|
func (o *debian) parseChangelog(changelog, name, ver string, confidence models.Confidence) ([]DetectedCveID, *models.Package, error) {
 | 
						|
	installedVer, err := version.NewVersion(ver)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, xerrors.Errorf("Failed to parse installed version: %s, err: %w", ver, err)
 | 
						|
	}
 | 
						|
	buf, cveIDs := []string{}, []string{}
 | 
						|
	scanner := bufio.NewScanner(strings.NewReader(changelog))
 | 
						|
	found := false
 | 
						|
	for scanner.Scan() {
 | 
						|
		line := scanner.Text()
 | 
						|
		buf = append(buf, line)
 | 
						|
		if matches := cveRe.FindAllString(line, -1); 0 < len(matches) {
 | 
						|
			for _, m := range matches {
 | 
						|
				cveIDs = util.AppendIfMissing(cveIDs, m)
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		ss := strings.Fields(line)
 | 
						|
		if len(ss) < 2 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if !strings.HasPrefix(ss[1], "(") || !strings.HasSuffix(ss[1], ")") {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		clogVer, err := version.NewVersion(ss[1][1 : len(ss[1])-1])
 | 
						|
		if err != nil {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if installedVer.Equal(clogVer) || installedVer.GreaterThan(clogVer) {
 | 
						|
			found = true
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !found {
 | 
						|
		if o.Distro.Family == constant.Raspbian {
 | 
						|
			pack := o.Packages[name]
 | 
						|
			pack.Changelog = &models.Changelog{
 | 
						|
				Contents: strings.Join(buf, "\n"),
 | 
						|
				Method:   models.ChangelogRoughMatchStr,
 | 
						|
			}
 | 
						|
 | 
						|
			cves := []DetectedCveID{}
 | 
						|
			for _, id := range cveIDs {
 | 
						|
				cves = append(cves, DetectedCveID{id, confidence})
 | 
						|
			}
 | 
						|
 | 
						|
			return cves, &pack, nil
 | 
						|
		}
 | 
						|
 | 
						|
		pack := o.Packages[name]
 | 
						|
		pack.Changelog = &models.Changelog{
 | 
						|
			Contents: "",
 | 
						|
			Method:   models.FailedToFindVersionInChangelog,
 | 
						|
		}
 | 
						|
		return nil, &pack, xerrors.Errorf(
 | 
						|
			"Failed to scan CVE IDs. The version is not in changelog. name: %s, version: %s",
 | 
						|
			name, ver)
 | 
						|
	}
 | 
						|
 | 
						|
	clog := models.Changelog{
 | 
						|
		Contents: strings.Join(buf[0:len(buf)-1], "\n"),
 | 
						|
		Method:   confidence.DetectionMethod,
 | 
						|
	}
 | 
						|
	pack := o.Packages[name]
 | 
						|
	pack.Changelog = &clog
 | 
						|
 | 
						|
	cves := []DetectedCveID{}
 | 
						|
	for _, id := range cveIDs {
 | 
						|
		cves = append(cves, DetectedCveID{id, confidence})
 | 
						|
	}
 | 
						|
 | 
						|
	return cves, &pack, nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) splitAptCachePolicy(stdout string) map[string]string {
 | 
						|
	re := regexp.MustCompile(`(?m:^[^ \t]+:\r?\n)`)
 | 
						|
	ii := re.FindAllStringIndex(stdout, -1)
 | 
						|
	ri := []int{}
 | 
						|
	for i := len(ii) - 1; 0 <= i; i-- {
 | 
						|
		ri = append(ri, ii[i][0])
 | 
						|
	}
 | 
						|
	splitted := []string{}
 | 
						|
	lasti := len(stdout)
 | 
						|
	for _, i := range ri {
 | 
						|
		splitted = append(splitted, stdout[i:lasti])
 | 
						|
		lasti = i
 | 
						|
	}
 | 
						|
 | 
						|
	packAptPolicy := map[string]string{}
 | 
						|
	for _, r := range splitted {
 | 
						|
		packName := r[:strings.Index(r, ":")]
 | 
						|
		packAptPolicy[packName] = r
 | 
						|
	}
 | 
						|
	return packAptPolicy
 | 
						|
}
 | 
						|
 | 
						|
type packCandidateVer struct {
 | 
						|
	Name      string
 | 
						|
	Installed string
 | 
						|
	Candidate string
 | 
						|
	Repo      string
 | 
						|
}
 | 
						|
 | 
						|
// parseAptCachePolicy the stdout of parse pat-get cache policy
 | 
						|
func (o *debian) parseAptCachePolicy(stdout, name string) (packCandidateVer, error) {
 | 
						|
	ver := packCandidateVer{Name: name}
 | 
						|
	lines := strings.Split(stdout, "\n")
 | 
						|
	isRepoline := false
 | 
						|
	for _, line := range lines {
 | 
						|
		fields := strings.Fields(line)
 | 
						|
		if len(fields) < 2 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		switch fields[0] {
 | 
						|
		case "Installed:":
 | 
						|
			ver.Installed = fields[1]
 | 
						|
		case "Candidate:":
 | 
						|
			ver.Candidate = fields[1]
 | 
						|
			goto nextline
 | 
						|
		default:
 | 
						|
			// nop
 | 
						|
		}
 | 
						|
		if ver.Candidate != "" && strings.Contains(line, ver.Candidate) {
 | 
						|
			isRepoline = true
 | 
						|
			goto nextline
 | 
						|
		}
 | 
						|
 | 
						|
		if isRepoline {
 | 
						|
			ss := strings.Split(strings.TrimSpace(line), " ")
 | 
						|
			if len(ss) == 5 || len(ss) == 4 {
 | 
						|
				ver.Repo = ss[2]
 | 
						|
				return ver, nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
	nextline:
 | 
						|
	}
 | 
						|
	return ver, xerrors.Errorf("Unknown Format: %s", stdout)
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) checkrestart() error {
 | 
						|
	initName, err := o.detectInitSystem()
 | 
						|
	if err != nil {
 | 
						|
		o.log.Warn(err)
 | 
						|
		// continue scanning
 | 
						|
	}
 | 
						|
 | 
						|
	cmd := "LANGUAGE=en_US.UTF-8 checkrestart"
 | 
						|
	r := o.exec(cmd, sudo)
 | 
						|
	if !r.isSuccess() {
 | 
						|
		return xerrors.Errorf(
 | 
						|
			"Failed to %s. status: %d, stdout: %s, stderr: %s",
 | 
						|
			cmd, r.ExitStatus, r.Stdout, r.Stderr)
 | 
						|
	}
 | 
						|
	packs, unknownServices := o.parseCheckRestart(r.Stdout)
 | 
						|
	pidService := map[string]string{}
 | 
						|
	if initName == upstart {
 | 
						|
		for _, s := range unknownServices {
 | 
						|
			cmd := "LANGUAGE=en_US.UTF-8 initctl status " + s
 | 
						|
			r := o.exec(cmd, sudo)
 | 
						|
			if !r.isSuccess() {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			if ss := strings.Fields(r.Stdout); len(ss) == 4 && ss[2] == "process" {
 | 
						|
				pidService[ss[3]] = s
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	for i, p := range packs {
 | 
						|
		pack := o.Packages[p.Name]
 | 
						|
		pack.NeedRestartProcs = p.NeedRestartProcs
 | 
						|
		o.Packages[p.Name] = pack
 | 
						|
 | 
						|
		for j, proc := range p.NeedRestartProcs {
 | 
						|
			if !proc.HasInit {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			packs[i].NeedRestartProcs[j].InitSystem = initName
 | 
						|
			if initName == systemd {
 | 
						|
				name, err := o.detectServiceName(proc.PID)
 | 
						|
				if err != nil {
 | 
						|
					o.log.Warn(err)
 | 
						|
					// continue scanning
 | 
						|
				}
 | 
						|
				packs[i].NeedRestartProcs[j].ServiceName = name
 | 
						|
			} else {
 | 
						|
				if proc.ServiceName == "" {
 | 
						|
					if ss := strings.Fields(r.Stdout); len(ss) == 4 && ss[2] == "process" {
 | 
						|
						if name, ok := pidService[ss[3]]; ok {
 | 
						|
							packs[i].NeedRestartProcs[j].ServiceName = name
 | 
						|
						}
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		o.Packages[p.Name] = p
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) parseCheckRestart(stdout string) (models.Packages, []string) {
 | 
						|
	services := []string{}
 | 
						|
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
						|
	for scanner.Scan() {
 | 
						|
		line := scanner.Text()
 | 
						|
		if strings.HasPrefix(line, "service") && strings.HasSuffix(line, "restart") {
 | 
						|
			ss := strings.Fields(line)
 | 
						|
			if len(ss) != 3 {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			services = append(services, ss[1])
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	packs := models.Packages{}
 | 
						|
	packName := ""
 | 
						|
	hasInit := true
 | 
						|
	scanner = bufio.NewScanner(strings.NewReader(stdout))
 | 
						|
	for scanner.Scan() {
 | 
						|
		line := scanner.Text()
 | 
						|
		if strings.HasSuffix(line, "do not seem to have an associated init script to restart them:") {
 | 
						|
			hasInit = false
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if strings.HasSuffix(line, ":") && len(strings.Fields(line)) == 1 {
 | 
						|
			packName = strings.TrimSuffix(line, ":")
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		if strings.HasPrefix(line, "\t") {
 | 
						|
			ss := strings.Fields(line)
 | 
						|
			if len(ss) != 2 {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			serviceName := ""
 | 
						|
			for _, s := range services {
 | 
						|
				if packName == s {
 | 
						|
					serviceName = s
 | 
						|
				}
 | 
						|
			}
 | 
						|
			if p, ok := packs[packName]; ok {
 | 
						|
				p.NeedRestartProcs = append(p.NeedRestartProcs, models.NeedRestartProcess{
 | 
						|
					PID:         ss[0],
 | 
						|
					Path:        ss[1],
 | 
						|
					ServiceName: serviceName,
 | 
						|
					HasInit:     hasInit,
 | 
						|
				})
 | 
						|
				packs[packName] = p
 | 
						|
			} else {
 | 
						|
				packs[packName] = models.Package{
 | 
						|
					Name: packName,
 | 
						|
					NeedRestartProcs: []models.NeedRestartProcess{
 | 
						|
						{
 | 
						|
							PID:         ss[0],
 | 
						|
							Path:        ss[1],
 | 
						|
							ServiceName: serviceName,
 | 
						|
							HasInit:     hasInit,
 | 
						|
						},
 | 
						|
					},
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	unknownServices := []string{}
 | 
						|
	for _, s := range services {
 | 
						|
		found := false
 | 
						|
		for _, p := range packs {
 | 
						|
			for _, proc := range p.NeedRestartProcs {
 | 
						|
				if proc.ServiceName == s {
 | 
						|
					found = true
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if !found {
 | 
						|
			unknownServices = append(unknownServices, s)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return packs, unknownServices
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) getOwnerPkgs(paths []string) (pkgNames []string, err error) {
 | 
						|
	cmd := "dpkg -S " + strings.Join(paths, " ")
 | 
						|
	r := o.exec(util.PrependProxyEnv(cmd), noSudo)
 | 
						|
	if !r.isSuccess(0, 1) {
 | 
						|
		return nil, xerrors.Errorf("Failed to SSH: %s", r)
 | 
						|
	}
 | 
						|
	return o.parseGetPkgName(r.Stdout), nil
 | 
						|
}
 | 
						|
 | 
						|
func (o *debian) parseGetPkgName(stdout string) (pkgNames []string) {
 | 
						|
	uniq := map[string]struct{}{}
 | 
						|
	scanner := bufio.NewScanner(strings.NewReader(stdout))
 | 
						|
	for scanner.Scan() {
 | 
						|
		line := scanner.Text()
 | 
						|
		ss := strings.Fields(line)
 | 
						|
		if len(ss) < 2 || ss[1] == "no" {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		s := strings.Split(ss[0], ":")[0]
 | 
						|
		uniq[s] = struct{}{}
 | 
						|
	}
 | 
						|
	for n := range uniq {
 | 
						|
		pkgNames = append(pkgNames, n)
 | 
						|
	}
 | 
						|
	return pkgNames
 | 
						|
}
 |