diff --git a/doc/core/howto/basics.xhtml b/doc/core/howto/basics.xhtml
index 30b7048..df27fba 100644
--- a/doc/core/howto/basics.xhtml
+++ b/doc/core/howto/basics.xhtml
@@ -60,35 +60,34 @@ pid (usually you would do <code class="shell">kill `cat twistd.pid`</code>).
 
 <p>As always, the gory details are in the manual page.</p>
 
-<h2>tap2deb</h2>
+<h2>OS Integration</h2>
 
 <p>
-For Twisted-based server application developers who want to deploy on
-Debian, Twisted supplies the <code class="shell">tap2deb</code> program. This program
-wraps a Twisted Application file (of any of the supported formats -- Python,
-source, xml or pickle)
-in a Debian package, including correct installation and removal scripts
-and <code>init.d</code> scripts. This frees the installer from manually
-stopping or starting the service, and will make sure it goes properly up
-on startup and down on shutdown and that it obeys the init levels.
+If you have an Application that runs with <code class="shell">twistd</code>,
+you can easily deploy it on RedHat Linux or Debian GNU/Linux based systems
+using the <code class="shell">tap2deb</code> or <code
+class="shell">tap2rpm</code> tools. These take a Twisted Application file (of
+any of the supported formats — Python source, XML or pickle), and build a
+Debian or RPM package (respectively) that installs the Application as a system
+service. The package includes the Application file, a default
+<code>/etc/init.d/</code> script that starts and stops the process with twistd,
+and post-installation scripts that configure the Application to be run in the
+appropriate init levels.
 </p>
 
+<!-- Is "note" really the right class to be using here? -->
+<div class="note">
+<code class="shell">tap2rpm</code> and <code class="shell">tap2deb</code> do
+not package your entire application and dependent code, just the Twisted
+Application file. You will need to find some other way to package your Python
+code, such as <code class="API">distutils</code>' <code>bdist_rpm</code>
+command.
+</div>
+
 <p>
-For the more savvy Debian users, the
-<code class="shell">tap2deb</code> also generates the source package, allowing her
-to modify and polish things which automated software cannot detect
-(such as dependencies or relationships to virtual packages). In addition,
-the Twisted team itself intends to produce Debian packages for some common
-services, such as web servers and an inetd replacement. Those packages
-will enjoy the best of all worlds -- both the consistency which comes
-from being based on the <code class="shell">tap2deb</code> and the delicate manual
-tweaking of a Debian maintainer, insuring perfect integration with
-Debian.
+For more savvy users, these tools also generate the source package, allowing
+you to modify and polish things which automated software cannot detect (such as
+dependencies or relationships to virtual packages).
 </p>
 
-<h2>tap2rpm</h2>
-
-<p><code class="shell">tap2rpm</code> is similar to <code class="shell">tap2deb</code>, except that
-it generates RPMs for Redhat and other related platforms.</p>
-
 </body></html>
diff --git a/doc/core/howto/tutorial/configuration.xhtml b/doc/core/howto/tutorial/configuration.xhtml
index 70fbf0e..c4e31f7 100644
--- a/doc/core/howto/tutorial/configuration.xhtml
+++ b/doc/core/howto/tutorial/configuration.xhtml
@@ -64,11 +64,12 @@ integration:</p>
 <h3>Red Hat / Mandrake</h3>
 
 <pre class="shell">
