mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
53e112d264
Use the MSRC security bulletin artifacts for detecting Win OS vulnerabilities
292 lines
6.2 KiB
Go
292 lines
6.2 KiB
Go
package utils
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
"unicode"
|
|
)
|
|
|
|
// Rpmvercmp Compares two evr strings (EPOCH:VERSION-RELEASE) by looking at each part in order:
|
|
// - EPOCHs are compared based on their numeric values, if missing then '0' is assumed,
|
|
// if equal then VERSIONs are compared.
|
|
// - VERSIONS are compared according to librpm's rpmvercmp algo
|
|
// (see http://ftp.rpm.org/api/4.4.2.2/rpmvercmp_8c-source.html), if equal RELEASEs are
|
|
// compared.
|
|
// - RELEASEs are compared using the rpmvercmp algo, if equal then both are equal.
|
|
//
|
|
// Returns:
|
|
//
|
|
// -1 if a < b
|
|
// 0 if a == b
|
|
// 1 if a > b
|
|
func Rpmvercmp(a, b string) int {
|
|
epoch1 := epoch(a)
|
|
epoch2 := epoch(b)
|
|
|
|
if epoch1 < epoch2 {
|
|
return -1
|
|
} else if epoch1 > epoch2 {
|
|
return 1
|
|
}
|
|
|
|
if r := rpmCmp(version(a), version(b)); r != 0 {
|
|
return r
|
|
}
|
|
|
|
return rpmCmp(Release(a), Release(b))
|
|
}
|
|
|
|
type segment struct {
|
|
number *int
|
|
letters string
|
|
offset int
|
|
}
|
|
|
|
func (s segment) isEmpty() bool {
|
|
// We need to ignore tildes because they are meant to be 'modifiers'.
|
|
return s.offset == 0 && s.number == nil && s.letters == "" || s.letters == "~"
|
|
}
|
|
|
|
func (s segment) compare(b segment) int {
|
|
// Tildes 'inside' versions 'lower' the precedent seg.
|
|
if s.letters == "~" {
|
|
if b.letters == "~" {
|
|
return 0
|
|
}
|
|
return -1
|
|
}
|
|
if b.letters == "~" {
|
|
if s.letters == "~" {
|
|
return 0
|
|
}
|
|
return 1
|
|
}
|
|
|
|
// Both are numeric, in which case we compare
|
|
// their numeric values
|
|
if s.number != nil && b.number != nil {
|
|
if *s.number == *b.number {
|
|
return 0
|
|
} else if *s.number < *b.number {
|
|
return -1
|
|
} else {
|
|
return 1
|
|
}
|
|
// 'a' is a number seg, 'b' is a letter seg,
|
|
// numbers are always greater than letters
|
|
} else if s.number != nil && b.number == nil {
|
|
return 1
|
|
// 'a' is a letter seg, 'b' is a number seg
|
|
} else if s.number == nil && b.number != nil {
|
|
return -1
|
|
// Both segs are letters, then we just
|
|
// compare them
|
|
} else {
|
|
if s.letters == b.letters {
|
|
return 0
|
|
}
|
|
return strings.Compare(s.letters, b.letters)
|
|
}
|
|
}
|
|
|
|
// Returns the next maximal alphabetic or numeric segment,
|
|
// with separators (non-alphanumeric characters) ignored.
|
|
func nextSeg(ver string) segment {
|
|
var end int
|
|
|
|
var wasNum bool
|
|
var wasLetter bool
|
|
|
|
var foundTilde bool
|
|
var foundNonAlphaNum bool
|
|
|
|
for i, c := range ver {
|
|
// Non-alpha num chars are either separators or a tilde
|
|
foundNonAlphaNum = !unicode.IsNumber(c) && !unicode.IsLetter(c)
|
|
foundTilde = c == '~'
|
|
|
|
// Check whether we arrived at a different segment.
|
|
if (unicode.IsNumber(c) && wasLetter) ||
|
|
(unicode.IsLetter(c) && wasNum) ||
|
|
foundNonAlphaNum {
|
|
end = i
|
|
break
|
|
}
|
|
|
|
wasNum = unicode.IsNumber(c)
|
|
wasLetter = unicode.IsLetter(c)
|
|
}
|
|
|
|
if end == 0 {
|
|
// Edge case: 'ver' starts
|
|
// with non-alphanumeric char
|
|
// like a separator or tilde.
|
|
if foundNonAlphaNum {
|
|
end = 1
|
|
} else {
|
|
end = len(ver)
|
|
}
|
|
}
|
|
|
|
r := segment{
|
|
offset: end,
|
|
}
|
|
|
|
// Skip over non-alphanumeric chars
|
|
// (except tildes)
|
|
if foundNonAlphaNum && !foundTilde {
|
|
r.offset++
|
|
}
|
|
|
|
if wasNum {
|
|
val, err := strconv.Atoi(ver[:end])
|
|
if err == nil {
|
|
r.number = &val
|
|
}
|
|
} else {
|
|
r.letters = ver[:end]
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
// How to compare two version strings according to the rpmvercmp algorithm:
|
|
// Each label is separated into a list of maximal alphabetic or numeric segments,
|
|
// with separators (non-alphanumeric characters) ignored. So, '2.0.1' becomes ('2', '0', '1'),
|
|
// while ('2xFg33.+f.5') becomes ('2', 'xFg', '33', 'f', '5').
|
|
// All numbers are converted to their numeric value. So '10' becomes 10, '000230'
|
|
// becomes 230, and '00000' becomes 0.
|
|
// The elements in the list are compared one by one using the following algorithm:
|
|
// - If two elements are decided to be different, the label with the newer element wins
|
|
// as the newer label.
|
|
// - If the elements are decided to be equal, the next elements are
|
|
// compared until we either reach different elements or one of the lists runs out.
|
|
// - In case one of the lists run out, the other label wins as the newer label.
|
|
// So, for example, (1, 2) is newer than (1, 1), and (1, 2, 0) is newer than (1, 2).
|
|
//
|
|
// The algorithm for comparing list elements is as follows:
|
|
// - If one of the elements is a number, while the other is alphabetic, the numeric
|
|
// elements is considered newer. So 10 is newer than 'abc', and 0 is newer than 'Z'.
|
|
// - If both the elements are numbers, the larger number is considered newer.
|
|
// So 5 is newer than 4 and 10 is newer than 2. If the numbers are equal, the elements are decided equal.
|
|
// -If both the elements are alphabetic, they are compared lexicographically, with the greater string
|
|
// resulting in a newer element. So 'b' is newer than 'a', 'add' is newer than 'ZULU'
|
|
// (because lowercase characters win in str comparisons), and 'aba' is newer than 'ab'.
|
|
// - If the strings are identical, the elements are decided equal.
|
|
func rpmCmp(a, b string) int {
|
|
var offsetA int
|
|
var offsetB int
|
|
|
|
if a == "" && b != "" {
|
|
return -1
|
|
}
|
|
if a != "" && b == "" {
|
|
return 1
|
|
}
|
|
|
|
for {
|
|
if a[offsetA:] == b[offsetB:] {
|
|
return 0
|
|
}
|
|
|
|
segA := nextSeg(a[offsetA:])
|
|
segB := nextSeg(b[offsetB:])
|
|
|
|
offsetA += segA.offset
|
|
if offsetA > len(a) {
|
|
break
|
|
}
|
|
offsetB += segB.offset
|
|
if offsetB > len(b) {
|
|
break
|
|
}
|
|
|
|
// Check whether 'a' or 'b' ran out of segments.
|
|
if segA.isEmpty() && !segB.isEmpty() {
|
|
return -1
|
|
}
|
|
if !segA.isEmpty() && segB.isEmpty() {
|
|
return 1
|
|
}
|
|
|
|
r := segA.compare(segB)
|
|
if r == 0 {
|
|
continue
|
|
}
|
|
return r
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
func Release(v string) string {
|
|
var s int
|
|
e := len(v)
|
|
var seen bool
|
|
|
|
for i, c := range v {
|
|
if c == '-' && !seen && i > 0 {
|
|
s = i + 1
|
|
seen = true
|
|
} else if c == ' ' && seen {
|
|
e = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if !seen {
|
|
return ""
|
|
}
|
|
|
|
return v[s:e]
|
|
}
|
|
|
|
func version(v string) string {
|
|
var s int
|
|
e := len(v)
|
|
var seenEpoch bool
|
|
|
|
for i, c := range v {
|
|
if c == ' ' && !seenEpoch {
|
|
s = i + 1
|
|
}
|
|
|
|
if c == ':' && !seenEpoch {
|
|
seenEpoch = true
|
|
s = i + 1
|
|
}
|
|
|
|
if c == '-' && i != 0 {
|
|
e = i
|
|
break
|
|
}
|
|
}
|
|
|
|
return v[s:e]
|
|
}
|
|
|
|
func epoch(v string) int {
|
|
var s int
|
|
var e int
|
|
|
|
for i, c := range v {
|
|
if c == ' ' {
|
|
s++
|
|
}
|
|
if c == ':' {
|
|
e = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if e == 0 {
|
|
return 0
|
|
}
|
|
|
|
r, err := strconv.Atoi(v[s:e])
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return r
|
|
}
|