diff --git a/rpmust/.cargo/config b/rpmust/.cargo/config new file mode 100644 index 0000000000000000000000000000000000000000..99e37380895b92500024b41076cb22276badde5a --- /dev/null +++ b/rpmust/.cargo/config @@ -0,0 +1,5 @@ +[source.crates-io] +registry = "https://github.com/rust-lang/crates.io-index" +replace-with = 'ustc' +[source.ustc] +registry = "git://mirrors.ustc.edu.cn/crates.io-index" \ No newline at end of file diff --git a/rpmust/Cargo.toml b/rpmust/Cargo.toml index fe3799369eed31b883b1d599c14e873cc26e5778..d03d1767bffb186197905a7617c575a42dd2a42c 100644 --- a/rpmust/Cargo.toml +++ b/rpmust/Cargo.toml @@ -6,3 +6,24 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +tokio = {version = "1.11", features = ["fs", "io-util"]} +thiserror = "1" +nom = "7" +num-traits = "0.2" +num-derive = "0.3" +num = "0.4" +enum-primitive-derive = "0.2" +enum-display-derive = "0.1" +cpio = "0.2" +# consider migrating to flate2 +libflate = "1" +sha2 = "0.9" +md-5 = "0.9" +sha1 = "0.6" +rand = { version = "0.8" } +pgp = { version="0.7.2", optional = true } +chrono = "0.4" +log = "0.4" +itertools = "0.10" +hex = { version = "0.4", features = ["std"] } +zstd = "0.9.0" \ No newline at end of file diff --git a/rpmust/src/main.rs b/rpmust/src/main.rs index e7a11a969c037e00a796aafeff6258501ec15e9a..93ae3f7b7fdab30057693cd90aa0d71d1a7e077d 100644 --- a/rpmust/src/main.rs +++ b/rpmust/src/main.rs @@ -1,3 +1,25 @@ -fn main() { - println!("Hello, world!"); +use std::io; +use std::io::prelude::*; +use std::fs::File; +use std::env::*; +pub mod rpm; +use rpm::*; + +fn main() -> io::Result<()> { + let mut file = std::fs::File::open("./test/stratovirt.rpm").expect("should be able to open rpm file"); + let file_size = file.metadata().unwrap().len(); + println!("{}",file_size); + let mut buf_reader = std::io::BufReader::with_capacity(file_size as usize,file); + let rpmmeta = RPMPackageMetadata::parse(&mut buf_reader); + let rpm = rpmmeta.unwrap(); + + //file.read_to_end(& mut buf_reader); + println!("{:#?}",rpm.signature.index_entries); + println!("{:#?}",rpm.header.index_entries); + + + let mut out_file = File::create("out.cpio")?; + + out_file.write_all(buf_reader.fill_buf().unwrap()); + Ok(()) } diff --git a/rpmust/src/rpm/headers/constants.rs b/rpmust/src/rpm/headers/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..063cec187b4fa78987a0e29bf9d929b2edf8e6b2 --- /dev/null +++ b/rpmust/src/rpm/headers/constants.rs @@ -0,0 +1,498 @@ +//! RPM specific constants +//! +//! These constants were extracted from the rpm upstream project +//! C headers. + +use std::fmt::Display; + +pub const HEADER_IMAGE: isize = 61; +pub const HEADER_SIGNATURES: isize = 62; +pub const HEADER_IMMUTABLE: isize = 63; +pub const HEADER_REGIONS: isize = 64; +pub const HEADER_I18NTABLE: isize = 100; +pub const HEADER_SIGBASE: isize = 256; +pub const HEADER_TAGBASE: isize = 1000; +pub const RPMTAG_SIG_BASE: isize = HEADER_SIGBASE; + +#[derive( + num_derive::FromPrimitive, + num_derive::ToPrimitive, + Debug, + PartialEq, + Copy, + Clone, + enum_display_derive::Display, +)] +#[allow(non_camel_case_types)] +pub enum IndexTag { + RPMTAG_HEADERIMAGE = HEADER_IMAGE, + RPMTAG_HEADERSIGNATURES = HEADER_SIGNATURES, + RPMTAG_HEADERIMMUTABLE = HEADER_IMMUTABLE, + RPMTAG_HEADERREGIONS = HEADER_REGIONS, + + RPMTAG_HEADERI18NTABLE = HEADER_I18NTABLE, + + RPMTAG_SIGSIZE = RPMTAG_SIG_BASE, + RPMTAG_SIGLEMD5_1 = RPMTAG_SIG_BASE + 2, + RPMTAG_SIGPGP = RPMTAG_SIG_BASE + 3, + RPMTAG_SIGLEMD5_2 = RPMTAG_SIG_BASE + 4, + RPMTAG_SIGMD5 = RPMTAG_SIG_BASE + 5, + + RPMTAG_SIGGPG = RPMTAG_SIG_BASE + 6, + RPMTAG_SIGPGP5 = RPMTAG_SIG_BASE + 7, + + RPMTAG_BADSHA1_1 = RPMTAG_SIG_BASE + 8, + RPMTAG_BADSHA1_2 = RPMTAG_SIG_BASE + 9, + RPMTAG_PUBKEYS = RPMTAG_SIG_BASE + 10, + RPMTAG_DSAHEADER = RPMTAG_SIG_BASE + 11, + RPMTAG_RSAHEADER = RPMTAG_SIG_BASE + 12, + RPMTAG_SHA1HEADER = RPMTAG_SIG_BASE + 13, + + RPMTAG_LONGSIGSIZE = RPMTAG_SIG_BASE + 14, + RPMTAG_LONGARCHIVESIZE = RPMTAG_SIG_BASE + 15, + + RPMTAG_SHA256HEADER = RPMTAG_SIG_BASE + 17, + + RPMTAG_NAME = 1000, + + RPMTAG_VERSION = 1001, + + RPMTAG_RELEASE = 1002, + + RPMTAG_EPOCH = 1003, + + RPMTAG_SUMMARY = 1004, + RPMTAG_DESCRIPTION = 1005, + RPMTAG_BUILDTIME = 1006, + RPMTAG_BUILDHOST = 1007, + RPMTAG_INSTALLTIME = 1008, + RPMTAG_SIZE = 1009, + RPMTAG_DISTRIBUTION = 1010, + RPMTAG_VENDOR = 1011, + RPMTAG_GIF = 1012, + RPMTAG_XPM = 1013, + RPMTAG_LICENSE = 1014, + RPMTAG_PACKAGER = 1015, + RPMTAG_GROUP = 1016, + RPMTAG_CHANGELOG = 1017, + RPMTAG_SOURCE = 1018, + RPMTAG_PATCH = 1019, + RPMTAG_URL = 1020, + RPMTAG_OS = 1021, + RPMTAG_ARCH = 1022, + RPMTAG_PREIN = 1023, + RPMTAG_POSTIN = 1024, + RPMTAG_PREUN = 1025, + RPMTAG_POSTUN = 1026, + RPMTAG_OLDFILENAMES = 1027, + RPMTAG_FILESIZES = 1028, + RPMTAG_FILESTATES = 1029, + RPMTAG_FILEMODES = 1030, + RPMTAG_FILEUIDS = 1031, + RPMTAG_FILEGIDS = 1032, + RPMTAG_FILERDEVS = 1033, + RPMTAG_FILEMTIMES = 1034, + RPMTAG_FILEDIGESTS = 1035, + + RPMTAG_FILELINKTOS = 1036, + RPMTAG_FILEFLAGS = 1037, + RPMTAG_ROOT = 1038, + RPMTAG_FILEUSERNAME = 1039, + RPMTAG_FILEGROUPNAME = 1040, + RPMTAG_EXCLUDE = 1041, + RPMTAG_EXCLUSIVE = 1042, + RPMTAG_ICON = 1043, + RPMTAG_SOURCERPM = 1044, + RPMTAG_FILEVERIFYFLAGS = 1045, + RPMTAG_ARCHIVESIZE = 1046, + RPMTAG_PROVIDENAME = 1047, + + RPMTAG_REQUIREFLAGS = 1048, + RPMTAG_REQUIRENAME = 1049, + + RPMTAG_REQUIREVERSION = 1050, + RPMTAG_NOSOURCE = 1051, + RPMTAG_NOPATCH = 1052, + RPMTAG_CONFLICTFLAGS = 1053, + RPMTAG_CONFLICTNAME = 1054, + + RPMTAG_CONFLICTVERSION = 1055, + RPMTAG_DEFAULTPREFIX = 1056, + RPMTAG_BUILDROOT = 1057, + RPMTAG_INSTALLPREFIX = 1058, + RPMTAG_EXCLUDEARCH = 1059, + RPMTAG_EXCLUDEOS = 1060, + RPMTAG_EXCLUSIVEARCH = 1061, + RPMTAG_EXCLUSIVEOS = 1062, + RPMTAG_AUTOREQPROV = 1063, + RPMTAG_RPMVERSION = 1064, + RPMTAG_TRIGGERSCRIPTS = 1065, + RPMTAG_TRIGGERNAME = 1066, + RPMTAG_TRIGGERVERSION = 1067, + RPMTAG_TRIGGERFLAGS = 1068, + RPMTAG_TRIGGERINDEX = 1069, + RPMTAG_VERIFYSCRIPT = 1079, + RPMTAG_CHANGELOGTIME = 1080, + RPMTAG_CHANGELOGNAME = 1081, + RPMTAG_CHANGELOGTEXT = 1082, + RPMTAG_BROKENMD5 = 1083, + RPMTAG_PREREQ = 1084, + RPMTAG_PREINPROG = 1085, + RPMTAG_POSTINPROG = 1086, + RPMTAG_PREUNPROG = 1087, + RPMTAG_POSTUNPROG = 1088, + RPMTAG_BUILDARCHS = 1089, + RPMTAG_OBSOLETENAME = 1090, + + RPMTAG_VERIFYSCRIPTPROG = 1091, + RPMTAG_TRIGGERSCRIPTPROG = 1092, + RPMTAG_DOCDIR = 1093, + RPMTAG_COOKIE = 1094, + RPMTAG_FILEDEVICES = 1095, + RPMTAG_FILEINODES = 1096, + RPMTAG_FILELANGS = 1097, + RPMTAG_PREFIXES = 1098, + RPMTAG_INSTPREFIXES = 1099, + RPMTAG_TRIGGERIN = 1100, + RPMTAG_TRIGGERUN = 1101, + RPMTAG_TRIGGERPOSTUN = 1102, + RPMTAG_AUTOREQ = 1103, + RPMTAG_AUTOPROV = 1104, + RPMTAG_CAPABILITY = 1105, + RPMTAG_SOURCEPACKAGE = 1106, + RPMTAG_OLDORIGFILENAMES = 1107, + RPMTAG_BUILDPREREQ = 1108, + RPMTAG_BUILDREQUIRES = 1109, + RPMTAG_BUILDCONFLICTS = 1110, + RPMTAG_BUILDMACROS = 1111, + RPMTAG_PROVIDEFLAGS = 1112, + RPMTAG_PROVIDEVERSION = 1113, + RPMTAG_OBSOLETEFLAGS = 1114, + RPMTAG_OBSOLETEVERSION = 1115, + RPMTAG_DIRINDEXES = 1116, + RPMTAG_BASENAMES = 1117, + RPMTAG_DIRNAMES = 1118, + RPMTAG_ORIGDIRINDEXES = 1119, + RPMTAG_ORIGBASENAMES = 1120, + RPMTAG_ORIGDIRNAMES = 1121, + RPMTAG_OPTFLAGS = 1122, + RPMTAG_DISTURL = 1123, + RPMTAG_PAYLOADFORMAT = 1124, + RPMTAG_PAYLOADCOMPRESSOR = 1125, + RPMTAG_PAYLOADFLAGS = 1126, + RPMTAG_INSTALLCOLOR = 1127, + RPMTAG_INSTALLTID = 1128, + RPMTAG_REMOVETID = 1129, + RPMTAG_SHA1RHN = 1130, + RPMTAG_RHNPLATFORM = 1131, + RPMTAG_PLATFORM = 1132, + RPMTAG_PATCHESNAME = 1133, + RPMTAG_PATCHESFLAGS = 1134, + RPMTAG_PATCHESVERSION = 1135, + RPMTAG_CACHECTIME = 1136, + RPMTAG_CACHEPKGPATH = 1137, + RPMTAG_CACHEPKGSIZE = 1138, + RPMTAG_CACHEPKGMTIME = 1139, + RPMTAG_FILECOLORS = 1140, + RPMTAG_FILECLASS = 1141, + RPMTAG_CLASSDICT = 1142, + RPMTAG_FILEDEPENDSX = 1143, + RPMTAG_FILEDEPENDSN = 1144, + RPMTAG_DEPENDSDICT = 1145, + RPMTAG_SOURCEPKGID = 1146, + RPMTAG_FILECONTEXTS = 1147, + RPMTAG_FSCONTEXTS = 1148, + RPMTAG_RECONTEXTS = 1149, + RPMTAG_POLICIES = 1150, + RPMTAG_PRETRANS = 1151, + RPMTAG_POSTTRANS = 1152, + RPMTAG_PRETRANSPROG = 1153, + RPMTAG_POSTTRANSPROG = 1154, + RPMTAG_DISTTAG = 1155, + RPMTAG_OLDSUGGESTSNAME = 1156, + + RPMTAG_OLDSUGGESTSVERSION = 1157, + RPMTAG_OLDSUGGESTSFLAGS = 1158, + RPMTAG_OLDENHANCESNAME = 1159, + + RPMTAG_OLDENHANCESVERSION = 1160, + RPMTAG_OLDENHANCESFLAGS = 1161, + RPMTAG_PRIORITY = 1162, + RPMTAG_CVSID = 1163, + + RPMTAG_BLINKPKGID = 1164, + RPMTAG_BLINKHDRID = 1165, + RPMTAG_BLINKNEVRA = 1166, + RPMTAG_FLINKPKGID = 1167, + RPMTAG_FLINKHDRID = 1168, + RPMTAG_FLINKNEVRA = 1169, + RPMTAG_PACKAGEORIGIN = 1170, + RPMTAG_TRIGGERPREIN = 1171, + RPMTAG_BUILDSUGGESTS = 1172, + RPMTAG_BUILDENHANCES = 1173, + RPMTAG_SCRIPTSTATES = 1174, + RPMTAG_SCRIPTMETRICS = 1175, + RPMTAG_BUILDCPUCLOCK = 1176, + RPMTAG_FILEDIGESTALGOS = 1177, + RPMTAG_VARIANTS = 1178, + RPMTAG_XMAJOR = 1179, + RPMTAG_XMINOR = 1180, + RPMTAG_REPOTAG = 1181, + RPMTAG_KEYWORDS = 1182, + RPMTAG_BUILDPLATFORMS = 1183, + RPMTAG_PACKAGECOLOR = 1184, + RPMTAG_PACKAGEPREFCOLOR = 1185, + RPMTAG_XATTRSDICT = 1186, + RPMTAG_FILEXATTRSX = 1187, + RPMTAG_DEPATTRSDICT = 1188, + RPMTAG_CONFLICTATTRSX = 1189, + RPMTAG_OBSOLETEATTRSX = 1190, + RPMTAG_PROVIDEATTRSX = 1191, + RPMTAG_REQUIREATTRSX = 1192, + RPMTAG_BUILDPROVIDES = 1193, + RPMTAG_BUILDOBSOLETES = 1194, + RPMTAG_DBINSTANCE = 1195, + RPMTAG_NVRA = 1196, + + RPMTAG_FILENAMES = 5000, + RPMTAG_FILEPROVIDE = 5001, + RPMTAG_FILEREQUIRE = 5002, + RPMTAG_FSNAMES = 5003, + RPMTAG_FSSIZES = 5004, + RPMTAG_TRIGGERCONDS = 5005, + RPMTAG_TRIGGERTYPE = 5006, + RPMTAG_ORIGFILENAMES = 5007, + RPMTAG_LONGFILESIZES = 5008, + RPMTAG_LONGSIZE = 5009, + RPMTAG_FILECAPS = 5010, + RPMTAG_FILEDIGESTALGO = 5011, + RPMTAG_BUGURL = 5012, + RPMTAG_EVR = 5013, + RPMTAG_NVR = 5014, + RPMTAG_NEVR = 5015, + RPMTAG_NEVRA = 5016, + RPMTAG_HEADERCOLOR = 5017, + RPMTAG_VERBOSE = 5018, + RPMTAG_EPOCHNUM = 5019, + RPMTAG_PREINFLAGS = 5020, + RPMTAG_POSTINFLAGS = 5021, + RPMTAG_PREUNFLAGS = 5022, + RPMTAG_POSTUNFLAGS = 5023, + RPMTAG_PRETRANSFLAGS = 5024, + RPMTAG_POSTTRANSFLAGS = 5025, + RPMTAG_VERIFYSCRIPTFLAGS = 5026, + RPMTAG_TRIGGERSCRIPTFLAGS = 5027, + RPMTAG_COLLECTIONS = 5029, + RPMTAG_POLICYNAMES = 5030, + RPMTAG_POLICYTYPES = 5031, + RPMTAG_POLICYTYPESINDEXES = 5032, + RPMTAG_POLICYFLAGS = 5033, + RPMTAG_VCS = 5034, + RPMTAG_ORDERNAME = 5035, + RPMTAG_ORDERVERSION = 5036, + RPMTAG_ORDERFLAGS = 5037, + RPMTAG_MSSFMANIFEST = 5038, + RPMTAG_MSSFDOMAIN = 5039, + RPMTAG_INSTFILENAMES = 5040, + RPMTAG_REQUIRENEVRS = 5041, + RPMTAG_PROVIDENEVRS = 5042, + RPMTAG_OBSOLETENEVRS = 5043, + RPMTAG_CONFLICTNEVRS = 5044, + RPMTAG_FILENLINKS = 5045, + RPMTAG_RECOMMENDNAME = 5046, + + RPMTAG_RECOMMENDVERSION = 5047, + RPMTAG_RECOMMENDFLAGS = 5048, + RPMTAG_SUGGESTNAME = 5049, + + RPMTAG_SUGGESTVERSION = 5050, + RPMTAG_SUGGESTFLAGS = 5051, + RPMTAG_SUPPLEMENTNAME = 5052, + + RPMTAG_SUPPLEMENTVERSION = 5053, + RPMTAG_SUPPLEMENTFLAGS = 5054, + RPMTAG_ENHANCENAME = 5055, + + RPMTAG_ENHANCEVERSION = 5056, + RPMTAG_ENHANCEFLAGS = 5057, + RPMTAG_RECOMMENDNEVRS = 5058, + RPMTAG_SUGGESTNEVRS = 5059, + RPMTAG_SUPPLEMENTNEVRS = 5060, + RPMTAG_ENHANCENEVRS = 5061, + RPMTAG_ENCODING = 5062, + RPMTAG_FILETRIGGERIN = 5063, + RPMTAG_FILETRIGGERUN = 5064, + RPMTAG_FILETRIGGERPOSTUN = 5065, + RPMTAG_FILETRIGGERSCRIPTS = 5066, + RPMTAG_FILETRIGGERSCRIPTPROG = 5067, + RPMTAG_FILETRIGGERSCRIPTFLAGS = 5068, + RPMTAG_FILETRIGGERNAME = 5069, + RPMTAG_FILETRIGGERINDEX = 5070, + RPMTAG_FILETRIGGERVERSION = 5071, + RPMTAG_FILETRIGGERFLAGS = 5072, + RPMTAG_TRANSFILETRIGGERIN = 5073, + RPMTAG_TRANSFILETRIGGERUN = 5074, + RPMTAG_TRANSFILETRIGGERPOSTUN = 5075, + RPMTAG_TRANSFILETRIGGERSCRIPTS = 5076, + RPMTAG_TRANSFILETRIGGERSCRIPTPROG = 5077, + RPMTAG_TRANSFILETRIGGERSCRIPTFLAGS = 5078, + RPMTAG_TRANSFILETRIGGERNAME = 5079, + RPMTAG_TRANSFILETRIGGERINDEX = 5080, + RPMTAG_TRANSFILETRIGGERVERSION = 5081, + RPMTAG_TRANSFILETRIGGERFLAGS = 5082, + RPMTAG_REMOVEPATHPOSTFIXES = 5083, + RPMTAG_FILETRIGGERPRIORITIES = 5084, + RPMTAG_TRANSFILETRIGGERPRIORITIES = 5085, + RPMTAG_FILETRIGGERCONDS = 5086, + RPMTAG_FILETRIGGERTYPE = 5087, + RPMTAG_TRANSFILETRIGGERCONDS = 5088, + RPMTAG_TRANSFILETRIGGERTYPE = 5089, + RPMTAG_FILESIGNATURES = 5090, + RPMTAG_FILESIGNATURELENGTH = 5091, + RPMTAG_PAYLOADDIGEST = 5092, + RPMTAG_PAYLOADDIGESTALGO = 5093, + RPMTAG_AUTOINSTALLED = 5094, + RPMTAG_IDENTITY = 5095, + RPMTAG_MODULARITYLABEL = 5096, + RPMTAG_PAYLOADDIGESTALT = 5097, +} + +#[derive( + num_derive::FromPrimitive, + num_derive::ToPrimitive, + Debug, + PartialEq, + Copy, + Clone, + enum_display_derive::Display, +)] +#[allow(non_camel_case_types)] +pub enum IndexSignatureTag { + HEADER_SIGNATURES = HEADER_SIGNATURES, + // This tag specifies the combined size of the Header and Payload sections. + RPMSIGTAG_SIZE = HEADER_TAGBASE, + + //This tag specifies the uncompressed size of the Payload archive, including the cpio headers. + RPMSIGTAG_PAYLOADSIZE = HEADER_TAGBASE + 7, + + //This index contains the SHA1 checksum of the entire Header Section, + //including the Header Record, Index Records and Header store. + RPMSIGTAG_SHA1 = 269, + + //This tag specifies the 128-bit MD5 checksum of the combined Header and Archive sections. + RPMSIGTAG_MD5 = 1004, + + //The tag contains the DSA signature of the Header section. + // The data is formatted as a Version 3 Signature Packet as specified in RFC 2440: OpenPGP Message Format. + // If this tag is present, then the SIGTAG_GPG tag shall also be present. + RPMSIGTAG_DSA = 267, + + // The tag contains the RSA signature of the Header section. + // The data is formatted as a Version 3 Signature Packet as specified in RFC 2440: OpenPGP Message Format. + // If this tag is present, then the SIGTAG_PGP shall also be present. + RPMSIGTAG_RSA = 268, + + // The tag contains the file signature of a file. + // The data is formatted as a hex-encoded string. + // If this tag is present, then the SIGTAG_FILESIGNATURE_LENGTH shall also be present. + RPMSIGTAG_FILESIGNATURES = 274, + + // The tag contains the length of the file signatures in total. + // If this tag is present, then the SIGTAG_FILESIGNATURE shall also be present. + RPMSIGTAG_FILESIGNATURE_LENGTH = 275, + + // This tag specifies the RSA signature of the combined Header and Payload sections. + // The data is formatted as a Version 3 Signature Packet as specified in RFC 2440: OpenPGP Message Format. + RPMSIGTAG_PGP = 1002, + + // The tag contains the DSA signature of the combined Header and Payload sections. + // The data is formatted as a Version 3 Signature Packet as specified in RFC 2440: OpenPGP Message Format. + RPMSIGTAG_GPG = 1005, + + //This index contains the SHA256 checksum of the entire Header Section, + //including the Header Record, Index Records and Header store. + RPMSIGTAG_SHA256 = IndexTag::RPMTAG_SHA256HEADER as isize, + + // A silly tag for a date. + RPMTAG_INSTALLTIME = IndexTag::RPMTAG_INSTALLTIME as isize, +} + +pub trait TypeName { + fn type_name() -> &'static str; +} + +impl TypeName for IndexTag { + fn type_name() -> &'static str { + "IndexTag" + } +} + +impl TypeName for IndexSignatureTag { + fn type_name() -> &'static str { + "IndexSignatureTag" + } +} + +/// lead header size +pub const LEAD_SIZE: usize = 96; +/// rpm magic as part of the lead header +pub const RPM_MAGIC: [u8; 4] = [0xed, 0xab, 0xee, 0xdb]; + +/// header magic recognition (not the lead!) +pub const HEADER_MAGIC: [u8; 3] = [0x8e, 0xad, 0xe8]; + +pub const RPMSENSE_ANY: u32 = 0; +pub const RPMSENSE_LESS: u32 = 1 << 1; +pub const RPMSENSE_GREATER: u32 = 1 << 2; +pub const RPMSENSE_EQUAL: u32 = 1 << 3; + +// there is no use yet for those constants. But they are part of the official package +// so I will leave them in in case we need them later. + +// const RPMSENSE_POSTTRANS: u32 = (1 << 5); +// const RPMSENSE_PREREQ: u32 = (1 << 6); +// const RPMSENSE_PRETRANS: u32 = (1 << 7); +// const RPMSENSE_INTERP: u32 = (1 << 8); +// const RPMSENSE_SCRIPT_PRE: u32 = (1 << 9); +// const RPMSENSE_SCRIPT_POST: u32 = (1 << 10); +// const RPMSENSE_SCRIPT_PREUN: u32 = (1 << 11); +// const RPMSENSE_SCRIPT_POSTUN: u32 = (1 << 12); +// const RPMSENSE_SCRIPT_VERIFY: u32 = (1 << 13); +// const RPMSENSE_FIND_REQUIRES: u32 = (1 << 14); +// const RPMSENSE_FIND_PROVIDES: u32 = (1 << 15); +// const RPMSENSE_TRIGGERIN: u32 = (1 << 16); +// const RPMSENSE_TRIGGERUN: u32 = (1 << 17); +// const RPMSENSE_TRIGGERPOSTUN: u32 = (1 << 18); +// const RPMSENSE_MISSINGOK: u32 = (1 << 19); + +// // for some weird reason, centos packages have another value for rpm lib sense. We have to observe this. +// const RPMSENSE_RPMLIB: u32 = (1 << 24); //0o100000012; +// const RPMSENSE_TRIGGERPREIN: u32 = (1 << 25); +// const RPMSENSE_KEYRING: u32 = (1 << 26); +// const RPMSENSE_CONFIG: u32 = (1 << 28); + +pub const RPMFILE_CONFIG: i32 = 1; +pub const RPMFILE_DOC: i32 = 1 << 1; +// const RPMFILE_DONOTUSE: i32 = (1 << 2); +// const RPMFILE_MISSINGOK: i32 = (1 << 3); +// const RPMFILE_NOREPLACE: i32 = (1 << 4); +// const RPMFILE_SPECFILE: i32 = (1 << 5); +// const RPMFILE_GHOST: i32 = (1 << 6); +// const RPMFILE_LICENSE: i32 = (1 << 7); +// const RPMFILE_README: i32 = (1 << 8); +// const RPMFILE_EXCLUDE: i32 = (1 << 9); + +// copied from rpmpgp.h +// should be technically equiv to +// `pgp::crypto::hash::HashAlgorithm` +// but that is only available with feature `signature` +pub const PGPHASHALGO_MD5: i32 = 1; +pub const PGPHASHALGO_SHA1: i32 = 2; +pub const PGPHASHALGO_RIPEMD160: i32 = 3; +pub const PGPHASHALGO_MD2: i32 = 5; +pub const PGPHASHALGO_TIGER192: i32 = 6; +pub const PGPHASHALGO_HAVAL_5_160: i32 = 7; +pub const PGPHASHALGO_SHA256: i32 = 8; +pub const PGPHASHALGO_SHA384: i32 = 9; +pub const PGPHASHALGO_SHA512: i32 = 10; +pub const PGPHASHALGO_SHA224: i32 = 11; diff --git a/rpmust/src/rpm/headers/errors.rs b/rpmust/src/rpm/headers/errors.rs new file mode 100644 index 0000000000000000000000000000000000000000..b7db1ab5e9acc366764b868c803a0b5e72266c71 --- /dev/null +++ b/rpmust/src/rpm/headers/errors.rs @@ -0,0 +1,62 @@ +use thiserror::Error; +use nom; +use std::io; + +#[derive(Error, Debug)] +#[non_exhaustive] +pub enum RPMError { + #[error(transparent)] + Io(#[from] io::Error), + + #[error("{0}")] + Nom(String), + #[error( + "invalid magic expected: {expected} but got: {actual} - whole input was {complete_input:?}" + )] + InvalidMagic { + expected: u8, + actual: u8, + complete_input: Vec, + }, + #[error("unsupported Version {0} - only header version 1 is supported")] + UnsupportedHeaderVersion(u8), + #[error("invalid tag {raw_tag} for store {store_type}")] + InvalidTag { + raw_tag: u32, + store_type: &'static str, + }, + #[error("invalid tag data type in store {store_type}: expected 0 - 9 but got {raw_data_type}")] + InvalidTagDataType { + raw_data_type: u32, + store_type: &'static str, + }, + #[error( + "unsupported lead major version {0} - only version 3 is supported" + )] + InvalidLeadMajorVersion(u8), + + #[error("unsupported lead major version {0} - only version 0 is supported")] + InvalidLeadMinorVersion(u8), + + #[error("invalid type - expected 0 or 1 but got {0}")] + InvalidLeadPKGType(u16), + + #[error("invalid os-type - expected 1 but got {0}")] + InvalidLeadOSType(u16), + + #[error("invalid signature-type - expected 5 but got {0}")] + InvalidLeadSignatureType(u16), + + #[error("invalid size of reserved area - expected length of {expected} but got {actual}")] + InvalidReservedSpaceSize { expected: u16, actual: usize }, +} +impl From> for RPMError { + fn from(error: nom::Err<(&[u8], nom::error::ErrorKind)>) -> Self { + match error { + nom::Err::Error((_, kind)) | nom::Err::Failure((_, kind)) => { + RPMError::Nom(kind.description().to_string()) + } + nom::Err::Incomplete(_) => RPMError::Nom("unhandled incomplete".to_string()), + } + } +} diff --git a/rpmust/src/rpm/headers/header.rs b/rpmust/src/rpm/headers/header.rs new file mode 100644 index 0000000000000000000000000000000000000000..01f7e3db179750e78f8246584cb705d211968404 --- /dev/null +++ b/rpmust/src/rpm/headers/header.rs @@ -0,0 +1,487 @@ + +use std::fmt; +use nom::bytes::complete; +use nom::number::complete::{be_i16, be_i32, be_i64, be_i8, be_u32, be_u8}; +use num::*; +use super::constants::{self,*}; +use std::convert::TryInto; +use super::errors::*; + +pub trait Tag: + num::FromPrimitive + num::ToPrimitive + PartialEq + fmt::Display + fmt::Debug + Copy + TypeName +{ +} + +impl Tag for T where + T: num::FromPrimitive + + num::ToPrimitive + + PartialEq + + fmt::Display + + fmt::Debug + + Copy + + TypeName +{ +} + +#[derive(Debug, PartialEq)] +pub struct Header { + pub(crate) index_header: IndexHeader, + pub(crate) index_entries: Vec>, + pub(crate) store: Vec, +} + + + + +impl Header +where + T: Tag, +{ + pub(crate) fn parse(input: &mut I) -> Result, RPMError> { + let mut buf: [u8; 16] = [0; 16]; + input.read_exact(&mut buf)?; + let index_header = IndexHeader::parse(&buf)?; + // read rest of header => each index consists of 16 bytes. The index header knows how large the store is. + let mut buf = vec![0; (index_header.header_size + index_header.num_entries * 16) as usize]; + input.read_exact(&mut buf)?; + Self::parse_header(index_header, &buf[..]) + } + + fn parse_header(index_header: IndexHeader, mut bytes: &[u8]) -> Result, RPMError> { + // parse all entries + let mut entries: Vec> = Vec::new(); + let mut buf_len = bytes.len(); + for _ in 0..index_header.num_entries { + let (rest, entry) = IndexEntry::parse(bytes)?; + entries.push(entry); + bytes = rest; + assert_eq!(16, buf_len - bytes.len()); + buf_len = bytes.len(); + } + + assert_eq!(bytes.len(), index_header.header_size as usize); + + let store = Vec::from(bytes); + // add data to entries + for entry in &mut entries { + let mut remaining = &bytes[entry.offset as usize..]; + match &mut entry.data { + IndexData::Null => {} + IndexData::Char(ref mut chars) => { + parse_entry_data_number(remaining, entry.num_items, chars, be_u8)?; + } + IndexData::Int8(ref mut ints) => { + parse_entry_data_number(remaining, entry.num_items, ints, be_i8)?; + } + IndexData::Int16(ref mut ints) => { + parse_entry_data_number(remaining, entry.num_items, ints, be_i16)?; + } + IndexData::Int32(ref mut ints) => { + parse_entry_data_number(remaining, entry.num_items, ints, be_i32)?; + } + IndexData::Int64(ref mut ints) => { + parse_entry_data_number(remaining, entry.num_items, ints, be_i64)?; + } + IndexData::StringTag(ref mut string) => { + let (_rest, raw_string) = complete::take_till(|item| item == 0)(remaining)?; + string.push_str(String::from_utf8_lossy(raw_string).as_ref()); + } + IndexData::Bin(ref mut bin) => { + parse_entry_data_number(remaining, entry.num_items, bin, be_u8)?; + } + IndexData::StringArray(ref mut strings) => { + for _ in 0..entry.num_items { + let (rest, raw_string) = complete::take_till(|item| item == 0)(remaining)?; + // the null byte is still in there.. we need to cut it out. + remaining = &rest[1..]; + let string = String::from_utf8_lossy(raw_string).to_string(); + strings.push(string); + } + } + IndexData::I18NString(ref mut strings) => { + for _ in 0..entry.num_items { + let (rest, raw_string) = complete::take_till(|item| item == 0)(remaining)?; + remaining = rest; + let string = String::from_utf8_lossy(raw_string).to_string(); + strings.push(string); + } + } + } + } + + Ok(Header { + index_header, + index_entries: entries, + store, + }) + } +} +#[derive(Debug, PartialEq)] +pub(crate) struct IndexHeader { + /// rpm specific magic header + pub(crate) magic: [u8; 3], + /// rpm version number, always 1 + pub(crate) version: u8, + /// number of header entries + pub(crate) num_entries: u32, + /// total header size excluding the fixed part ( I think ) + pub(crate) header_size: u32, +} + +impl IndexHeader { + pub(crate) fn parse(input: &[u8]) -> Result { + let (rest, magic) = complete::take(3usize)(input)?; + for i in 0..2 { + if HEADER_MAGIC[i] != magic[i] { + return Err(RPMError::InvalidMagic { + expected: HEADER_MAGIC[i], + actual: magic[i], + complete_input: input.to_vec(), + }) + } + } + let (rest, version) = be_u8(rest)?; + + if version != 1 { + return Err(RPMError::UnsupportedHeaderVersion(version)); + } + + let (rest, _) = complete::take(4usize)(rest)?; + + let (rest, num_entries) = be_u32(rest)?; + + let (rest, header_size) = be_u32(rest)?; + + Ok(IndexHeader { + magic: magic.try_into().unwrap(), + version: 1, + num_entries, + header_size, + }) + } +} + +#[derive(Debug, PartialEq)] +pub(crate) struct IndexEntry { + pub(crate) tag: T, + pub(crate) data: IndexData, + pub(crate) offset: i32, + pub(crate) num_items: u32, +} +use super::constants::TypeName; + +impl IndexEntry { + pub(crate) fn parse(input: &[u8]) -> Result<(&[u8], Self), RPMError> { + //first 4 bytes are the tag. + let (input, raw_tag) = be_u32(input)?; + + let tag: T = num::FromPrimitive::from_u32(raw_tag).ok_or_else(|| RPMError::InvalidTag { + raw_tag: raw_tag, + store_type: T::type_name(), + })?; + //next 4 bytes is the tag type + let (input, raw_tag_type) = be_u32(input)?; + + // initialize the datatype. Parsing of the data happens later since the store comes after the index section. + let data = + IndexData::from_u32(raw_tag_type).ok_or_else(|| RPMError::InvalidTagDataType { + raw_data_type: raw_tag_type, + store_type: T::type_name(), + })?; + + // next 4 bytes is the offset relative to the beginning of the store + let (input, offset) = be_i32(input)?; + + // last 4 bytes are the count that contains the number of data items pointed to by the index entry + let (rest, num_items) = be_u32(input)?; + + Ok(( + rest, + IndexEntry { + tag, + data, + offset, + num_items, + }, + )) + } +} + +impl Header { + pub(crate) fn parse_signature( + input: &mut I, + ) -> Result, RPMError> { + let result = Self::parse(input)?; + // this structure is aligned to 8 bytes - rest is filled up with zeros. + // if the size of our store is not a modulo of 8, we discard bytes to align to the 8 byte boundary. + let modulo = result.index_header.header_size % 8; + if modulo > 0 { + let align_size = 8 - modulo; + let mut discard = vec![0; align_size as usize]; + input.read_exact(&mut discard)?; + } + Ok(result) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub(crate) enum IndexData { + Null, + Char(Vec), + Int8(Vec), + Int16(Vec), + Int32(Vec), + Int64(Vec), + StringTag(String), + Bin(Vec), + StringArray(Vec), + I18NString(Vec), +} + +impl fmt::Display for IndexData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let rep = match self { + IndexData::Null => "Null", + IndexData::Bin(_) => "Bin", + IndexData::Char(_) => "Char", + IndexData::I18NString(_) => "I18NString", + IndexData::StringTag(_) => "String", + IndexData::StringArray(_) => "StringArray", + IndexData::Int8(_) => "i8", + IndexData::Int16(_) => "i16", + IndexData::Int32(_) => "i32", + IndexData::Int64(_) => "i64", + }; + write!(f, "{}", rep) + } +} + +impl IndexData { + pub(crate) fn append(&self, store: &mut Vec) -> u32 { + match &self { + IndexData::Null => 0, + IndexData::Char(d) => { + store.extend_from_slice(d); + 0 + } + IndexData::Int8(d) => { + for i in d.iter().map(|i| i.to_be_bytes()) { + store.push(i[0]); + } + 0 + } + IndexData::Int16(d) => { + // align to 2 bytes + + let alignment = if store.len() % 2 != 0 { + store.push(0); + 1 + } else { + 0 + }; + let iter = d.iter().flat_map(|item| item.to_be_bytes().to_vec()); + for byte in iter { + store.push(byte); + } + alignment + } + IndexData::Int32(d) => { + // align to 4 bytes + let mut alignment = 0; + while store.len() % 4 > 0 { + store.push(0); + alignment += 1; + } + let iter = d.iter().flat_map(|item| item.to_be_bytes().to_vec()); + for byte in iter { + store.push(byte); + } + alignment + } + IndexData::Int64(d) => { + // align to 8 bytes + let mut alignment = 0; + while store.len() % 8 > 0 { + store.push(0); + alignment += 1; + } + let iter = d.iter().flat_map(|item| item.to_be_bytes().to_vec()); + for byte in iter { + store.push(byte); + } + alignment + } + IndexData::StringTag(d) => { + store.extend_from_slice(d.as_bytes()); + store.push(0); + 0 + } + IndexData::Bin(d) => { + store.extend_from_slice(&d); + 0 + } + IndexData::StringArray(d) => { + for item in d { + store.extend_from_slice(item.as_bytes()); + store.push(0); + } + 0 + } + IndexData::I18NString(d) => { + for item in d { + store.extend_from_slice(item.as_bytes()); + store.push(0); + } + 0 + } + } + } + + pub(crate) fn num_items(&self) -> u32 { + match self { + IndexData::Null => 0, + IndexData::Bin(items) => items.len() as u32, + IndexData::Char(items) => items.len() as u32, + IndexData::I18NString(items) => items.len() as u32, + IndexData::StringTag(_) => 1, + IndexData::StringArray(items) => items.len() as u32, + IndexData::Int8(items) => items.len() as u32, + IndexData::Int16(items) => items.len() as u32, + IndexData::Int32(items) => items.len() as u32, + IndexData::Int64(items) => items.len() as u32, + } + } + pub(crate) fn from_u32(i: u32) -> Option { + match i { + 0 => Some(IndexData::Null), + 1 => Some(IndexData::Char(Vec::new())), + 2 => Some(IndexData::Int8(Vec::new())), + 3 => Some(IndexData::Int16(Vec::new())), + 4 => Some(IndexData::Int32(Vec::new())), + 5 => Some(IndexData::Int64(Vec::new())), + 6 => Some(IndexData::StringTag(String::new())), + 7 => Some(IndexData::Bin(Vec::new())), + 8 => Some(IndexData::StringArray(Vec::new())), + 9 => Some(IndexData::I18NString(Vec::new())), + _ => None, + } + } + pub(crate) fn to_u32(&self) -> u32 { + match self { + IndexData::Null => 0, + IndexData::Char(_) => 1, + IndexData::Int8(_) => 2, + IndexData::Int16(_) => 3, + IndexData::Int32(_) => 4, + IndexData::Int64(_) => 5, + IndexData::StringTag(_) => 6, + IndexData::Bin(_) => 7, + + IndexData::StringArray(_) => 8, + IndexData::I18NString(_) => 9, + } + } + + pub(crate) fn as_str(&self) -> Option<&str> { + match self { + IndexData::StringTag(s) => Some(&s), + _ => None, + } + } + + #[allow(unused)] + pub(crate) fn as_char_array(&self) -> Option> { + match self { + IndexData::Char(s) => Some(s.to_vec()), + _ => None, + } + } + + #[allow(unused)] + pub(crate) fn as_i8_array(&self) -> Option> { + match self { + IndexData::Int8(s) => Some(s.to_vec()), + _ => None, + } + } + + pub(crate) fn as_i16_array(&self) -> Option> { + match self { + IndexData::Int16(s) => Some(s.to_vec()), + _ => None, + } + } + + pub(crate) fn as_i32(&self) -> Option { + match self { + IndexData::Int32(s) => { + if !s.is_empty() { + Some(s[0]) + } else { + None + } + } + _ => None, + } + } + pub(crate) fn as_i32_array(&self) -> Option> { + match self { + IndexData::Int32(s) => Some(s.to_vec()), + _ => None, + } + } + + pub(crate) fn as_i64(&self) -> Option { + match self { + IndexData::Int64(s) => { + if !s.is_empty() { + Some(s[0]) + } else { + None + } + } + _ => None, + } + } + + pub(crate) fn as_i64_array(&self) -> Option> { + match self { + IndexData::Int64(s) => Some(s.to_vec()), + _ => None, + } + } + + pub(crate) fn as_string_array(&self) -> Option<&[String]> { + match self { + IndexData::StringArray(d) | IndexData::I18NString(d) => Some(&d), + _ => None, + } + } + + pub(crate) fn as_binary(&self) -> Option<&[u8]> { + match self { + IndexData::Bin(d) => Some(d.as_slice()), + _ => None, + } + } +} + +fn parse_entry_data_number<'a, T, E, F>( + mut input: &'a [u8], + num_items: u32, + items: &mut Vec, + parser: F, +) -> nom::IResult<&'a [u8], (), E> +where + E: nom::error::ParseError<&'a [u8]>, + F: Fn(&'a [u8]) -> nom::IResult<&'a [u8], T, E>, +{ + for _ in 0..num_items { + let (rest, data) = parser(input)?; + items.push(data); + input = rest; + } + + Ok((input, ())) +} + diff --git a/rpmust/src/rpm/headers/lead.rs b/rpmust/src/rpm/headers/lead.rs new file mode 100644 index 0000000000000000000000000000000000000000..95c6788884d3399d5023d09cf534ca06a94fdd3a --- /dev/null +++ b/rpmust/src/rpm/headers/lead.rs @@ -0,0 +1,98 @@ +use nom::bytes::complete; +use nom::number::complete::{be_u16, be_u8}; + +use super::constants::*; +use super::errors::*; + +pub struct Lead { + magic: [u8; 4], + major: u8, + minor: u8, + package_type: u16, + arch: u16, + name: [u8; 66], + os: u16, + signature_type: u16, + reserved: [u8; 16], +} + +impl std::fmt::Debug for Lead { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = String::from_utf8_lossy(&self.name); + f.debug_struct("Lead") + .field("magic", &self.magic) + .field("major", &self.major) + .field("minor", &self.minor) + .field("package_type", &self.package_type) + .field("arch", &self.arch) + .field("name", &name) + .field("os", &self.os) + .field("signature_type", &self.signature_type) + .field("reserved", &self.reserved) + .finish() + } +} + +impl Lead { + pub fn parse(input: &[u8]) -> Result { + let (rest, magic) = complete::take(4usize)(input)?; + for i in 0..magic.len() { + if magic[i] != RPM_MAGIC[i] { + return Err(RPMError::InvalidMagic { + expected: RPM_MAGIC[i], + actual: magic[i], + complete_input: input.to_vec(), + }) + } + } + let (rest,major) = be_u8(rest)?; + if major != 3 { + return Err(RPMError::InvalidLeadMajorVersion(major)); + } + let (rest,minor) = be_u8(rest)?; + if minor != 0 { + return Err(RPMError::InvalidLeadMinorVersion(minor)); + } + let (rest, pkg_type) = be_u16(rest)?; + + if pkg_type > 1 { + return Err(RPMError::InvalidLeadPKGType(pkg_type)); + } + + let (rest, arch) = be_u16(rest)?; + let (rest, name) = complete::take(66usize)(rest)?; + + let (rest, os) = be_u16(rest)?; + if os != 1 { + return Err(RPMError::InvalidLeadOSType(os)); + } + + let (rest, sigtype) = be_u16(rest)?; + if sigtype != 5 { + return Err(RPMError::InvalidLeadSignatureType(sigtype)); + } + + if rest.len() != 16 { + return Err(RPMError::InvalidReservedSpaceSize { + expected: 16, + actual: rest.len(), + }); + } + + let mut name_arr: [u8; 66] = [0; 66]; + name_arr.copy_from_slice(name); + + //save unwrap here since we've checked length of slices. + Ok(Lead { + magic: magic.try_into().unwrap(), + major, + minor, + package_type: pkg_type, + arch, + name: name_arr, + os, + signature_type: sigtype, + reserved: rest.try_into().unwrap(), + }) + } +} \ No newline at end of file diff --git a/rpmust/src/rpm/headers/mod.rs b/rpmust/src/rpm/headers/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..a61ece85c638961c039224713aca30d66fd3bb45 --- /dev/null +++ b/rpmust/src/rpm/headers/mod.rs @@ -0,0 +1,9 @@ +mod lead; +mod errors; +mod constants; +mod header; + +pub use errors::*; +pub use lead::*; +pub use constants::*; +pub use header::*; \ No newline at end of file diff --git a/rpmust/src/rpm/mod.rs b/rpmust/src/rpm/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..7b57c1c62ffe2b43242172b56d9a694bfb19af50 --- /dev/null +++ b/rpmust/src/rpm/mod.rs @@ -0,0 +1,6 @@ + +pub mod rpmmeta; +pub mod headers; + +pub use rpmmeta::*; +pub use headers::*; \ No newline at end of file diff --git a/rpmust/src/rpm/rpmmeta.rs b/rpmust/src/rpm/rpmmeta.rs new file mode 100644 index 0000000000000000000000000000000000000000..ee7a160dbecff70ce859698428f68bb465d4befc --- /dev/null +++ b/rpmust/src/rpm/rpmmeta.rs @@ -0,0 +1,21 @@ +use super::headers::*; + +pub struct RPMPackageMetadata { + pub lead: Lead, + pub signature: Header, + pub header: Header, +} +impl RPMPackageMetadata { + pub(crate) fn parse(input: &mut T) -> Result { + let mut lead_buffer = [0; LEAD_SIZE]; + input.read_exact(&mut lead_buffer)?; + let lead = Lead::parse(&lead_buffer)?; + let signature_header = Header::parse_signature(input)?; + let header = Header::parse(input)?; + Ok(RPMPackageMetadata { + lead, + signature: signature_header, + header, + }) + } +} \ No newline at end of file diff --git a/rpmust/src/test/stratovirt.rpm b/rpmust/src/test/stratovirt.rpm new file mode 100644 index 0000000000000000000000000000000000000000..a78888296604a7fc74c0c00d86111cda4a9a7e57 Binary files /dev/null and b/rpmust/src/test/stratovirt.rpm differ