1package models 2 3import ( 4 "bytes" 5 "fmt" 6 "regexp" 7 "strings" 8 9 "golang.org/x/xerrors" 10) 11 12// Packages is Map of Package 13// { "package-name": Package } 14type Packages map[string]Package 15 16// NewPackages create Packages 17func NewPackages(packs ...Package) Packages { 18 m := Packages{} 19 for _, pack := range packs { 20 m[pack.Name] = pack 21 } 22 return m 23} 24 25// MergeNewVersion merges candidate version information to the receiver struct 26func (ps Packages) MergeNewVersion(as Packages) { 27 for name, pack := range ps { 28 pack.NewVersion = pack.Version 29 pack.NewRelease = pack.Release 30 ps[name] = pack 31 } 32 33 for _, a := range as { 34 if pack, ok := ps[a.Name]; ok { 35 pack.NewVersion = a.NewVersion 36 pack.NewRelease = a.NewRelease 37 pack.Repository = a.Repository 38 ps[a.Name] = pack 39 } 40 } 41} 42 43// Merge returns merged map (immutable) 44func (ps Packages) Merge(other Packages) Packages { 45 merged := Packages{} 46 for k, v := range ps { 47 merged[k] = v 48 } 49 for k, v := range other { 50 merged[k] = v 51 } 52 return merged 53} 54 55// FindOne search a element 56func (ps Packages) FindOne(f func(Package) bool) (string, Package, bool) { 57 for key, p := range ps { 58 if f(p) { 59 return key, p, true 60 } 61 } 62 return "", Package{}, false 63} 64 65// FindByFQPN search a package by Fully-Qualified-Package-Name 66func (ps Packages) FindByFQPN(nameVerRelArc string) (*Package, error) { 67 for _, p := range ps { 68 if nameVerRelArc == p.FQPN() { 69 return &p, nil 70 } 71 } 72 return nil, xerrors.Errorf("Failed to find the package: %s", nameVerRelArc) 73} 74 75// Package has installed binary packages. 76type Package struct { 77 Name string `json:"name"` 78 Version string `json:"version"` 79 Release string `json:"release"` 80 NewVersion string `json:"newVersion"` 81 NewRelease string `json:"newRelease"` 82 Arch string `json:"arch"` 83 Repository string `json:"repository"` 84 Changelog Changelog `json:"changelog"` 85 AffectedProcs []AffectedProcess `json:",omitempty"` 86 NeedRestartProcs []NeedRestartProcess `json:",omitempty"` 87} 88 89// FQPN returns Fully-Qualified-Package-Name 90// name-version-release.arch 91func (p Package) FQPN() string { 92 fqpn := p.Name 93 if p.Version != "" { 94 fqpn += fmt.Sprintf("-%s", p.Version) 95 } 96 if p.Release != "" { 97 fqpn += fmt.Sprintf("-%s", p.Release) 98 } 99 if p.Arch != "" { 100 fqpn += fmt.Sprintf(".%s", p.Arch) 101 } 102 return fqpn 103} 104 105// FormatVer returns package version-release 106func (p Package) FormatVer() string { 107 ver := p.Version 108 if 0 < len(p.Release) { 109 ver = fmt.Sprintf("%s-%s", ver, p.Release) 110 } 111 return ver 112} 113 114// FormatNewVer returns package version-release 115func (p Package) FormatNewVer() string { 116 ver := p.NewVersion 117 if 0 < len(p.NewRelease) { 118 ver = fmt.Sprintf("%s-%s", ver, p.NewRelease) 119 } 120 return ver 121} 122 123// FormatVersionFromTo formats installed and new package version 124func (p Package) FormatVersionFromTo(stat PackageFixStatus) string { 125 to := p.FormatNewVer() 126 if stat.NotFixedYet { 127 if stat.FixState != "" { 128 to = stat.FixState 129 } else { 130 to = "Not Fixed Yet" 131 } 132 } else if p.NewVersion == "" { 133 to = "Unknown" 134 } 135 var fixedIn string 136 if stat.FixedIn != "" { 137 fixedIn = fmt.Sprintf(" (FixedIn: %s)", stat.FixedIn) 138 } 139 return fmt.Sprintf("%s-%s -> %s%s", 140 p.Name, p.FormatVer(), to, fixedIn) 141} 142 143// FormatChangelog formats the changelog 144func (p Package) FormatChangelog() string { 145 buf := []string{} 146 packVer := fmt.Sprintf("%s-%s -> %s", 147 p.Name, p.FormatVer(), p.FormatNewVer()) 148 var delim bytes.Buffer 149 for i := 0; i < len(packVer); i++ { 150 delim.WriteString("-") 151 } 152 153 clog := p.Changelog.Contents 154 if lines := strings.Split(clog, "\n"); len(lines) != 0 { 155 clog = strings.Join(lines[0:len(lines)-1], "\n") 156 } 157 158 switch p.Changelog.Method { 159 case FailedToGetChangelog: 160 clog = "No changelogs" 161 case FailedToFindVersionInChangelog: 162 clog = "Failed to parse changelogs. For details, check yourself" 163 } 164 buf = append(buf, packVer, delim.String(), clog) 165 return strings.Join(buf, "\n") 166} 167 168// Changelog has contents of changelog and how to get it. 169// Method: models.detectionMethodStr 170type Changelog struct { 171 Contents string `json:"contents"` 172 Method DetectionMethod `json:"method"` 173} 174 175// AffectedProcess keep a processes information affected by software update 176type AffectedProcess struct { 177 PID string `json:"pid,omitempty"` 178 Name string `json:"name,omitempty"` 179 ListenPorts []string `json:"listenPorts,omitempty"` 180 ListenPortStats []PortStat `json:"listenPortStats,omitempty"` 181} 182 183// PortStat has the result of parsing the port information to the address and port. 184type PortStat struct { 185 BindAddress string `json:"bindAddress"` 186 Port string `json:"port"` 187 PortReachableTo []string `json:"portReachableTo"` 188} 189 190func NewPortStat(ipPort string) (*PortStat, error) { 191 if ipPort == "" { 192 return &PortStat{}, nil 193 } 194 sep := strings.LastIndex(ipPort, ":") 195 if sep == -1 { 196 return nil, xerrors.Errorf("Failed to parse IP:Port: %s", ipPort) 197 } 198 return &PortStat{ 199 BindAddress: ipPort[:sep], 200 Port: ipPort[sep+1:], 201 }, nil 202} 203 204// HasReachablePort checks if Package.AffectedProcs has PortReachableTo 205func (p Package) HasReachablePort() bool { 206 for _, ap := range p.AffectedProcs { 207 for _, lp := range ap.ListenPortStats { 208 if len(lp.PortReachableTo) > 0 { 209 return true 210 } 211 } 212 } 213 return false 214} 215 216// NeedRestartProcess keep a processes information affected by software update 217type NeedRestartProcess struct { 218 PID string `json:"pid"` 219 Path string `json:"path"` 220 ServiceName string `json:"serviceName"` 221 InitSystem string `json:"initSystem"` 222 HasInit bool `json:"-"` 223} 224 225// SrcPackage has installed source package information. 226// Debian based Linux has both of package and source information in dpkg. 227// OVAL database often includes a source version (Not a binary version), 228// so it is also needed to capture source version for OVAL version comparison. 229// https://github.com/future-architect/vuls/issues/504 230type SrcPackage struct { 231 Name string `json:"name"` 232 Version string `json:"version"` 233 Arch string `json:"arch"` 234 BinaryNames []string `json:"binaryNames"` 235} 236 237// AddBinaryName add the name if not exists 238func (s *SrcPackage) AddBinaryName(name string) { 239 found := false 240 for _, n := range s.BinaryNames { 241 if n == name { 242 return 243 } 244 } 245 if !found { 246 s.BinaryNames = append(s.BinaryNames, name) 247 } 248} 249 250// SrcPackages is Map of SrcPackage 251// { "package-name": SrcPackage } 252type SrcPackages map[string]SrcPackage 253 254// FindByBinName finds by bin-package-name 255func (s SrcPackages) FindByBinName(name string) (*SrcPackage, bool) { 256 for _, p := range s { 257 for _, binName := range p.BinaryNames { 258 if binName == name { 259 return &p, true 260 } 261 } 262 } 263 return nil, false 264} 265 266// raspiPackNamePattern is a regular expression pattern to detect the Raspberry Pi specific package from the package name. 267// e.g. libraspberrypi-dev, rpi-eeprom, python3-rpi.gpio, pi-bluetooth 268var raspiPackNamePattern = regexp.MustCompile(`(.*raspberry.*|^rpi.*|.*-rpi.*|^pi-.*)`) 269 270// raspiPackNamePattern is a regular expression pattern to detect the Raspberry Pi specific package from the version. 271// e.g. ffmpeg 7:4.1.4-1+rpt7~deb10u1, vlc 3.0.10-0+deb10u1+rpt2 272var raspiPackVersionPattern = regexp.MustCompile(`.+\+rp(t|i)\d+`) 273 274// raspiPackNameList is a package name array of Raspberry Pi specific packages that are difficult to detect with regular expressions. 275var raspiPackNameList = []string{"piclone", "pipanel", "pishutdown", "piwiz", "pixflat-icons"} 276 277// IsRaspbianPackage judges whether it is a package related to Raspberry Pi from the package name and version 278func IsRaspbianPackage(name, version string) bool { 279 if raspiPackNamePattern.MatchString(name) || raspiPackVersionPattern.MatchString(version) { 280 return true 281 } 282 for _, n := range raspiPackNameList { 283 if n == name { 284 return true 285 } 286 } 287 288 return false 289} 290