-% tap2rpm --type=python finger.tac #[maybe other options needed]
-% sudo rpm -i .build/*.rpm
+% tap2rpm --type=python finger.tac
+% sudo rpm -i *.rpm
 </pre>
 
-<p>Will properly register the tap/tac, init.d scripts, etc. for the given file.</p>
+<p>These packages will properly install and register init.d scripts, etc. for
+the given file.</p>
 
 <p>If it doesn't work on your favorite OS: patches accepted!</p>
 </body>
diff --git a/doc/core/man/tap2rpm.1 b/doc/core/man/tap2rpm.1
index a406c45..cb279b2 100644
--- a/doc/core/man/tap2rpm.1
+++ b/doc/core/man/tap2rpm.1
@@ -7,9 +7,6 @@ tap2rpm \- create RPM packages which wrap .tap files
 .SH DESCRIPTION
 Create a set of RPM/SRPM packages in the current directory
 .TP
-\fB\-u\fR, \fB\--unsigned\fR 
-do not sign the RPM package
-.TP
 \fB\-t\fR, \fB\--tapfile\fR \fI<tapfile>\fR
 Build the application around the given .tap (default twistd.tap)
 .TP
@@ -40,7 +37,7 @@ this being an automatic package created from tap2rpm.
 .TP
 \fB\-m\fR, \fB\--maintainer\fR \fI<maintainer>\fR
 The maintainer, as "Name Lastname <email address>". This will
-go in the meta-files, as well as be used as the id to sign the package.
+go in the meta-files.
 .TP
 \fB\--version\fR
 Output version information and exit.
@@ -48,9 +45,11 @@ Output version information and exit.
 tap2rpm was written by Sean Reifschneider based on tap2deb by Moshe Zadka.
 This man page is heavily based on the tap2deb man page by Moshe Zadka.
 .SH "REPORTING BUGS"
-To report a bug, visit \fIhttp://twistedmatrix.com/bugs/\fR
+To report a bug, visit
+\fIhttp://twistedmatrix.com/trac/wiki/TwistedDevelopment#FilingTickets\fR for more
+information.
 .SH COPYRIGHT
-Copyright \(co 2000-2008 Twisted Matrix Laboratories.
+Copyright \(co 2000-2009 Twisted Matrix Laboratories.
 .br
 This is free software; see the source for copying conditions.  There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/twisted/scripts/tap2rpm.py b/twisted/scripts/tap2rpm.py
index 8a179ee..a3888a7 100755
--- a/twisted/scripts/tap2rpm.py
+++ b/twisted/scripts/tap2rpm.py
@@ -1,7 +1,7 @@
-#  based off the tap2deb.py file
-#  tap2rpm.py built by Sean Reifschneider, <jafo@tummy.com>
+# -*- test-case-name: twisted.scripts.test.test_tap2rpm -*-
 
-#  TODO: need to implement log-file rotation
+# Copyright (c) 2003-2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
 
 import sys, os, shutil, time, glob
 
@@ -88,7 +88,7 @@ Summary:    %(description)s
 Name:       %(rpm_file)s
 Version:    %(version)s
 Release:    1
-Copyright:  Unknown
+License:    Unknown
 Group:      Networking/Daemons
 Source:     %(tarfile_basename)s
 BuildRoot:  /var/tmp/%%{name}-%%{version}-root
@@ -207,11 +207,11 @@ def makeBuildDir(baseDir):
 
 
 ##########
-def run():
+def run(options=None):
     #  parse options
     try:
         config = MyOptions()
-        config.parseOptions()
+        config.parseOptions(options)
     except usage.error, ue:
          sys.exit("%s: %s" % (sys.argv[0], ue))
 
@@ -271,3 +271,8 @@ def run():
     
     #  remove the build directory
     shutil.rmtree(tmp_dir)
+
+    return [
+            os.path.basename(rpm_path),
+            os.path.basename(srpm_path),
+        ]
diff --git a/twisted/scripts/test/test_tap2rpm.py b/twisted/scripts/test/test_tap2rpm.py
new file mode 100644
index 0000000..3544533
--- /dev/null
+++ b/twisted/scripts/test/test_tap2rpm.py
@@ -0,0 +1,281 @@
+# Copyright (c) 2009 Twisted Matrix Laboratories.
+# See LICENSE for details.
+
+"""
+Tests for L{twisted.scripts.tap2rpm}.
+"""
+from os.path import exists
+from twisted.trial.unittest import TestCase, SkipTest
+from twisted.python import log
+from twisted.internet import utils
+from twisted.scripts import tap2rpm
+
+# When we query the RPM metadata, we get back a string we'll have to parse, so
+# we'll use suitably rare delimiter characters to split on. Luckily, ASCII
+# defines some for us!
+RECORD_SEPARATOR = "\x1E"
+UNIT_SEPARATOR   = "\x1F"
+
+
+
+def _makeRPMs(tapfile=None, maintainer=None, protocol=None, description=None,
+        longDescription=None, setVersion=None, rpmfile=None, type_=None):
+    """
+    Helper function to invoke tap2rpm with the given parameters.
+    """
+    args = []
+
+    if not tapfile:
+        tapfile = "dummy-tap-file"
+        handle = open(tapfile, "w")
+        handle.write("# Dummy TAP file\n")
+        handle.close()
+
+    args.extend(["--tapfile", tapfile])
+
+    if maintainer:
+        args.extend(["--maintainer", maintainer])
+    if protocol:
+        args.extend(["--protocol", protocol])
+    if description:
+        args.extend(["--description", description])
+    if longDescription:
+        args.extend(["--long_description", longDescription])
+    if setVersion:
+        args.extend(["--set-version", setVersion])
+    if rpmfile:
+        args.extend(["--rpmfile", rpmfile])
+    if type_:
+        args.extend(["--type", type_])
+
+    return tap2rpm.run(args)
+
+
+
+def _queryRPMTags(rpmfile, taglist):
+    """
+    Helper function to read the given header tags from the given RPM file.
+
+    Returns a Deferred that fires with dictionary mapping a tag name to a list
+    of the associated values in the RPM header. If a tag has only a single
+    value in the header (like NAME or VERSION), it will be returned as a 1-item
+    list.
+
+    Run "rpm --querytags" to see what tags can be queried.
+    """
+
+    # Build a query format string that will return appropriately delimited
+    # results. Every field is treated as an array field, so single-value tags
+    # like VERSION will be returned as 1-item lists.
+    queryFormat = RECORD_SEPARATOR.join([
+            "[%%{%s}%s]" % (tag, UNIT_SEPARATOR) for tag in taglist
+           ])
+
+    def parseTagValues(output):
+        res = {}
+
+        for tag, values in zip(taglist, output.split(RECORD_SEPARATOR)):
+            values = values.strip(UNIT_SEPARATOR).split(UNIT_SEPARATOR)
+            res[tag] = values
+
+        return res
+
+    d = utils.getProcessOutput("rpm",
+            ("-q", "--queryformat", queryFormat, "-p", rpmfile))
+    d.addCallback(parseTagValues)
+    return d
+
+
+
+class TestTap2RPM(TestCase):
+
+
+    def setUp(self):
+        return self._checkForRpmbuild()
+
+
+    def _checkForRpmbuild(self):
+        """
+        tap2rpm requires rpmbuild; skip tests if rpmbuild is not present.
+        """
+        skipMsg = "rpmbuild must be present to test tap2rpm"
+        def skipTestIfNotFound(result):
+            out, err, code = result
+            # If there was a problem running rpmbuild, or it looks like we've
+            # accidentally found some unrelated script with the same name, skip
+            # these tests.
+            if code != 0 or not out.startswith("RPM version"):
+                raise SkipTest(skipMsg)
+
+        def skipTestIfError(failure):
+            # According to the spawnProcess docs, OSError is raised if new
+            # proceses can't be created for some reason. According to #4184,
+            # OSError is raised if Windows can't find the program to be run.
+            failure.trap(OSError)
+            raise SkipTest(skipMsg)
+
+        d = utils.getProcessOutputAndValue("rpmbuild", ("--version",))
+        d.addCallbacks(skipTestIfNotFound, skipTestIfError)
+        return d
+
+
+    def _makeTapFile(self, basename="dummy"):
+        """
+        Makes a temporary .tap file and returns the absolute path.
+        """
+        path = basename + ".tap"
+        handle = open(path, "w")
+        handle.write("# Dummy .tap file")
+        handle.close()
+        return path
+
+
+    def _verifyRPMTags(self, rpmfile, **tags):
+        """
+        Checks the given file has the given tags set to the given values.
+        """
+
+        d = _queryRPMTags(rpmfile, tags.keys())
+        d.addCallback(self.failUnlessEqual, tags)
+        return d
+
+
+    def test_basicOperation(self):
+        """
+        Calling tap2rpm should produce an RPM and SRPM with default metadata.
+        """
+        basename = "frenchtoast"
+
+        # Create RPMs based on a TAP file with this name.
+        rpm, srpm = _makeRPMs(tapfile = self._makeTapFile(basename))
+
+        # Verify the resulting RPMs have the correct tags.
+        d = self._verifyRPMTags(rpm,
+                NAME=["twisted-%s" % (basename,)],
+                VERSION=["1.0"],
+                RELEASE=["1"],
+                SUMMARY=["A TCP server for %s" % (basename,)],
+                DESCRIPTION=["Automatically created by tap2rpm"],
+            )
+        d.addCallback(lambda _: self._verifyRPMTags(srpm,
+                NAME=["twisted-%s" % (basename,)],
+                VERSION=["1.0"],
+                RELEASE=["1"],
+                SUMMARY=["A TCP server for %s" % (basename,)],
+                DESCRIPTION=["Automatically created by tap2rpm"],
+            ))
+
+        return d
+
+
+    def test_protocolOverride(self):
+        """
+        Setting 'protocol' should change the name of the resulting package.
+        """
+        basename = "acorn"
+        protocol = "banana"
+
+        # Create RPMs based on a TAP file with this name.
+        rpm, srpm = _makeRPMs(tapfile = self._makeTapFile(basename),
+                protocol=protocol)
+
+        # Verify the resulting RPMs have the correct tags.
+        d = self._verifyRPMTags(rpm,
+                NAME=["twisted-%s" % (protocol,)],
+                SUMMARY=["A TCP server for %s" % (protocol,)],
+            )
+        d.addCallback(lambda _: self._verifyRPMTags(srpm,
+                NAME=["twisted-%s" % (protocol,)],
+                SUMMARY=["A TCP server for %s" % (protocol,)],
+            ))
+
+        return d
+
+
+    def test_rpmfileOverride(self):
+        """
+        Setting 'rpmfile' should change the name of the resulting package.
+        """
+        basename = "cherry"
+        rpmfile = "donut"
+
+        # Create RPMs based on a TAP file with this name.
+        rpm, srpm = _makeRPMs(tapfile = self._makeTapFile(basename),
+                rpmfile=rpmfile)
+
+        # Verify the resulting RPMs have the correct tags.
+        d = self._verifyRPMTags(rpm,
+                NAME=[rpmfile],
+                SUMMARY=["A TCP server for %s" % (basename,)],
+            )
+        d.addCallback(lambda _: self._verifyRPMTags(srpm,
+                NAME=[rpmfile],
+                SUMMARY=["A TCP server for %s" % (basename,)],
+            ))
+
+        return d
+
+
+    def test_descriptionOverride(self):
+        """
+        Setting 'description' should change the SUMMARY tag.
+        """
+        description="eggplant"
+
+        # Create RPMs based on a TAP file with this name.
+        rpm, srpm = _makeRPMs(tapfile = self._makeTapFile(),
+                description=description)
+
+        # Verify the resulting RPMs have the correct tags.
+        d = self._verifyRPMTags(rpm,
+                SUMMARY=[description],
+            )
+        d.addCallback(lambda _: self._verifyRPMTags(srpm,
+                SUMMARY=[description],
+            ))
+
+        return d
+
+
+    def test_longDescriptionOverride(self):
+        """
+        Setting 'longDescription' should change the DESCRIPTION tag.
+        """
+        longDescription="fig"
+
+        # Create RPMs based on a TAP file with this name.
+        rpm, srpm = _makeRPMs(tapfile = self._makeTapFile(),
+                longDescription=longDescription)
+
+        # Verify the resulting RPMs have the correct tags.
+        d = self._verifyRPMTags(rpm,
+                DESCRIPTION=[longDescription],
+            )
+        d.addCallback(lambda _: self._verifyRPMTags(srpm,
+                DESCRIPTION=[longDescription],
+            ))
+
+        return d
+
+
+    def test_setVersionOverride(self):
+        """
+        Setting 'setVersion' should change the RPM's version info.
+        """
+        version="123.456"
+
+        # Create RPMs based on a TAP file with this name.
+        rpm, srpm = _makeRPMs(tapfile = self._makeTapFile(),
+                setVersion=version)
+
+        # Verify the resulting RPMs have the correct tags.
+        d = self._verifyRPMTags(rpm,
+                VERSION=["123.456"],
+                RELEASE=["1"],
+            )
+        d.addCallback(lambda _: self._verifyRPMTags(srpm,
+                VERSION=["123.456"],
+                RELEASE=["1"],
+            ))
+
+        return d
diff --git a/twisted/topfiles/3292.bugfix b/twisted/topfiles/3292.bugfix
new file mode 100644
index 0000000..b10fccc
--- /dev/null
+++ b/twisted/topfiles/3292.bugfix
@@ -0,0 +1 @@
+The tap2rpm script now works with modern versions of RPM.
