Created
May 30, 2020 11:33
-
-
Save geekman/9dde45cb8e9d8dc48c7b1dfad09bde06 to your computer and use it in GitHub Desktop.
RPM file dumper, adapted from https://github.com/holocm/holo-build
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/******************************************************************************* | |
* | |
* RPM File Dumper | |
* Adapted to run as a self-contained binary from Holo: | |
* https://github.com/holocm/holo-build | |
* | |
* 2020.05.30 darell tan | |
* | |
* Copyright 2015 Stefan Majewsky <majewsky@gmx.net> | |
* | |
* This file is part of Holo. | |
* | |
* Holo 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. | |
* | |
* Holo 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 | |
* Holo. If not, see <http://www.gnu.org/licenses/>. | |
* | |
*******************************************************************************/ | |
package main | |
import ( | |
"bytes" | |
"encoding/binary" | |
"encoding/hex" | |
"fmt" | |
"io" | |
"io/ioutil" | |
"os" | |
"sort" | |
"strings" | |
) | |
func main() { | |
data, err := ioutil.ReadFile(os.Args[1]) | |
if err != nil { | |
panic(err) | |
} | |
fmt.Println(DumpRpm(data, true)) | |
} | |
func Indent(s string) string { return s } | |
//DumpRpm dumps RPM packages. | |
func DumpRpm(data []byte, withChecksums bool) (string, error) { | |
//We don't have a library for the RPM format, and unfortunately, it's an utter mess. | |
//The main reference that I used (apart from sample RPMs from Fedora, Mageia, and Suse) | |
//is <http://www.rpm.org/max-rpm/s1-rpm-file-format-rpm-file-format.html> and | |
//<https://docs.fedoraproject.org/ro/Fedora_Draft_Documentation/0.1/html/RPM_Guide/ch-package-structure.html>. | |
reader := bytes.NewReader(data) | |
//decode the various header structures | |
leadDump, err := dumpRpmLead(reader) | |
if err != nil { | |
return "", err | |
} | |
signatureDump, err := dumpRpmHeader(reader, "signature", true, rpmtagDictForSignatureHeader) | |
if err != nil { | |
return "", err | |
} | |
headerDump, err := dumpRpmHeader(reader, "header", false, rpmtagDictForMetadataHeader) | |
if err != nil { | |
return "", err | |
} | |
//decode payload | |
payloadDump := "" | |
//payloadData, err := ioutil.ReadAll(reader) | |
//if err != nil { | |
// return "", err | |
//} | |
//payloadDump, err := RecognizeAndDump(payloadData, withChecksums) | |
//if err != nil { | |
// return "", err | |
//} | |
return "RPM package\n" + Indent(leadDump) + Indent(signatureDump) + Indent(headerDump) + Indent(">> payload: "+payloadDump), nil | |
} | |
func dumpRpmLead(reader io.Reader) (string, error) { | |
//read the lead (the initial fixed-size header) | |
var lead struct { | |
Magic uint32 | |
MajorVersion uint8 | |
MinorVersion uint8 | |
Type uint16 | |
Architecture uint16 | |
Name [66]byte | |
OSNum uint16 | |
SignatureType uint16 | |
Reserved [16]byte | |
} | |
err := binary.Read(reader, binary.BigEndian, &lead) | |
if err != nil { | |
return "", err | |
} | |
lines := []string{ | |
fmt.Sprintf("RPM format version %d.%d", lead.MajorVersion, lead.MinorVersion), | |
fmt.Sprintf("Type: %d (0 = binary, 1 = source)", lead.Type), | |
fmt.Sprintf("Architecture: %d (0 = noarch, 1 = x86 (also x86-64), 2 = Alpha, 3 = Sparc, 4 = MIPS, 5 = PPC, ..., 9 = IA-64, 12 = ARM, ...)", lead.Architecture), | |
//lead.Name is a NUL-terminated (and NUL-padded) string; trim all the NULs at the end | |
fmt.Sprintf("Name: %s", strings.TrimRight(string(lead.Name[:]), "\x00")), | |
fmt.Sprintf("Built for OS: %d (1 = Linux, ...)", lead.OSNum), | |
fmt.Sprintf("Signature type: %d", lead.SignatureType), | |
} | |
return ">> lead section:\n" + Indent(strings.Join(lines, "\n")), nil | |
} | |
//IndexEntry represents an entry in the index of an RPM header. | |
type IndexEntry struct { | |
Tag uint32 //defines the semantics of the value in this field | |
Type uint32 //data type | |
Offset uint32 //relative to the beginning of the store | |
Count uint32 //number of data items in this field | |
} | |
func dumpRpmHeader(reader io.Reader, sectionIdent string, readAligned bool, tagDict map[uint32]string) (string, error) { | |
//the header has a header (I'm So Meta, Even This Acronym) | |
var header struct { | |
Magic [3]byte | |
Version uint8 | |
Reserved [4]byte | |
EntryCount uint32 //supports 4 billion header entries... Now that's planning ahead! :) | |
DataSize uint32 //size of the store (i.e. the data section, everything after the index until the end of the header section) | |
} | |
err := binary.Read(reader, binary.BigEndian, &header) | |
if err != nil { | |
return "", err | |
} | |
if header.Magic != [3]byte{0x8e, 0xad, 0xe8} { | |
return "", fmt.Errorf( | |
"did not find RPM header structure header at expected position (saw 0x%s instead of 0x8eade8)", | |
hex.EncodeToString(header.Magic[:]), | |
) | |
} | |
identifier := fmt.Sprintf(">> %s section: format version %d, %d entries, %d bytes of data\n", | |
sectionIdent, header.Version, header.EntryCount, header.DataSize, | |
) | |
//read index of fields | |
indexEntries := make([]IndexEntry, 0, header.EntryCount) | |
for idx := uint32(0); idx < header.EntryCount; idx++ { | |
var entry IndexEntry | |
err := binary.Read(reader, binary.BigEndian, &entry) | |
if err != nil { | |
return "", err | |
} | |
indexEntries = append(indexEntries, entry) | |
} | |
//read remaining part of header (the data store) into a buffer for random access | |
buffer := make([]byte, header.DataSize) | |
_, err = io.ReadFull(reader, buffer) | |
if err != nil { | |
return "", err | |
} | |
bufferedReader := bytes.NewReader(buffer) | |
if readAligned { | |
//next structure in reader is aligned to 8-byte boundary -- skip over padding | |
modulo := header.DataSize % 8 | |
if modulo != 0 { | |
_, err = io.ReadFull(reader, make([]byte, 8-modulo)) | |
if err != nil { | |
return "", err | |
} | |
} | |
} | |
//decode and dump entries | |
dumpForTag := make(map[uint32]string) | |
tags := make([]uint32, 0, len(indexEntries)) | |
for _, entry := range indexEntries { | |
//seek to start of entry | |
_, err := bufferedReader.Seek(int64(entry.Offset), 0) | |
if err != nil { | |
return "", err | |
} | |
var sublines []string | |
if entry.Type == 7 { | |
//for entry.Type = 7 (BIN), entry.Count is the number of bytes to be read | |
data := make([]byte, entry.Count) | |
_, err = io.ReadFull(bufferedReader, data) | |
if err != nil { | |
return "", err | |
} | |
sublines = []string{hex.Dump(data)} | |
} else { | |
//for all other types, entry.Count tells the number of records to read | |
sublines = make([]string, 0, entry.Count) | |
for idx := uint32(0); idx < entry.Count; idx++ { | |
repr, err := decodeIndexEntry(entry.Type, bufferedReader) | |
if err != nil { | |
return "", err | |
} | |
sublines = append(sublines, repr) | |
} | |
} | |
//identify entry by looking up the tag name | |
tagName, isKnownTag := tagDict[entry.Tag] | |
if isKnownTag { | |
tagName = fmt.Sprintf("tag %d (%s)", entry.Tag, tagName) | |
} else { | |
tagName = fmt.Sprintf("tag %d", entry.Tag) | |
} | |
dump := fmt.Sprintf("%s: length %d", tagName, entry.Count) | |
if len(sublines) > 0 { | |
dump += "\n" + strings.TrimSuffix(Indent(strings.Join(sublines, "\n")), "\n") | |
} | |
//the same tag should not appear multiple times; but if it does, report | |
//both instead of overwriting the first occurrence with the second one | |
if _, duplicateTag := dumpForTag[entry.Tag]; duplicateTag { | |
dumpForTag[entry.Tag] += "\n" + dump | |
} else { | |
dumpForTag[entry.Tag] = dump | |
tags = append(tags, entry.Tag) | |
} | |
} | |
//sort entry dumps by tag value, because order should not matter | |
sort.Sort(byUint32(tags)) | |
lines := make([]string, 0, len(tags)) | |
for _, tag := range tags { | |
lines = append(lines, dumpForTag[tag]) | |
} | |
return identifier + Indent(strings.Join(lines, "\n")), nil | |
} | |
type byUint32 []uint32 | |
func (b byUint32) Len() int { return len(b) } | |
func (b byUint32) Less(i, j int) bool { return b[i] < b[j] } | |
func (b byUint32) Swap(i, j int) { b[i], b[j] = b[j], b[i] } | |
func decodeIndexEntry(dataType uint32, reader io.Reader) (string, error) { | |
//check data type | |
switch dataType { | |
case 0: //NULL | |
return "null", nil | |
case 1: //CHAR | |
var value uint8 | |
err := binary.Read(reader, binary.BigEndian, &value) | |
return fmt.Sprintf("char: %c = 0x%X = 0o%o", rune(value), value, value), err | |
case 2: //INT8 | |
var value int8 | |
err := binary.Read(reader, binary.BigEndian, &value) | |
return fmt.Sprintf("int8: %d = 0x%X = 0o%o", value, uint8(value), uint8(value)), err | |
case 3: //INT16 | |
var value int16 | |
err := binary.Read(reader, binary.BigEndian, &value) | |
return fmt.Sprintf("int16: %d = 0x%X = 0o%o", value, uint16(value), uint16(value)), err | |
case 4: //INT32 | |
var value int32 | |
err := binary.Read(reader, binary.BigEndian, &value) | |
return fmt.Sprintf("int32: %d = 0x%X = 0o%o", value, uint32(value), uint32(value)), err | |
case 5: //INT64 | |
var value int64 | |
err := binary.Read(reader, binary.BigEndian, &value) | |
return fmt.Sprintf("int64: %d = 0x%X = 0o%o", value, uint64(value), uint64(value)), err | |
case 7: //BIN | |
panic("Cannot be reached") | |
case 6, 8: //STRING or STRING_ARRAY or I18NSTRING (not different at this point) | |
str, err := readNulTerminatedString(reader) | |
return fmt.Sprintf("string: %s", str), err | |
case 9: //I18N_STRING | |
str, err := readNulTerminatedString(reader) | |
return fmt.Sprintf("translatable string: %s", str), err | |
default: | |
return fmt.Sprintf("don't know how to decode data type %d", dataType), nil | |
} | |
} | |
func readNulTerminatedString(reader io.Reader) (string, error) { | |
//read NUL-terminated string (byte-wise) | |
var result []byte | |
buffer := make([]byte, 1) | |
for { | |
_, err := reader.Read(buffer) | |
if err != nil { | |
return "", err | |
} | |
if buffer[0] == 0 { | |
break | |
} else { | |
result = append(result, buffer[0]) | |
} | |
} | |
return string(result), nil | |
} | |
//////////////////////////////////////////////////////////////////////////////// | |
// mappings of tag ID -> tag name (as extracted from /usr/include/rpm/rpmtag.h) | |
var rpmtagDictForSignatureHeader = map[uint32]string{ | |
62: "HEADERSIGNATURES", | |
1000: "SIZE", | |
1001: "LEMD5_1", | |
1002: "PGP", | |
1003: "LEMD5_2", | |
1004: "MD5", | |
1005: "GPG", | |
1006: "PGP5", | |
1007: "PAYLOADSIZE", | |
1008: "RESERVEDSPACE", | |
264: "BADSHA1_1", | |
265: "BADSHA1_2", | |
267: "DSA", | |
268: "RSA", | |
269: "SHA1", | |
270: "LONGSIZE", | |
271: "LONGARCHIVESIZE", | |
} | |
var rpmtagDictForMetadataHeader = map[uint32]string{ | |
63: "HEADERIMMUTABLE", | |
100: "HEADERI18NTABLE", | |
1000: "NAME", | |
1001: "VERSION", | |
1002: "RELEASE", | |
1003: "EPOCH", | |
1004: "SUMMARY", | |
1005: "DESCRIPTION", | |
1006: "BUILDTIME", | |
1007: "BUILDHOST", | |
1008: "INSTALLTIME", | |
1009: "SIZE", | |
1010: "DISTRIBUTION", | |
1011: "VENDOR", | |
1012: "GIF", | |
1013: "XPM", | |
1014: "LICENSE", | |
1015: "PACKAGER", | |
1016: "GROUP", | |
1017: "CHANGELOG", | |
1018: "SOURCE", | |
1019: "PATCH", | |
1020: "URL", | |
1021: "OS", | |
1022: "ARCH", | |
1023: "PREIN", | |
1024: "POSTIN", | |
1025: "PREUN", | |
1026: "POSTUN", | |
1027: "OLDFILENAMES", | |
1028: "FILESIZES", | |
1029: "FILESTATES", | |
1030: "FILEMODES", | |
1031: "FILEUIDS", | |
1032: "FILEGIDS", | |
1033: "FILERDEVS", | |
1034: "FILEMTIMES", | |
1035: "FILEMD5S", | |
1036: "FILELINKTOS", | |
1037: "FILEFLAGS", | |
1038: "ROOT", | |
1039: "FILEUSERNAME", | |
1040: "FILEGROUPNAME", | |
1041: "EXCLUDE", | |
1042: "EXCLUSIVE", | |
1043: "ICON", | |
1044: "SOURCERPM", | |
1045: "FILEVERIFYFLAGS", | |
1046: "ARCHIVESIZE", | |
1047: "PROVIDENAME", | |
1048: "REQUIREFLAGS", | |
1049: "REQUIRENAME", | |
1050: "REQUIREVERSION", | |
1051: "NOSOURCE", | |
1052: "NOPATCH", | |
1053: "CONFLICTFLAGS", | |
1054: "CONFLICTNAME", | |
1055: "CONFLICTVERSION", | |
1056: "DEFAULTPREFIX", | |
1057: "BUILDROOT", | |
1058: "INSTALLPREFIX", | |
1059: "EXCLUDEARCH", | |
1060: "EXCLUDEOS", | |
1061: "EXCLUSIVEARCH", | |
1062: "EXCLUSIVEOS", | |
1063: "AUTOREQPROV", | |
1064: "RPMVERSION", | |
1065: "TRIGGERSCRIPTS", | |
1066: "TRIGGERNAME", | |
1067: "TRIGGERVERSION", | |
1068: "TRIGGERFLAGS", | |
1069: "TRIGGERINDEX", | |
1079: "VERIFYSCRIPT", | |
1080: "CHANGELOGTIME", | |
1081: "CHANGELOGNAME", | |
1082: "CHANGELOGTEXT", | |
1083: "BROKENMD5", | |
1084: "PREREQ", | |
1085: "PREINPROG", | |
1086: "POSTINPROG", | |
1087: "PREUNPROG", | |
1088: "POSTUNPROG", | |
1089: "BUILDARCHS", | |
1090: "OBSOLETENAME", | |
1091: "VERIFYSCRIPTPROG", | |
1092: "TRIGGERSCRIPTPROG", | |
1093: "DOCDIR", | |
1094: "COOKIE", | |
1095: "FILEDEVICES", | |
1096: "FILEINODES", | |
1097: "FILELANGS", | |
1098: "PREFIXES", | |
1099: "INSTPREFIXES", | |
1100: "TRIGGERIN", | |
1101: "TRIGGERUN", | |
1102: "TRIGGERPOSTUN", | |
1103: "AUTOREQ", | |
1104: "AUTOPROV", | |
1105: "CAPABILITY", | |
1106: "SOURCEPACKAGE", | |
1107: "OLDORIGFILENAMES", | |
1108: "BUILDPREREQ", | |
1109: "BUILDREQUIRES", | |
1110: "BUILDCONFLICTS", | |
1111: "BUILDMACROS", | |
1112: "PROVIDEFLAGS", | |
1113: "PROVIDEVERSION", | |
1114: "OBSOLETEFLAGS", | |
1115: "OBSOLETEVERSION", | |
1116: "DIRINDEXES", | |
1117: "BASENAMES", | |
1118: "DIRNAMES", | |
1119: "ORIGDIRINDEXES", | |
1120: "ORIGBASENAMES", | |
1121: "ORIGDIRNAMES", | |
1122: "OPTFLAGS", | |
1123: "DISTURL", | |
1124: "PAYLOADFORMAT", | |
1125: "PAYLOADCOMPRESSOR", | |
1126: "PAYLOADFLAGS", | |
1127: "INSTALLCOLOR", | |
1128: "INSTALLTID", | |
1129: "REMOVETID", | |
1130: "SHA1RHN", | |
1131: "RHNPLATFORM", | |
1132: "PLATFORM", | |
1133: "PATCHESNAME", | |
1134: "PATCHESFLAGS", | |
1135: "PATCHESVERSION", | |
1136: "CACHECTIME", | |
1137: "CACHEPKGPATH", | |
1138: "CACHEPKGSIZE", | |
1139: "CACHEPKGMTIME", | |
1140: "FILECOLORS", | |
1141: "FILECLASS", | |
1142: "CLASSDICT", | |
1143: "FILEDEPENDSX", | |
1144: "FILEDEPENDSN", | |
1145: "DEPENDSDICT", | |
1146: "SOURCEPKGID", | |
1147: "FILECONTEXTS", | |
1148: "FSCONTEXTS", | |
1149: "RECONTEXTS", | |
1150: "POLICIES", | |
1151: "PRETRANS", | |
1152: "POSTTRANS", | |
1153: "PRETRANSPROG", | |
1154: "POSTTRANSPROG", | |
1155: "DISTTAG", | |
1156: "OLDSUGGESTSNAME", | |
1157: "OLDSUGGESTSVERSION", | |
1158: "OLDSUGGESTSFLAGS", | |
1159: "OLDENHANCESNAME", | |
1160: "OLDENHANCESVERSION", | |
1161: "OLDENHANCESFLAGS", | |
1162: "PRIORITY", | |
1163: "CVSID", | |
1164: "BLINKPKGID", | |
1165: "BLINKHDRID", | |
1166: "BLINKNEVRA", | |
1167: "FLINKPKGID", | |
1168: "FLINKHDRID", | |
1169: "FLINKNEVRA", | |
1170: "PACKAGEORIGIN", | |
1171: "TRIGGERPREIN", | |
1172: "BUILDSUGGESTS", | |
1173: "BUILDENHANCES", | |
1174: "SCRIPTSTATES", | |
1175: "SCRIPTMETRICS", | |
1176: "BUILDCPUCLOCK", | |
1177: "FILEDIGESTALGOS", | |
1178: "VARIANTS", | |
1179: "XMAJOR", | |
1180: "XMINOR", | |
1181: "REPOTAG", | |
1182: "KEYWORDS", | |
1183: "BUILDPLATFORMS", | |
1184: "PACKAGECOLOR", | |
1185: "PACKAGEPREFCOLOR", | |
1186: "XATTRSDICT", | |
1187: "FILEXATTRSX", | |
1188: "DEPATTRSDICT", | |
1189: "CONFLICTATTRSX", | |
1190: "OBSOLETEATTRSX", | |
1191: "PROVIDEATTRSX", | |
1192: "REQUIREATTRSX", | |
1193: "BUILDPROVIDES", | |
1194: "BUILDOBSOLETES", | |
1195: "DBINSTANCE", | |
1196: "NVRA", | |
5000: "FILENAMES", | |
5001: "FILEPROVIDE", | |
5002: "FILEREQUIRE", | |
5003: "FSNAMES", | |
5004: "FSSIZES", | |
5005: "TRIGGERCONDS", | |
5006: "TRIGGERTYPE", | |
5007: "ORIGFILENAMES", | |
5008: "LONGFILESIZES", | |
5009: "LONGSIZE", | |
5010: "FILECAPS", | |
5011: "FILEDIGESTALGO", | |
5012: "BUGURL", | |
5013: "EVR", | |
5014: "NVR", | |
5015: "NEVR", | |
5016: "NEVRA", | |
5017: "HEADERCOLOR", | |
5018: "VERBOSE", | |
5019: "EPOCHNUM", | |
5020: "PREINFLAGS", | |
5021: "POSTINFLAGS", | |
5022: "PREUNFLAGS", | |
5023: "POSTUNFLAGS", | |
5024: "PRETRANSFLAGS", | |
5025: "POSTTRANSFLAGS", | |
5026: "VERIFYSCRIPTFLAGS", | |
5027: "TRIGGERSCRIPTFLAGS", | |
5029: "COLLECTIONS", | |
5030: "POLICYNAMES", | |
5031: "POLICYTYPES", | |
5032: "POLICYTYPESINDEXES", | |
5033: "POLICYFLAGS", | |
5034: "VCS", | |
5035: "ORDERNAME", | |
5036: "ORDERVERSION", | |
5037: "ORDERFLAGS", | |
5038: "MSSFMANIFEST", | |
5039: "MSSFDOMAIN", | |
5040: "INSTFILENAMES", | |
5041: "REQUIRENEVRS", | |
5042: "PROVIDENEVRS", | |
5043: "OBSOLETENEVRS", | |
5044: "CONFLICTNEVRS", | |
5045: "FILENLINKS", | |
5046: "RECOMMENDNAME", | |
5047: "RECOMMENDVERSION", | |
5048: "RECOMMENDFLAGS", | |
5049: "SUGGESTNAME", | |
5050: "SUGGESTVERSION", | |
5051: "SUGGESTFLAGS", | |
5052: "SUPPLEMENTNAME", | |
5053: "SUPPLEMENTVERSION", | |
5054: "SUPPLEMENTFLAGS", | |
5055: "ENHANCENAME", | |
5056: "ENHANCEVERSION", | |
5057: "ENHANCEFLAGS", | |
5058: "RECOMMENDNEVRS", | |
5059: "SUGGESTNEVRS", | |
5060: "SUPPLEMENTNEVRS", | |
5061: "ENHANCENEVRS", | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment