mirror of
https://github.com/prometheus-community/postgres_exporter
synced 2025-04-28 22:18:05 +00:00
This commit implements a massive refactor of the repository, and moves the build system over to use Mage (magefile.org) which should allow seamless building across multiple platforms.
476 lines
11 KiB
Go
476 lines
11 KiB
Go
package rardecode
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"errors"
|
|
"hash"
|
|
"hash/crc32"
|
|
"io"
|
|
"io/ioutil"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
// block types
|
|
block5Arc = 1
|
|
block5File = 2
|
|
block5Service = 3
|
|
block5Encrypt = 4
|
|
block5End = 5
|
|
|
|
// block flags
|
|
block5HasExtra = 0x0001
|
|
block5HasData = 0x0002
|
|
block5DataNotFirst = 0x0008
|
|
block5DataNotLast = 0x0010
|
|
|
|
// end block flags
|
|
endArc5NotLast = 0x0001
|
|
|
|
// archive encryption block flags
|
|
enc5CheckPresent = 0x0001 // password check data is present
|
|
|
|
// main archive block flags
|
|
arc5MultiVol = 0x0001
|
|
arc5Solid = 0x0004
|
|
|
|
// file block flags
|
|
file5IsDir = 0x0001
|
|
file5HasUnixMtime = 0x0002
|
|
file5HasCRC32 = 0x0004
|
|
file5UnpSizeUnknown = 0x0008
|
|
|
|
// file encryption record flags
|
|
file5EncCheckPresent = 0x0001 // password check data is present
|
|
file5EncUseMac = 0x0002 // use MAC instead of plain checksum
|
|
|
|
cacheSize50 = 4
|
|
maxPbkdf2Salt = 64
|
|
pwCheckSize = 8
|
|
maxKdfCount = 24
|
|
|
|
minHeaderSize = 7
|
|
)
|
|
|
|
var (
|
|
errBadPassword = errors.New("rardecode: incorrect password")
|
|
errCorruptEncrypt = errors.New("rardecode: corrupt encryption data")
|
|
errUnknownEncMethod = errors.New("rardecode: unknown encryption method")
|
|
)
|
|
|
|
type extra struct {
|
|
ftype uint64 // field type
|
|
data readBuf // field data
|
|
}
|
|
|
|
type blockHeader50 struct {
|
|
htype uint64 // block type
|
|
flags uint64
|
|
data readBuf // block header data
|
|
extra []extra // extra fields
|
|
dataSize int64 // size of block data
|
|
}
|
|
|
|
// leHash32 wraps a hash.Hash32 to return the result of Sum in little
|
|
// endian format.
|
|
type leHash32 struct {
|
|
hash.Hash32
|
|
}
|
|
|
|
func (h leHash32) Sum(b []byte) []byte {
|
|
s := h.Sum32()
|
|
return append(b, byte(s), byte(s>>8), byte(s>>16), byte(s>>24))
|
|
}
|
|
|
|
func newLittleEndianCRC32() hash.Hash32 {
|
|
return leHash32{crc32.NewIEEE()}
|
|
}
|
|
|
|
// hash50 implements fileChecksum for RAR 5 archives
|
|
type hash50 struct {
|
|
hash.Hash // hash file data is written to
|
|
sum []byte // file checksum
|
|
key []byte // if present used with hmac in calculating checksum from hash
|
|
}
|
|
|
|
func (h *hash50) valid() bool {
|
|
sum := h.Sum(nil)
|
|
if len(h.key) > 0 {
|
|
mac := hmac.New(sha256.New, h.key)
|
|
mac.Write(sum)
|
|
sum = mac.Sum(sum[:0])
|
|
if len(h.sum) == 4 {
|
|
// CRC32
|
|
for i, v := range sum[4:] {
|
|
sum[i&3] ^= v
|
|
}
|
|
sum = sum[:4]
|
|
}
|
|
}
|
|
return bytes.Equal(sum, h.sum)
|
|
}
|
|
|
|
// archive50 implements fileBlockReader for RAR 5 file format archives
|
|
type archive50 struct {
|
|
byteReader // reader for current block data
|
|
v *bufio.Reader // reader for current archive volume
|
|
pass []byte
|
|
blockKey []byte // key used to encrypt blocks
|
|
multi bool // archive is multi-volume
|
|
solid bool // is a solid archive
|
|
checksum hash50 // file checksum
|
|
dec decoder // optional decoder used to unpack file
|
|
buf readBuf // temporary buffer
|
|
keyCache [cacheSize50]struct { // encryption key cache
|
|
kdfCount int
|
|
salt []byte
|
|
keys [][]byte
|
|
}
|
|
}
|
|
|
|
// calcKeys50 calculates the keys used in RAR 5 archive processing.
|
|
// The returned slice of byte slices contains 3 keys.
|
|
// Key 0 is used for block or file decryption.
|
|
// Key 1 is optionally used for file checksum calculation.
|
|
// Key 2 is optionally used for password checking.
|
|
func calcKeys50(pass, salt []byte, kdfCount int) [][]byte {
|
|
if len(salt) > maxPbkdf2Salt {
|
|
salt = salt[:maxPbkdf2Salt]
|
|
}
|
|
keys := make([][]byte, 3)
|
|
if len(keys) == 0 {
|
|
return keys
|
|
}
|
|
|
|
prf := hmac.New(sha256.New, pass)
|
|
prf.Write(salt)
|
|
prf.Write([]byte{0, 0, 0, 1})
|
|
|
|
t := prf.Sum(nil)
|
|
u := append([]byte(nil), t...)
|
|
|
|
kdfCount--
|
|
|
|
for i, iter := range []int{kdfCount, 16, 16} {
|
|
for iter > 0 {
|
|
prf.Reset()
|
|
prf.Write(u)
|
|
u = prf.Sum(u[:0])
|
|
for j := range u {
|
|
t[j] ^= u[j]
|
|
}
|
|
iter--
|
|
}
|
|
keys[i] = append([]byte(nil), t...)
|
|
}
|
|
|
|
pwcheck := keys[2]
|
|
for i, v := range pwcheck[pwCheckSize:] {
|
|
pwcheck[i&(pwCheckSize-1)] ^= v
|
|
}
|
|
keys[2] = pwcheck[:pwCheckSize]
|
|
|
|
return keys
|
|
}
|
|
|
|
// getKeys reads kdfcount and salt from b and returns the corresponding encryption keys.
|
|
func (a *archive50) getKeys(b *readBuf) (keys [][]byte, err error) {
|
|
if len(*b) < 17 {
|
|
return nil, errCorruptEncrypt
|
|
}
|
|
// read kdf count and salt
|
|
kdfCount := int(b.byte())
|
|
if kdfCount > maxKdfCount {
|
|
return nil, errCorruptEncrypt
|
|
}
|
|
kdfCount = 1 << uint(kdfCount)
|
|
salt := b.bytes(16)
|
|
|
|
// check cache of keys for match
|
|
for _, v := range a.keyCache {
|
|
if kdfCount == v.kdfCount && bytes.Equal(salt, v.salt) {
|
|
return v.keys, nil
|
|
}
|
|
}
|
|
// not found, calculate keys
|
|
keys = calcKeys50(a.pass, salt, kdfCount)
|
|
|
|
// store in cache
|
|
copy(a.keyCache[1:], a.keyCache[:])
|
|
a.keyCache[0].kdfCount = kdfCount
|
|
a.keyCache[0].salt = append([]byte(nil), salt...)
|
|
a.keyCache[0].keys = keys
|
|
|
|
return keys, nil
|
|
}
|
|
|
|
// checkPassword calculates if a password is correct given password check data and keys.
|
|
func checkPassword(b *readBuf, keys [][]byte) error {
|
|
if len(*b) < 12 {
|
|
return nil // not enough bytes, ignore for the moment
|
|
}
|
|
pwcheck := b.bytes(8)
|
|
sum := b.bytes(4)
|
|
csum := sha256.Sum256(pwcheck)
|
|
if bytes.Equal(sum, csum[:len(sum)]) && !bytes.Equal(pwcheck, keys[2]) {
|
|
return errBadPassword
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// parseFileEncryptionRecord processes the optional file encryption record from a file header.
|
|
func (a *archive50) parseFileEncryptionRecord(b readBuf, f *fileBlockHeader) error {
|
|
if ver := b.uvarint(); ver != 0 {
|
|
return errUnknownEncMethod
|
|
}
|
|
flags := b.uvarint()
|
|
|
|
keys, err := a.getKeys(&b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
f.key = keys[0]
|
|
if len(b) < 16 {
|
|
return errCorruptEncrypt
|
|
}
|
|
f.iv = b.bytes(16)
|
|
|
|
if flags&file5EncCheckPresent > 0 {
|
|
if err := checkPassword(&b, keys); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if flags&file5EncUseMac > 0 {
|
|
a.checksum.key = keys[1]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *archive50) parseFileHeader(h *blockHeader50) (*fileBlockHeader, error) {
|
|
a.checksum.sum = nil
|
|
a.checksum.key = nil
|
|
|
|
f := new(fileBlockHeader)
|
|
|
|
f.first = h.flags&block5DataNotFirst == 0
|
|
f.last = h.flags&block5DataNotLast == 0
|
|
|
|
flags := h.data.uvarint() // file flags
|
|
f.IsDir = flags&file5IsDir > 0
|
|
f.UnKnownSize = flags&file5UnpSizeUnknown > 0
|
|
f.UnPackedSize = int64(h.data.uvarint())
|
|
f.PackedSize = h.dataSize
|
|
f.Attributes = int64(h.data.uvarint())
|
|
if flags&file5HasUnixMtime > 0 {
|
|
if len(h.data) < 4 {
|
|
return nil, errCorruptFileHeader
|
|
}
|
|
f.ModificationTime = time.Unix(int64(h.data.uint32()), 0)
|
|
}
|
|
if flags&file5HasCRC32 > 0 {
|
|
if len(h.data) < 4 {
|
|
return nil, errCorruptFileHeader
|
|
}
|
|
a.checksum.sum = append([]byte(nil), h.data.bytes(4)...)
|
|
if f.first {
|
|
a.checksum.Hash = newLittleEndianCRC32()
|
|
f.cksum = &a.checksum
|
|
}
|
|
}
|
|
|
|
flags = h.data.uvarint() // compression flags
|
|
f.solid = flags&0x0040 > 0
|
|
f.winSize = uint(flags&0x3C00)>>10 + 17
|
|
method := (flags >> 7) & 7 // compression method (0 == none)
|
|
if f.first && method != 0 {
|
|
unpackver := flags & 0x003f
|
|
if unpackver != 0 {
|
|
return nil, errUnknownDecoder
|
|
}
|
|
if a.dec == nil {
|
|
a.dec = new(decoder50)
|
|
}
|
|
f.decoder = a.dec
|
|
}
|
|
switch h.data.uvarint() {
|
|
case 0:
|
|
f.HostOS = HostOSWindows
|
|
case 1:
|
|
f.HostOS = HostOSUnix
|
|
default:
|
|
f.HostOS = HostOSUnknown
|
|
}
|
|
nlen := int(h.data.uvarint())
|
|
if len(h.data) < nlen {
|
|
return nil, errCorruptFileHeader
|
|
}
|
|
f.Name = string(h.data.bytes(nlen))
|
|
|
|
// parse optional extra records
|
|
for _, e := range h.extra {
|
|
var err error
|
|
switch e.ftype {
|
|
case 1: // encryption
|
|
err = a.parseFileEncryptionRecord(e.data, f)
|
|
case 2:
|
|
// TODO: hash
|
|
case 3:
|
|
// TODO: time
|
|
case 4: // version
|
|
_ = e.data.uvarint() // ignore flags field
|
|
f.Version = int(e.data.uvarint())
|
|
case 5:
|
|
// TODO: redirection
|
|
case 6:
|
|
// TODO: owner
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
// parseEncryptionBlock calculates the key for block encryption.
|
|
func (a *archive50) parseEncryptionBlock(b readBuf) error {
|
|
if ver := b.uvarint(); ver != 0 {
|
|
return errUnknownEncMethod
|
|
}
|
|
flags := b.uvarint()
|
|
keys, err := a.getKeys(&b)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if flags&enc5CheckPresent > 0 {
|
|
if err := checkPassword(&b, keys); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
a.blockKey = keys[0]
|
|
return nil
|
|
}
|
|
|
|
func (a *archive50) readBlockHeader() (*blockHeader50, error) {
|
|
r := io.Reader(a.v)
|
|
if a.blockKey != nil {
|
|
// block is encrypted
|
|
iv := a.buf[:16]
|
|
if err := readFull(r, iv); err != nil {
|
|
return nil, err
|
|
}
|
|
r = newAesDecryptReader(r, a.blockKey, iv)
|
|
}
|
|
|
|
b := a.buf[:minHeaderSize]
|
|
if err := readFull(r, b); err != nil {
|
|
return nil, err
|
|
}
|
|
crc := b.uint32()
|
|
|
|
hash := crc32.NewIEEE()
|
|
hash.Write(b)
|
|
|
|
size := int(b.uvarint()) // header size
|
|
if size > cap(a.buf) {
|
|
a.buf = readBuf(make([]byte, size))
|
|
} else {
|
|
a.buf = a.buf[:size]
|
|
}
|
|
n := copy(a.buf, b) // copy left over bytes
|
|
if err := readFull(r, a.buf[n:]); err != nil { // read rest of header
|
|
return nil, err
|
|
}
|
|
|
|
// check header crc
|
|
hash.Write(a.buf[n:])
|
|
if crc != hash.Sum32() {
|
|
return nil, errBadHeaderCrc
|
|
}
|
|
|
|
b = a.buf
|
|
h := new(blockHeader50)
|
|
h.htype = b.uvarint()
|
|
h.flags = b.uvarint()
|
|
|
|
var extraSize int
|
|
if h.flags&block5HasExtra > 0 {
|
|
extraSize = int(b.uvarint())
|
|
}
|
|
if h.flags&block5HasData > 0 {
|
|
h.dataSize = int64(b.uvarint())
|
|
}
|
|
if len(b) < extraSize {
|
|
return nil, errCorruptHeader
|
|
}
|
|
h.data = b.bytes(len(b) - extraSize)
|
|
|
|
// read header extra records
|
|
for len(b) > 0 {
|
|
size = int(b.uvarint())
|
|
if len(b) < size {
|
|
return nil, errCorruptHeader
|
|
}
|
|
data := readBuf(b.bytes(size))
|
|
ftype := data.uvarint()
|
|
h.extra = append(h.extra, extra{ftype, data})
|
|
}
|
|
|
|
return h, nil
|
|
}
|
|
|
|
// next advances to the next file block in the archive
|
|
func (a *archive50) next() (*fileBlockHeader, error) {
|
|
for {
|
|
h, err := a.readBlockHeader()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
a.byteReader = limitByteReader(a.v, h.dataSize)
|
|
switch h.htype {
|
|
case block5File:
|
|
return a.parseFileHeader(h)
|
|
case block5Arc:
|
|
flags := h.data.uvarint()
|
|
a.multi = flags&arc5MultiVol > 0
|
|
a.solid = flags&arc5Solid > 0
|
|
case block5Encrypt:
|
|
err = a.parseEncryptionBlock(h.data)
|
|
case block5End:
|
|
flags := h.data.uvarint()
|
|
if flags&endArc5NotLast == 0 || !a.multi {
|
|
return nil, errArchiveEnd
|
|
}
|
|
return nil, errArchiveContinues
|
|
default:
|
|
// discard block data
|
|
_, err = io.Copy(ioutil.Discard, a.byteReader)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
func (a *archive50) version() int { return fileFmt50 }
|
|
|
|
func (a *archive50) reset() {
|
|
a.blockKey = nil // reset encryption when opening new volume file
|
|
}
|
|
|
|
func (a *archive50) isSolid() bool {
|
|
return a.solid
|
|
}
|
|
|
|
// newArchive50 creates a new fileBlockReader for a Version 5 archive.
|
|
func newArchive50(r *bufio.Reader, password string) fileBlockReader {
|
|
a := new(archive50)
|
|
a.v = r
|
|
a.pass = []byte(password)
|
|
a.buf = make([]byte, 100)
|
|
return a
|
|
}
|