Пример #1
0
        /// <summary>
        /// Calculates the signature for this package.
        /// </summary>
        /// <param name="package">
        /// The package for whcih to calculate the signature.
        /// </param>
        /// <param name="signer">
        /// The signer to use.
        /// </param>
        /// <param name="compressedPayloadStream">
        /// The compressed payload.
        /// </param>
        public void CalculateSignature(RpmPackage package, IPackageSigner signer, Stream compressedPayloadStream)
        {
            RpmSignature signature = new RpmSignature(package);

            using (MemoryStream headerStream = this.GetHeaderStream(package))
                using (ConcatStream headerAndPayloadStream = new ConcatStream(leaveOpen: true, streams: new Stream[] { headerStream, compressedPayloadStream }))
                {
                    SHA1 sha = SHA1.Create();
                    signature.Sha1Hash = sha.ComputeHash(headerStream);

                    MD5 md5 = MD5.Create();
                    signature.MD5Hash = md5.ComputeHash(headerAndPayloadStream);

                    // Verify the PGP signatures
                    // 3 for the header
                    headerStream.Position        = 0;
                    signature.HeaderPgpSignature = signer.Sign(headerStream);

                    headerAndPayloadStream.Position        = 0;
                    signature.HeaderAndPayloadPgpSignature = signer.Sign(headerAndPayloadStream);

                    // Verify the signature size (header + compressed payload)
                    signature.HeaderAndPayloadSize = (int)headerAndPayloadStream.Length;
                }

            // Verify the payload size (header + uncompressed payload)
            using (Stream payloadStream = RpmPayloadReader.GetDecompressedPayloadStream(package, compressedPayloadStream))
            {
                signature.UncompressedPayloadSize = (int)payloadStream.Length;
            }
        }
Пример #2
0
        /// <summary>
        /// Creates a RPM Package.
        /// </summary>
        /// <param name="archiveEntries">
        /// The archive entries which make up the RPM package.
        /// </param>
        /// <param name="payloadStream">
        /// A <see cref="Stream"/> which contains the CPIO archive for the RPM package.
        /// </param>
        /// <param name="name">
        /// The name of the package.
        /// </param>
        /// <param name="version">
        /// The version of the software.
        /// </param>
        /// <param name="arch">
        /// The architecture targetted by the package.
        /// </param>
        /// <param name="release">
        /// The release version.
        /// </param>
        /// <param name="createUser">
        /// <see langword="true"/> to create a user account; otherwise, <see langword="false"/>.
        /// </param>
        /// <param name="userName">
        /// The name of the user account to create.
        /// </param>
        /// <param name="installService">
        /// <see langword="true"/> to install a system service, otherwise, <see langword="false"/>.
        /// </param>
        /// <param name="serviceName">
        /// The name of the system service to create.
        /// </param>
        /// <param name="vendor">
        /// The package vendor.
        /// </param>
        /// <param name="description">
        /// The package description.
        /// </param>
        /// <param name="url">
        /// The package URL.
        /// </param>
        /// <param name="prefix">
        /// A prefix to use.
        /// </param>
        /// <param name="preInstallScript">
        /// Pre-Install script
        /// </param>
        /// <param name="postInstallScript">
        /// Post-Install script
        /// </param>
        /// <param name="preRemoveScript">
        /// Pre-Remove script
        /// </param>
        /// <param name="postRemoveScript">
        /// Post-Remove script
        /// </param>
        /// <param name="additionalDependencies">
        /// Additional dependencies to add to the RPM package.
        /// </param>
        /// <param name="additionalMetadata">
        /// Any additional metadata.
        /// </param>
        /// <param name="signer">
        /// The signer to use when signing the package.
        /// </param>
        /// <param name="targetStream">
        /// The <see cref="Stream"/> to which to write the package.
        /// </param>
        /// <param name="includeVersionInName">
        /// <see langword="true"/> to include the version number and release number
        /// in the <see cref="RpmLead.Name"/>; <see langword="false"/> to only
        /// use the package name.
        /// </param>
        /// <param name="payloadIsCompressed">
        /// <see langword="true"/> if <paramref name="payloadStream"/> is already
        /// compressed. In this case, the <paramref name="payloadStream"/> will be
        /// copied "as is" to the resulting RPM package.
        /// </param>
        public void CreatePackage(
            List <ArchiveEntry> archiveEntries,
            Stream payloadStream,
            string name,
            string version,
            string arch,
            string release,
            bool createUser,
            string userName,
            bool installService,
            string serviceName,
            string vendor,
            string description,
            string url,
            string prefix,
            string preInstallScript,
            string postInstallScript,
            string preRemoveScript,
            string postRemoveScript,
            IEnumerable <PackageDependency> additionalDependencies,
            Action <RpmMetadata> additionalMetadata,
            IPackageSigner signer,
            Stream targetStream,
            bool includeVersionInName = false,
            bool payloadIsCompressed  = false)
        {
            // This routine goes roughly like:
            // 1. Calculate all the metadata, including a signature,
            //    but use an empty compressed payload to calculate
            //    the signature
            // 2. Write out the rpm file, and compress the payload
            // 3. Update the signature
            //
            // This way, we avoid having to compress the payload into a temporary
            // file.

            // Core routine to populate files and dependencies (part of the metadata
            // in the header)
            RpmPackage package  = new RpmPackage();
            var        metadata = new RpmMetadata(package)
            {
                Name    = name,
                Version = version,
                Arch    = arch,
                Release = release,
            };

            this.AddPackageProvides(metadata);
            this.AddLdDependencies(metadata);

            var files = this.CreateFiles(archiveEntries);

            metadata.Files = files;

            this.AddRpmDependencies(metadata, additionalDependencies);

            // Try to define valid defaults for most metadata
            metadata.Locales = new Collection <string> {
                "C"
            };                                                 // Should come before any localizable data.
            metadata.BuildHost         = "dotnet-rpm";
            metadata.BuildTime         = DateTimeOffset.Now;
            metadata.Cookie            = "dotnet-rpm";
            metadata.FileDigetsAlgo    = PgpHashAlgo.PGPHASHALGO_SHA256;
            metadata.Group             = "System Environment/Libraries";
            metadata.OptFlags          = string.Empty;
            metadata.Os                = "linux";
            metadata.PayloadCompressor = "xz";
            metadata.PayloadFlags      = "2";
            metadata.PayloadFormat     = "cpio";
            metadata.Platform          = "x86_64-redhat-linux-gnu";
            metadata.RpmVersion        = "4.11.3";
            metadata.SourcePkgId       = new byte[0x10];
            metadata.SourceRpm         = $"{name}-{version}-{release}.src.rpm";

            // Scripts which run before & after installation and removal.
            var preIn  = preInstallScript ?? string.Empty;
            var postIn = postInstallScript ?? string.Empty;
            var preUn  = preRemoveScript ?? string.Empty;
            var postUn = postRemoveScript ?? string.Empty;

            if (createUser)
            {
                // Add the user and group, under which the service runs.
                // These users are never removed because UIDs are re-used on Linux.
                preIn += $"/usr/sbin/groupadd -r {userName} 2>/dev/null || :\n" +
                         $"/usr/sbin/useradd -g {userName} -s /sbin/nologin -r {userName} 2>/dev/null || :\n";
            }

            if (installService)
            {
                // Install and activate the service.
                postIn +=
                    $"if [ $1 -eq 1 ] ; then \n" +
                    $"    systemctl enable --now {serviceName}.service >/dev/null 2>&1 || : \n" +
                    $"fi\n";

                preUn +=
                    $"if [ $1 -eq 0 ] ; then \n" +
                    $"    # Package removal, not upgrade \n" +
                    $"    systemctl --no-reload disable --now {serviceName}.service > /dev/null 2>&1 || : \n" +
                    $"fi\n";

                postUn +=
                    $"if [ $1 -ge 1 ] ; then \n" +
                    $"    # Package upgrade, not uninstall \n" +
                    $"    systemctl try-restart {serviceName}.service >/dev/null 2>&1 || : \n" +
                    $"fi\n";
            }

            // Remove all directories marked as such (these are usually directories which contain temporary files)
            foreach (var entryToRemove in archiveEntries.Where(e => e.RemoveOnUninstall))
            {
                preUn +=
                    $"if [ $1 -eq 0 ] ; then \n" +
                    $"    # Package removal, not upgrade \n" +
                    $"    /usr/bin/rm -rf {entryToRemove.TargetPath}\n" +
                    $"fi\n";
            }

            if (!string.IsNullOrEmpty(preIn))
            {
                metadata.PreInProg = "/bin/sh";
                metadata.PreIn     = preIn;
            }

            if (!string.IsNullOrEmpty(postIn))
            {
                metadata.PostInProg = "/bin/sh";
                metadata.PostIn     = postIn;
            }

            if (!string.IsNullOrEmpty(preUn))
            {
                metadata.PreUnProg = "/bin/sh";
                metadata.PreUn     = preUn;
            }

            if (!string.IsNullOrEmpty(postUn))
            {
                metadata.PostUnProg = "/bin/sh";
                metadata.PostUn     = postUn;
            }

            // Not providing these (or setting empty values) would cause rpmlint errors
            metadata.Description = string.IsNullOrEmpty(description)
                ? $"{name} version {version}-{release}"
                : description;
            metadata.Summary = $"{name} version {version}-{release}";
            metadata.License = $"{name} License";

            metadata.Distribution = string.Empty;
            metadata.DistUrl      = string.Empty;
            metadata.Url          = url ?? string.Empty;
            metadata.Vendor       = vendor ?? string.Empty;

            metadata.ChangelogEntries = new Collection <ChangelogEntry>()
            {
                new ChangelogEntry(DateTimeOffset.Now, "dotnet-rpm", "Created a RPM package using dotnet-rpm")
            };

            // User-set metadata
            if (additionalMetadata != null)
            {
                additionalMetadata(metadata);
            }

            this.CalculateHeaderOffsets(package);

            using (MemoryStream dummyCompressedPayload = new MemoryStream())
            {
                using (XZOutputStream dummyPayloadCompressor =
                           new XZOutputStream(
                               dummyCompressedPayload,
                               this.CompressionThreads,
                               XZOutputStream.DefaultPreset,
                               leaveOpen: true))
                {
                    dummyPayloadCompressor.Write(new byte[] { 0 }, 0, 1);
                }

                this.CalculateSignature(package, signer, dummyCompressedPayload);
            }

            this.CalculateSignatureOffsets(package);

            // Write out all the data - includes the lead
            byte[] nameBytes  = new byte[66];
            var    nameInLead = includeVersionInName ? $"{name}-{version}-{release}" : name;

            Encoding.UTF8.GetBytes(nameInLead, 0, nameInLead.Length, nameBytes, 0);

            var lead = new RpmLead()
            {
                ArchNum       = 1,
                Magic         = 0xedabeedb,
                Major         = 0x03,
                Minor         = 0x00,
                NameBytes     = nameBytes,
                OsNum         = 0x0001,
                Reserved      = new byte[16],
                SignatureType = 0x0005,
                Type          = 0x0000,
            };

            // Write out the lead, signature and header
            targetStream.Position = 0;
            targetStream.SetLength(0);

            targetStream.WriteStruct(lead);
            this.WriteSignature(package, targetStream);
            this.WriteHeader(package, targetStream);

            // Write out the compressed payload
            int compressedPayloadOffset = (int)targetStream.Position;

            // The user can choose to pass an already-comrpessed
            // payload. In this case, no need to re-compress.
            if (payloadIsCompressed)
            {
                payloadStream.CopyTo(targetStream);
            }
            else
            {
                using (XZOutputStream compressor = new XZOutputStream(
                           targetStream,
                           XZOutputStream.DefaultThreads,
                           XZOutputStream.DefaultPreset,
                           leaveOpen: true))
                {
                    payloadStream.Position = 0;
                    payloadStream.CopyTo(compressor);
                }
            }

            using (SubStream compressedPayloadStream = new SubStream(
                       targetStream,
                       compressedPayloadOffset,
                       targetStream.Length - compressedPayloadOffset,
                       leaveParentOpen: true,
                       readOnly: true))
            {
                this.CalculateSignature(package, signer, compressedPayloadStream);
                this.CalculateSignatureOffsets(package);
            }

            // Update the lead and signature
            targetStream.Position = 0;

            targetStream.WriteStruct(lead);
            this.WriteSignature(package, targetStream);
        }