/* Vuls - Vulnerability Scanner Copyright (C) 2016 Future Architect, Inc. Japan. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package scan import ( "fmt" "regexp" "sort" "strconv" "strings" "time" "github.com/future-architect/vuls/config" "github.com/future-architect/vuls/cveapi" "github.com/future-architect/vuls/models" "github.com/future-architect/vuls/util" "github.com/k0kubun/pp" ) // inherit OsTypeInterface type redhat struct { base } // NewRedhat is constructor func newRedhat(c config.ServerInfo) *redhat { r := &redhat{} r.log = util.NewCustomLogger(c) return r } // https://github.com/serverspec/specinfra/blob/master/lib/specinfra/helper/detect_os/redhat.rb func detectRedhat(c config.ServerInfo) (itsMe bool, red osTypeInterface) { red = newRedhat(c) // set sudo option flag c.SudoOpt = config.SudoOption{ExecBySudo: true} red.setServerInfo(c) if r := sshExec(c, "ls /etc/fedora-release", noSudo); r.isSuccess() { red.setDistributionInfo("fedora", "unknown") Log.Warn("Fedora not tested yet: %s", r) return true, red } if r := sshExec(c, "ls /etc/redhat-release", noSudo); r.isSuccess() { // https://www.rackaid.com/blog/how-to-determine-centos-or-red-hat-version/ // e.g. // $ cat /etc/redhat-release // CentOS release 6.5 (Final) if r := sshExec(c, "cat /etc/redhat-release", noSudo); r.isSuccess() { re, _ := regexp.Compile(`(.*) release (\d[\d.]*)`) result := re.FindStringSubmatch(strings.TrimSpace(r.Stdout)) if len(result) != 3 { Log.Warn("Failed to parse RedHat/CentOS version: %s", r) return true, red } release := result[2] switch strings.ToLower(result[1]) { case "centos", "centos linux": red.setDistributionInfo("centos", release) default: red.setDistributionInfo("rhel", release) } return true, red } return true, red } if r := sshExec(c, "ls /etc/system-release", noSudo); r.isSuccess() { family := "amazon" release := "unknown" if r := sshExec(c, "cat /etc/system-release", noSudo); r.isSuccess() { fields := strings.Fields(r.Stdout) if len(fields) == 5 { release = fields[4] } } red.setDistributionInfo(family, release) return true, red } Log.Debugf("Not RedHat like Linux. servername: %s", c.ServerName) return false, red } // CentOS 5 ... yum-plugin-security, yum-changelog // CentOS 6 ... yum-plugin-security, yum-plugin-changelog // CentOS 7 ... yum-plugin-security, yum-plugin-changelog // RHEL, Amazon ... no additinal packages needed func (o *redhat) install() error { switch o.Family { case "rhel", "amazon": o.log.Infof("Nothing to do") return nil } if err := o.installYumPluginSecurity(); err != nil { return err } return o.installYumChangelog() } func (o *redhat) installYumPluginSecurity() error { if r := o.ssh("rpm -q yum-plugin-security", noSudo); r.isSuccess() { o.log.Infof("Ignored: yum-plugin-security already installed") return nil } o.log.Info("Installing yum-plugin-security...") cmd := util.PrependProxyEnv("yum install -y yum-plugin-security") if r := o.ssh(cmd, sudo); !r.isSuccess() { return fmt.Errorf("Failed to SSH: %s", r) } return nil } func (o *redhat) installYumChangelog() error { if o.Family == "centos" { var majorVersion int if 0 < len(o.Release) { majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0]) } else { return fmt.Errorf( "Not implemented yet. family: %s, release: %s", o.Family, o.Release) } var packName = "" if majorVersion < 6 { packName = "yum-changelog" } else { packName = "yum-plugin-changelog" } cmd := "rpm -q " + packName if r := o.ssh(cmd, noSudo); r.isSuccess() { o.log.Infof("Ignored: %s already installed", packName) return nil } o.log.Infof("Installing %s...", packName) cmd = util.PrependProxyEnv("yum install -y " + packName) if r := o.ssh(cmd, sudo); !r.isSuccess() { return fmt.Errorf("Failed to SSH: %s", r) } o.log.Infof("Installed: %s", packName) } return nil } func (o *redhat) checkRequiredPackagesInstalled() error { if config.Conf.UseYumPluginSecurity { // check if yum-plugin-security is installed. // Amazon Linux, REHL can execute 'yum updateinfo --security updates' without yum-plugin-security if o.Family == "centos" { cmd := "rpm -q yum-plugin-security" if r := o.ssh(cmd, noSudo); !r.isSuccess() { msg := "yum-plugin-security is not installed" o.log.Errorf(msg) return fmt.Errorf(msg) } } return nil } if o.Family == "centos" { var majorVersion int if 0 < len(o.Release) { majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0]) } else { msg := fmt.Sprintf("Not implemented yet. family: %s, release: %s", o.Family, o.Release) o.log.Errorf(msg) return fmt.Errorf(msg) } var packName = "" if majorVersion < 6 { packName = "yum-changelog" } else { packName = "yum-plugin-changelog" } cmd := "rpm -q " + packName if r := o.ssh(cmd, noSudo); !r.isSuccess() { msg := fmt.Sprintf("%s is not installed", packName) o.log.Errorf(msg) return fmt.Errorf(msg) } } return nil } func (o *redhat) scanPackages() error { var err error var packs []models.PackageInfo if packs, err = o.scanInstalledPackages(); err != nil { o.log.Errorf("Failed to scan installed packages") return err } o.setPackages(packs) var unsecurePacks []CvePacksInfo if unsecurePacks, err = o.scanUnsecurePackages(); err != nil { o.log.Errorf("Failed to scan vulnerable packages") return err } o.setUnsecurePackages(unsecurePacks) return nil } func (o *redhat) scanInstalledPackages() (installedPackages models.PackageInfoList, err error) { cmd := "rpm -qa --queryformat '%{NAME}\t%{VERSION}\t%{RELEASE}\n'" r := o.ssh(cmd, noSudo) if r.isSuccess() { // e.g. // openssl 1.0.1e 30.el6.11 lines := strings.Split(r.Stdout, "\n") for _, line := range lines { if trimed := strings.TrimSpace(line); len(trimed) != 0 { var packinfo models.PackageInfo if packinfo, err = o.parseScannedPackagesLine(line); err != nil { return } installedPackages = append(installedPackages, packinfo) } } return } return installedPackages, fmt.Errorf( "Scan packages failed. status: %d, stdout: %s, stderr: %s", r.ExitStatus, r.Stdout, r.Stderr) } func (o *redhat) parseScannedPackagesLine(line string) (models.PackageInfo, error) { fields := strings.Fields(line) if len(fields) != 3 { return models.PackageInfo{}, fmt.Errorf("Failed to parse package line: %s", line) } return models.PackageInfo{ Name: fields[0], Version: fields[1], Release: fields[2], }, nil } func (o *redhat) scanUnsecurePackages() ([]CvePacksInfo, error) { if o.Family != "centos" || config.Conf.UseYumPluginSecurity { // Amazon, RHEL has yum updateinfo as default // yum updateinfo can collenct vendor advisory information. return o.scanUnsecurePackagesUsingYumPluginSecurity() } // CentOS does not have security channel... // So, yum check-update then parse chnagelog. return o.scanUnsecurePackagesUsingYumCheckUpdate() } //TODO return whether already expired. func (o *redhat) scanUnsecurePackagesUsingYumCheckUpdate() (CvePacksList, error) { cmd := "LANG=en_US.UTF-8 yum --color=never check-update" r := o.ssh(util.PrependProxyEnv(cmd), sudo) if !r.isSuccess(0, 100) { //returns an exit code of 100 if there are available updates. return nil, fmt.Errorf("Failed to SSH: %s", r) } // get Updateble package name, installed, candidate version. packInfoList, err := o.parseYumCheckUpdateLines(r.Stdout) if err != nil { return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err) } o.log.Debugf("%s", pp.Sprintf("%v", packInfoList)) // Collect CVE-IDs in changelog type PackInfoCveIDs struct { PackInfo models.PackageInfo CveIDs []string } var rpm2changelog map[string]*string if !config.Conf.SSHExternal { allChangelog, err := o.getAllChangelog(packInfoList) if err != nil { o.log.Errorf("Failed to getAllchangelog. err: %s", err) return nil, err } rpm2changelog, err = o.parseAllChangelog(allChangelog) if err != nil { return nil, fmt.Errorf("Failed to parseAllChangelog. err: %s", err) } } var results []PackInfoCveIDs for i, packInfo := range packInfoList { changelog := "" if !config.Conf.SSHExternal { changelog = o.getChangelogCVELines(rpm2changelog, packInfo) } else { changelog, err = o.getChangelog(packInfo.Name) if err != nil { o.log.Errorf("Failed to collect CVE IDs. err: %s", err) return nil, err } } // Collect unique set of CVE-ID in each changelog uniqueCveIDMap := make(map[string]bool) lines := strings.Split(changelog, "\n") for _, line := range lines { cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line) for _, c := range cveIDs { uniqueCveIDMap[c] = true } } // keys var cveIDs []string for k := range uniqueCveIDMap { cveIDs = append(cveIDs, k) } p := PackInfoCveIDs{ PackInfo: packInfo, CveIDs: cveIDs, } results = append(results, p) o.log.Infof("(%d/%d) Scanned %s-%s-%s -> %s-%s : %s", i+1, len(packInfoList), p.PackInfo.Name, p.PackInfo.Version, p.PackInfo.Release, p.PackInfo.NewVersion, p.PackInfo.NewRelease, p.CveIDs) } // transform datastructure // - From // [ // { // PackInfo: models.PackageInfo, // CveIDs: []string, // }, // ] // - To // map { // CveID: []models.PackageInfo // } cveIDPackInfoMap := make(map[string][]models.PackageInfo) for _, res := range results { for _, cveID := range res.CveIDs { // packInfo, found := o.Packages.FindByName(res.Packname) // if !found { // return CvePacksList{}, fmt.Errorf( // "Faild to transform data structure: %v", res.Packname) // } cveIDPackInfoMap[cveID] = append(cveIDPackInfoMap[cveID], res.PackInfo) } } var uniqueCveIDs []string for cveID := range cveIDPackInfoMap { uniqueCveIDs = append(uniqueCveIDs, cveID) } // cveIDs => []cve.CveInfo o.log.Info("Fetching CVE details...") cveDetails, err := cveapi.CveClient.FetchCveDetails(uniqueCveIDs) if err != nil { return nil, err } o.log.Info("Done") cvePacksList := []CvePacksInfo{} for _, detail := range cveDetails { // Amazon, RHEL do not use this method, so VendorAdvisory do not set. cvePacksList = append(cvePacksList, CvePacksInfo{ CveID: detail.CveID, CveDetail: detail, Packs: cveIDPackInfoMap[detail.CveID], // CvssScore: cinfo.CvssScore(conf.Lang), }) } return cvePacksList, nil } // parseYumCheckUpdateLines parse yum check-update to get package name, candidate version func (o *redhat) parseYumCheckUpdateLines(stdout string) (results models.PackageInfoList, err error) { needToParse := false lines := strings.Split(stdout, "\n") for _, line := range lines { // update information of packages begin after blank line. if trimed := strings.TrimSpace(line); len(trimed) == 0 { needToParse = true continue } if needToParse { if strings.HasPrefix(line, "Obsoleting") { continue } candidate, err := o.parseYumCheckUpdateLine(line) if err != nil { return results, err } installed, found := o.Packages.FindByName(candidate.Name) if !found { o.log.Warnf("Not found the package in rpm -qa. candidate: %s-%s-%s", candidate.Name, candidate.Version, candidate.Release) results = append(results, candidate) continue } installed.NewVersion = candidate.NewVersion installed.NewRelease = candidate.NewRelease results = append(results, installed) } } return } func (o *redhat) parseYumCheckUpdateLine(line string) (models.PackageInfo, error) { fields := strings.Fields(line) if len(fields) != 3 { return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line) } splitted := strings.Split(fields[0], ".") packName := "" if len(splitted) == 1 { packName = fields[0] } else { packName = strings.Join(strings.Split(fields[0], ".")[0:(len(splitted)-1)], ".") } fields = strings.Split(fields[1], "-") if len(fields) != 2 { return models.PackageInfo{}, fmt.Errorf("Unknown format: %s", line) } version := fields[0] release := fields[1] return models.PackageInfo{ Name: packName, NewVersion: version, NewRelease: release, }, nil } func (o *redhat) getChangelog(packageNames string) (stdout string, err error) { command := "" if o.ServerInfo.User == "root" { command = "echo N | " } if 0 < len(config.Conf.HTTPProxy) { command += util.ProxyEnv() } // yum update --changelog doesn't have --color option. command += fmt.Sprintf(" yum update --changelog %s | grep CVE", packageNames) r := o.ssh(command, sudo) if !r.isSuccess(0, 1) { return "", fmt.Errorf("Failed to SSH: %s", r) } return r.Stdout, nil } func (o *redhat) mkPstring() *string { str := "" return &str } func (o *redhat) regexpReplace(src string, pat string, rep string) string { r := regexp.MustCompile(pat) return r.ReplaceAllString(src, rep) } func (o *redhat) getChangelogCVELines(rpm2changelog map[string]*string, packInfo models.PackageInfo) string { rpm := fmt.Sprintf("%s-%s-%s", packInfo.Name, o.regexpReplace(packInfo.NewVersion, `^[0-9]+:`, ""), packInfo.NewRelease) retLine := "" if rpm2changelog[rpm] != nil { lines := strings.Split(*rpm2changelog[rpm], "\n") for _, line := range lines { match, _ := regexp.MatchString("CVE-[0-9]+-[0-9]+", line) if match { retLine += fmt.Sprintf("%s\n", line) } } } return retLine } func (o *redhat) parseAllChangelog(allChangelog string) (map[string]*string, error) { var majorVersion int if 0 < len(o.Release) && o.Family == "centos" { majorVersion, _ = strconv.Atoi(strings.Split(o.Release, ".")[0]) } else { return nil, fmt.Errorf( "Not implemented yet. family: %s, release: %s", o.Family, o.Release) } orglines := strings.Split(allChangelog, "\n") tmpline := "" var lines []string var prev, now bool for i, _ := range orglines { if majorVersion == 5 { /* for CentOS5 (yum-util < 1.1.20) */ prev = false now = false if i > 0 { prev, _ = o.isRpmPackageNameLine(orglines[i-1]) } now, _ = o.isRpmPackageNameLine(orglines[i]) if prev && now { tmpline = fmt.Sprintf("%s, %s", tmpline, orglines[i]) continue } if !prev && now { tmpline = fmt.Sprintf("%s%s", tmpline, orglines[i]) continue } if tmpline != "" { lines = append(lines, fmt.Sprintf("%s", tmpline)) tmpline = "" } lines = append(lines, fmt.Sprintf("%s", orglines[i])) } else { /* for CentOS6,7 (yum-util >= 1.1.20) */ line := orglines[i] line = o.regexpReplace(line, `^ChangeLog for: `, "") line = o.regexpReplace(line, `^\*\*\sNo\sChangeLog\sfor:.*`, "") lines = append(lines, line) } } rpm2changelog := make(map[string]*string) writePointer := o.mkPstring() for _, line := range lines { match, _ := o.isRpmPackageNameLine(line) if match { rpms := strings.Split(line, ",") pNewString := o.mkPstring() writePointer = pNewString for _, rpm := range rpms { rpm = strings.TrimSpace(rpm) rpm = o.regexpReplace(rpm, `^[0-9]+:`, "") rpm = o.regexpReplace(rpm, `\.centos([.0-9]+)?\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`, "") rpm = o.regexpReplace(rpm, `\.(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)$`, "") rpm2changelog[rpm] = pNewString } } else { stop, _ := regexp.MatchString("^Dependencies Resolved", line) if stop { return rpm2changelog, nil } else { *writePointer += fmt.Sprintf("%s\n", line) } } } return rpm2changelog, nil } func (o *redhat) getAllChangelog(packInfoList models.PackageInfoList) (stdout string, err error) { packageNames := "" for _, packInfo := range packInfoList { packageNames += fmt.Sprintf("%s ", packInfo.Name) } command := "" if o.ServerInfo.User == "root" { command = "echo N | " } if 0 < len(config.Conf.HTTPProxy) { command += util.ProxyEnv() } // yum update --changelog doesn't have --color option. command += fmt.Sprintf(" yum update --changelog %s", packageNames) r := o.ssh(command, sudo) if !r.isSuccess(0, 1) { return "", fmt.Errorf( "Failed to get changelog. status: %d, stdout: %s, stderr: %s", r.ExitStatus, r.Stdout, r.Stderr) } return r.Stdout, nil } type distroAdvisoryCveIDs struct { DistroAdvisory models.DistroAdvisory CveIDs []string } // Scaning unsecure packages using yum-plugin-security. //TODO return whether already expired. func (o *redhat) scanUnsecurePackagesUsingYumPluginSecurity() (CvePacksList, error) { if o.Family == "centos" { // CentOS has no security channel. // So use yum check-update && parse changelog return CvePacksList{}, fmt.Errorf( "yum updateinfo is not suppported on CentOS") } cmd := "yum --color=never repolist" r := o.ssh(util.PrependProxyEnv(cmd), sudo) if !r.isSuccess() { return nil, fmt.Errorf("Failed to SSH: %s", r) } // get advisoryID(RHSA, ALAS) - package name,version cmd = "yum --color=never updateinfo list available --security" r = o.ssh(util.PrependProxyEnv(cmd), sudo) if !r.isSuccess() { return nil, fmt.Errorf("Failed to SSH: %s", r) } advIDPackNamesList, err := o.parseYumUpdateinfoListAvailable(r.Stdout) // get package name, version, rel to be upgrade. // cmd = "yum check-update --security" cmd = "LANG=en_US.UTF-8 yum --color=never check-update" r = o.ssh(util.PrependProxyEnv(cmd), sudo) if !r.isSuccess(0, 100) { //returns an exit code of 100 if there are available updates. return nil, fmt.Errorf("Failed to SSH: %s", r) } updatable, err := o.parseYumCheckUpdateLines(r.Stdout) if err != nil { return nil, fmt.Errorf("Failed to parse %s. err: %s", cmd, err) } o.log.Debugf("%s", pp.Sprintf("%v", updatable)) dict := map[string][]models.PackageInfo{} for _, advIDPackNames := range advIDPackNamesList { packInfoList := models.PackageInfoList{} for _, packName := range advIDPackNames.PackNames { packInfo, found := updatable.FindByName(packName) if !found { return nil, fmt.Errorf( "PackInfo not found. packInfo: %#v", packName) } packInfoList = append(packInfoList, packInfo) continue } dict[advIDPackNames.AdvisoryID] = packInfoList } // get advisoryID(RHSA, ALAS) - CVE IDs cmd = "yum --color=never updateinfo --security update" r = o.ssh(util.PrependProxyEnv(cmd), sudo) if !r.isSuccess() { return nil, fmt.Errorf("Failed to SSH: %s", r) } advisoryCveIDsList, err := o.parseYumUpdateinfo(r.Stdout) if err != nil { return CvePacksList{}, err } // pp.Println(advisoryCveIDsList) // All information collected. // Convert to CvePacksList. o.log.Info("Fetching CVE details...") result := CvePacksList{} for _, advIDCveIDs := range advisoryCveIDsList { cveDetails, err := cveapi.CveClient.FetchCveDetails(advIDCveIDs.CveIDs) if err != nil { return nil, err } for _, cveDetail := range cveDetails { found := false for i, p := range result { if cveDetail.CveID == p.CveID { advAppended := append(p.DistroAdvisories, advIDCveIDs.DistroAdvisory) result[i].DistroAdvisories = advAppended packs := dict[advIDCveIDs.DistroAdvisory.AdvisoryID] result[i].Packs = append(result[i].Packs, packs...) found = true break } } if !found { cpinfo := CvePacksInfo{ CveID: cveDetail.CveID, CveDetail: cveDetail, DistroAdvisories: []models.DistroAdvisory{advIDCveIDs.DistroAdvisory}, Packs: dict[advIDCveIDs.DistroAdvisory.AdvisoryID], } result = append(result, cpinfo) } } } o.log.Info("Done") return result, nil } func (o *redhat) parseYumUpdateinfo(stdout string) (result []distroAdvisoryCveIDs, err error) { sectionState := Outside lines := strings.Split(stdout, "\n") lines = append(lines, "=============") // Amazon Linux AMI Security Information advisory := models.DistroAdvisory{} cveIDsSetInThisSection := make(map[string]bool) // use this flag to Collect CVE IDs in CVEs field. var inDesctiption = false for _, line := range lines { line = strings.TrimSpace(line) // find the new section pattern if match, _ := o.isHorizontalRule(line); match { // set previous section's result to return-variable if sectionState == Content { foundCveIDs := []string{} for cveID := range cveIDsSetInThisSection { foundCveIDs = append(foundCveIDs, cveID) } sort.Strings(foundCveIDs) result = append(result, distroAdvisoryCveIDs{ DistroAdvisory: advisory, CveIDs: foundCveIDs, }) // reset for next section. cveIDsSetInThisSection = make(map[string]bool) inDesctiption = false } // Go to next section sectionState = o.changeSectionState(sectionState) continue } switch sectionState { case Header: switch o.Family { case "centos": // CentOS has no security channel. // So use yum check-update && parse changelog return result, fmt.Errorf( "yum updateinfo is not suppported on CentOS") case "rhel", "amazon": // nop } case Content: if found := o.isDescriptionLine(line); found { inDesctiption = true } // severity severity, found := o.parseYumUpdateinfoToGetSeverity(line) if found { advisory.Severity = severity } // No need to parse in description except severity if inDesctiption { continue } cveIDs := o.parseYumUpdateinfoLineToGetCveIDs(line) for _, cveID := range cveIDs { cveIDsSetInThisSection[cveID] = true } advisoryID, found := o.parseYumUpdateinfoToGetAdvisoryID(line) if found { advisory.AdvisoryID = advisoryID } issued, found := o.parseYumUpdateinfoLineToGetIssued(line) if found { advisory.Issued = issued } updated, found := o.parseYumUpdateinfoLineToGetUpdated(line) if found { advisory.Updated = updated } } } return } // state const ( Outside = iota Header = iota Content = iota ) func (o *redhat) changeSectionState(state int) (newState int) { switch state { case Outside, Content: newState = Header case Header: newState = Content } return newState } func (o *redhat) isHorizontalRule(line string) (bool, error) { return regexp.MatchString("^=+$", line) } func (o *redhat) isRpmPackageNameLine(line string) (bool, error) { return regexp.MatchString("^[^ ]+(i386|i486|i586|i686|k6|athlon|x86_64|noarch|ppc|alpha|sparc)", line) } // see test case func (o *redhat) parseYumUpdateinfoHeaderCentOS(line string) (packs []models.PackageInfo, err error) { pkgs := strings.Split(strings.TrimSpace(line), ",") for _, pkg := range pkgs { packs = append(packs, models.PackageInfo{}) s := strings.Split(pkg, "-") if len(s) == 3 { packs[len(packs)-1].Name = s[0] packs[len(packs)-1].Version = s[1] packs[len(packs)-1].Release = s[2] } else { return packs, fmt.Errorf("CentOS: Unknown Header format: %s", line) } } return } func (o *redhat) parseYumUpdateinfoHeaderAmazon(line string) (a models.DistroAdvisory, names []string, err error) { re, _ := regexp.Compile(`(ALAS-.+): (.+) priority package update for (.+)$`) result := re.FindStringSubmatch(line) if len(result) == 4 { a.AdvisoryID = result[1] a.Severity = result[2] spaceSeparatedPacknames := result[3] names = strings.Fields(spaceSeparatedPacknames) return } err = fmt.Errorf("Amazon Linux: Unknown Header Format. %s", line) return } func (o *redhat) parseYumUpdateinfoLineToGetCveIDs(line string) []string { re, _ := regexp.Compile(`(CVE-\d{4}-\d{4})`) return re.FindAllString(line, -1) } func (o *redhat) parseYumUpdateinfoToGetAdvisoryID(line string) (advisoryID string, found bool) { re, _ := regexp.Compile(`^ *Update ID : (.*)$`) result := re.FindStringSubmatch(line) if len(result) != 2 { return "", false } return strings.TrimSpace(result[1]), true } func (o *redhat) parseYumUpdateinfoLineToGetIssued(line string) (date time.Time, found bool) { return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Issued : (\d{4}-\d{2}-\d{2})`) } func (o *redhat) parseYumUpdateinfoLineToGetUpdated(line string) (date time.Time, found bool) { return o.parseYumUpdateinfoLineToGetDate(line, `^\s*Updated : (\d{4}-\d{2}-\d{2})`) } func (o *redhat) parseYumUpdateinfoLineToGetDate(line, regexpFormat string) (date time.Time, found bool) { re, _ := regexp.Compile(regexpFormat) result := re.FindStringSubmatch(line) if len(result) != 2 { return date, false } t, err := time.Parse("2006-01-02", result[1]) if err != nil { return date, false } return t, true } func (o *redhat) isDescriptionLine(line string) bool { re, _ := regexp.Compile(`^\s*Description : `) return re.MatchString(line) } func (o *redhat) parseYumUpdateinfoToGetSeverity(line string) (severity string, found bool) { re, _ := regexp.Compile(`^ *Severity : (.*)$`) result := re.FindStringSubmatch(line) if len(result) != 2 { return "", false } return strings.TrimSpace(result[1]), true } type advisoryIDPacks struct { AdvisoryID string PackNames []string } type advisoryIDPacksList []advisoryIDPacks func (list advisoryIDPacksList) find(advisoryID string) (advisoryIDPacks, bool) { for _, a := range list { if a.AdvisoryID == advisoryID { return a, true } } return advisoryIDPacks{}, false } func (o *redhat) extractPackNameVerRel(nameVerRel string) (name, ver, rel string) { fields := strings.Split(nameVerRel, ".") archTrimed := strings.Join(fields[0:len(fields)-1], ".") fields = strings.Split(archTrimed, "-") rel = fields[len(fields)-1] ver = fields[len(fields)-2] name = strings.Join(fields[0:(len(fields)-2)], "-") return } // parseYumUpdateinfoListAvailable collect AdvisorID(RHSA, ALAS), packages func (o *redhat) parseYumUpdateinfoListAvailable(stdout string) (advisoryIDPacksList, error) { result := []advisoryIDPacks{} lines := strings.Split(stdout, "\n") for _, line := range lines { if !(strings.HasPrefix(line, "RHSA") || strings.HasPrefix(line, "ALAS")) { continue } fields := strings.Fields(line) if len(fields) != 3 { return []advisoryIDPacks{}, fmt.Errorf( "Unknown format. line: %s", line) } // extract fields advisoryID := fields[0] packVersion := fields[2] packName, _, _ := o.extractPackNameVerRel(packVersion) found := false for i, s := range result { if s.AdvisoryID == advisoryID { names := s.PackNames names = append(names, packName) result[i].PackNames = names found = true break } } if !found { result = append(result, advisoryIDPacks{ AdvisoryID: advisoryID, PackNames: []string{packName}, }) } } return result, nil } func (o *redhat) clone() osTypeInterface { return o }