OPENAFS-SA-2018-001, OPENAFS-SA-2018-002, and OPENAFS-SA-2018-003.
Add the signed advisory texts, patches, and appropriate notes
on the main security page.
Change-Id: I033d3c524f28736e66abf380558750e98049a094
--- /dev/null
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+OpenAFS Security Advisory 2018-001
+
+Topic: Volume-level data replacement via unauthenticated butc connections
+
+Issued: 11 September, 2018
+Affected: OpenAFS server versions 1.0 through 1.6.22.4, and 1.8.0 through 1.8.1.1
+
+The backup tape controller process accepts incoming RPCs but does not
+require (or allow for) authentication of those RPCs. Handling those RPCs
+results in operations being performed with administrator credentials,
+including dumping/restoring volume contents and manipulating the backup
+database.
+
+SUMMARY
+=======
+
+Initial versions of the AFS protocol were developed with no authentication
+at all, and at the time that the in-tree backup system was developed, all
+volume operations were performed unauthenticated. This meant that the
+backup tape controller (butc) was also performing unauthenticated volume
+operations when dumping/restoring volumes to/from tape. Eventually,
+authentication was introduced throughout the protocol, and the model for
+butc became that butc runs locally on the device with the tape drive, and
+is run by a user with administrative tokens (to perform those volume
+operations) or to authenticate with the cell-wide key (via -localauth).
+However, because "the machine with the tape drive" was not expected to be
+a regular fileserver, the architecture did not assume that butc would have
+access to a stable long-term key, and so butc remained in the original state
+of not performing authentication for incoming RPCs. Because butc still must
+run with some form of administrative credentials in order to perform volume
+operations, it is in effect by design a mechanism for privilege escalation,
+allowing unauthenticated users to perform various actions with administrative
+privilege.
+
+IMPACT
+======
+
+Any cell with a butc process that is accessible to untrusted users is
+vulnerable. The in-tree backup suite is not believed to be in common
+use, so in that sense the impact is limited. Applying a strict firewall
+policy can also reduce the exposure of the butc to untrusted input and
+provide some level of remediation. However, for vulnerable cells, the
+impact is quite severe: an unauthenticated, anonymous attacker can
+create volume dumps with contents of their own choosing, create and restore
+(potentially modified) backup database contents, and restore volumes from
+those modified backup database. These restored volumes can be at different
+paths/names than the corresponding dumps were created from (and can be
+restored on top of existing volumes), and the backup database can be restored
+to a pristene state after volume modifications. Taken together, this allows
+the attacker to silently replace the contents of arbitrary volumes, leaving
+the backup database unchanged. (Volume-header timestamps would still provide
+some indication of when a volume was changed, though this is difficult to
+distinguish from normal operations.)
+
+AFFECTED SOFTWARE
+=================
+
+All releases of OpenAFS prior to 1.6.23 are affected, as are OpenAFS
+1.8.0, 1.8.1, and 1.8.1.1.
+
+FIXES
+=====
+
+The OpenAFS project recommends that administrators upgrade all servers
+to the 1.8.2 or 1.6.23 releases. It is necessary to restart the butc
+processes in order for the fixes to take effect.
+
+It may be possible to sufficiently mitigate the risk of attack by using
+a firewall to partition the butc and trusted backup clients from all
+untrusted network traffic. The butc listens on one of a range of ports
+starting at 7025, controlled by the "port offset" parameter. Consult your
+firewall documentation for how to block unwanted traffic.
+
+DETAILS
+=======
+
+The backup tape controller (butc) is a program that runs locally on the
+machine with the tape drive being used for backups (or, if the XBSA TSM
+integration is in use, with the TSM credentials). It must function as
+both an Rx client (to interact with the backup database and volume
+and volume location servers), and as an Rx server (to accept commands for
+what to backup and/or restore). In modern deployments, modifying the
+backup database and performing volume dumps/restores require administrative
+privilege, which can be supplied either via a user's token or the cell-wide
+key (using butc -localauth). However, because only the cell-wide key can
+be used to authenticate incoming RPCs, and it was envisioned that butc would
+be run using a user's token (which is potentially less trusted than the
+cell-wide key, and more auditable), no facility was provided to authenticate
+the incoming RPCs to the butc.
+
+The butc, acting as a privilege escalation service from unauthenticated
+inbound RPCs to privileged administrative operations, allows for a great
+deal of operations, including snapshotting/restoring the backup database,
+dumping volume contents to the local tape device, restoring contents from
+the local tape device to arbitrary volume names, and the like. There are
+many damaging workflows available to an attacker given this privilege
+escalation service, too many to reasonably enumerate here. Perhaps the
+most damaging is:
+
+ 0. Wait until there are no active backups
+ 1. Save the current backup database
+ 2. Write one or more volume dumps containing arbitrary contents to the tape device
+ 3. Restore the volume dump just written to a volserver potentially overwriting
+ the original volume contents
+ 4. Erase the dump from the tape device
+ 5. Restore the backup database to its prior state removing the history
+
+which effectively allows for the (unauthenticated) attacker to replace the
+contents of an arbitrary volume with attacker-controlled data, leaving no
+traces of the changes in the backup system's state.
+
+In modern deployments, the division between using a user's token and using
+the cell-wide key to authenticate the butc's outgoing connections has largely
+evaporated, with nearly all sites running butc with access to the cell-wide
+key. Combining this fact with the internet security model of an untrusted
+network, it is clear that the cell-wide key should be used to authenticate
+the incoming connections to the butc in the vast majority of cases.
+The patch for this vulnerability in effect makes this the default behavior --
+if the -localauth flag is used, authentication will be required for incoming
+connections, and if the -localauth flag is not used, the butc will by default
+not start, since that is an insecure configuration. The new butc
+- -allow_unauthenticated flag will revert either case back to the (insecure)
+historical behavior.
+
+ACKNOWLEDGMENTS
+===============
+
+Thanks to Jeffrey Altman for the detailed report and suggested
+mitigation strategies.
+-----BEGIN PGP SIGNATURE-----
+
+iQG3BAEBCgAdFiEE2WGV4E2ARf9BYP0XKNmm82TrdRIFAluX/SUACgkQKNmm82Tr
+dRKNAgwghH0AoFXn81vTYePxdSbw+dpsPRQKjbhZ9DenJxsyGhYRO5Hhn1LNwsu7
+TQc1aoFLc8GTjKL4NGt3W/apyFZoxyhf6bTgZ6mVE/ISJSBDIktli8us092xQvJB
+W9AtOrSjkNQ0y2ZsBjtqaUQVgyHzVo8idwxkjdmHqfGP15THBrqRgt20OZM4xFPz
+3vDHI1yhm6JBTE7fBmzvXuM4Z694UUaVoFxNPwJXamPpW7TFOQ5WIQYz/zJjopUG
+7nGtMccfuRpDYC26mesiSWAl6d+tcIAnMxUo0lv58Vj5hSHizcCcNwHcOx88FfL2
+nGZeJHxoOsAAb6ZPOe98x/o/eYG+T4Ep9fiREvfhh9piyrprF5BS+moflFQdQr+3
+DPh6v9E4BSG4mOSPRXKnII4uuU4Pou8s44WUlqqs6h+VS0isHSl3xWgZZGgEbnJ5
+vMs7Ilz18AKZ33xsdUFlhUoI+pXK3RcdGhETWGa6cpTJknBLjhL2RgG882+7xJ8c
+U8Yiwxrq4mgJXQ==
+=pv5u
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+OpenAFS Security Advisory-2018-002
+
+Issued: 11 September, 2018
+Topic: information leakage from uninitialized RPC output variables
+Affected: OpenAFS client versions 1.0 through 1.6.22.4 and 1.8.0 through 1.8.1.1
+ OpenAFS server versions 1.0 through 1.6.22.4 and 1.8.0 through 1.8.1.1
+
+Several RPC server routines did not fully initialize their output variables
+before returning, leaking memory contents from both the stack and the heap.
+Because the OpenAFS cache manager functions as an Rx server for the AFSCB service,
+clients are also susceptible to information leakage.
+
+SUMMARY
+=======
+
+OpenAFS uses the Rx RPC protocol for all remote operations, and uses the
+rxgen utility to generate the server RPC stubs. Output variables are
+allocated either on the stack (for scalars) or on the heap, by the RPC
+handler (for variable-length arrays). Many RPC handlers did not fully
+initialize these output variables, leaking memory contents to the remote
+caller for otherwise-successful RPCs.
+
+IMPACT
+======
+
+Many of the affected RPCs do not require authentication (and are simple
+"data lookup" functions), so an unauthenticated attacker could obtain
+tens to hundreds of bytes of memory contents from the affected process,
+per RPC call. Over time, this may be sufficient to build up a picture
+of interesting data, such as file or datbase contents, but the amount and
+rate of data exposure is highly context dependent.
+
+AFFECTED SOFTWARE
+=================
+
+All releases of OpenAFS prior to 1.6.23 are affected, as are OpenAFS
+1.8.0, 1.8.1, and 1.8.1.1.
+
+FIXES
+=====
+
+The OpenAFS project recommends that administrators upgrade all clients
+and servers to the 1.8.2 or 1.6.23 releases. It is necessary to restart
+affected processes in order for the fixes to take effect.
+
+DETAILS
+=======
+
+Only successful RPCs will trigger the information leakage; unsuccessful
+RPCs are replied to with Rx abort packets that do not include the output
+variable contents. For RPCs that require authentication, this limits
+the scope of information disclosure, but many of the following RPCs
+do not require any form of authentication and will leak information even
+to anonymous callers.
+
+STC_ReadLabel does not initialize the 32-bit taskId output parameter.
+
+VOTE_Debug and VOTE_XDebug (udebug) both leave a single field
+uninitialized if there is no current transaction. This leaks the memory
+contents of the ubik server over the wire:
+ struct ubik_debug
+ - 4 bytes in member writeTrans
+
+KAM_ListEntry (kas list) does not initialize its output correctly. It
+leaks kaserver memory contents over the wire:
+ struct kaindex
+ - up to 64 bytes for member name
+ - up to 64 bytes for member instance
+
+TC_ScanStatus (backup status) and TC_GetStatus (internal backup status
+watcher) do not initialize their output buffers. They leak memory
+contents over the wire:
+ struct tciStatusS
+ - up to 64 bytes in member taskName (TC_MAXNAMELEN 64)
+ - up to 64 bytes in member volumeName (TC_MAXNAMELEN 64)
+
+TC_ReadLabel (backup readlabel) does not initialize its output buffer
+completely. It leaks butc memory contents over the wire:
+ struct tc_tapeLabel
+ - up to 32 bytes from member afsname (TC_MAXTAPELEN 32)
+ - up to 32 bytes from member pname (TC_MAXTAPELEN 32)
+
+The following budb RPCs do not initialize their output correctly.
+This leaks buserver memory contents over the wire:
+ BUDB_FindLatestDump (backup dump)
+ BUDB_FindDump (backup volrestore, diskrestore, volsetrestore)
+ BUDB_GetDumps (backup dumpinfo)
+ BUDB_FindLastTape (backup dump)
+
+ struct budb_dumpEntry
+ - up to 32 bytes in member volumeSetName
+ - up to 256 bytes in member dumpPath
+ - up to 32 bytes in member name
+ - up to 32 bytes in member tape.tapeServer
+ - up to 32 bytes in member tape.format
+ - up to 256 bytes in member dumper.name
+ - up to 128 bytes in member dumper.instance
+ - up to 256 bytes in member dumper.cell
+
+RXAFSCB_TellMeAboutYourself does not completely initialize its output
+buffers. This leaks kernel memory over the wire:
+ struct interfaceAddr
+
+ Unix cache manager (libafs)
+ - up to 124 bytes in array addr_in ((AFS_MAX_INTERFACE_ADDR 32 * 4) - 4))
+ - up to 124 bytes in array subnetmask (the same)
+ - up to 124 bytes in array mtu (the same)
+
+ Windows cache manager
+ - 64 bytes in array addr_in ((AFS_MAX_INTERFACE_ADDR 32-CM_MAXINTERFACE_ADDR 16)* 4)
+ - 64 bytes in array subnetmask (the same)
+ - 64 bytes in array mtu (the same)
+
+RXAFSCB_GetLock (cmdebug) does not correctly initialize its output.
+This leaks kernel memory over the wire:
+ struct AFSDBLock
+ - up to 14 bytes for member name (16 - '<cellname>\0')
+
+PR_ListEntries (pts listentries) does not properly initialize its output
+buffers. This leaks ptserver memory over the wire:
+ struct prlistentries
+ - up to 62 bytes for each entry name (PR_MAXNAMELEN 64 - 'a\0')
+
+AFSVolMonitor (vos status) does not properly initialize its output
+buffers. This leaks information from volserver memory:
+ struct transDebugInfo
+ - up to 29 bytes in member lastProcName (30-'\0')
+ - 16 bytes in members readNext, tranmitNext, lastSendTime,
+ lastReceiveTime
+
+AFSVolPartitionInfo and AFSVolPartitionInfo64 (vos partinfo) do not
+properly initialize their reply buffers. This leaks the contents of
+volserver memory over the wire:
+ AFSVolPartitionInfo (struct diskPartition)
+ - up to 24 bytes in member name (32-'/vicepa\0'))
+ - up to 12 bytes in member devName (32-'/vicepa/Lock/vicepa\0'))
+
+ AFSVolPartitionInfo64 (struct diskPartition64)
+ - up to 248 bytes in member name (256-'/vicepa\0'))
+ - up to 236 bytes in member devName (256-'/vicepa/Lock/vicepa\0')
+
+PR_IDToName does not completely initialize the return array of names,
+and thus leaks information from ptserver memory:
+ - up to 62 bytes per requested id (PR_MAXNAMELEN 64 - 'a\0')
+
+ACKNOWLEDGMENTS
+===============
+
+Thanks to Mark Vitale for the detailed report and patches.
+-----BEGIN PGP SIGNATURE-----
+
+iQG3BAEBCgAdFiEE2WGV4E2ARf9BYP0XKNmm82TrdRIFAluX/TYACgkQKNmm82Tr
+dRK0nwwgtW7cPNtKHwUl274oPIkTMXQScaOc2dScUN1nORP3LVCnp6wv9nXvaQCC
+E0bhfhRbvlK8Ifmmo4hzN2UDgeah318KL46DB/MP76TU4Lpgoic0vhHb6UlNvTWV
+F1x6nZbbaiOnd4xWw9gf1/oZfZMxqj02GVAvHNSB3Ks3ORv3ZQ6LZUQsXBdpOO0d
+8RQ9VO7Dpza+2CTlIbmjcAnp51Pg3+R/sSrU3NwO1tqjpVs3A418T1XqDKKuXodW
+kzkjT/78AnIst0zm7vYTFGH2m5eagYy62BrRNnNd8+KvpLZm20O5SG8Q4qJX2/Ht
+vHE5F5rPdE6H1GL1kCJlX98E3O+v2KLjsuaY2W3CbJGtEeG3tpvwbmiAcTKWdFJj
+VcMMq30bIEQa5v8jNs8GhvVsvSGecvDy/kNXzWJSt/CliCxCs/8bnQ83OSntbx34
+vcKY6Cr+5/HDK3dlOgf9XhgKbKxNRly0bNU5JKf0cxDn8ouCa1VGWJIcb90jf8t9
+hfEJnxe1Dg6/AQ==
+=rTMI
+-----END PGP SIGNATURE-----
--- /dev/null
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA512
+
+OpenAFS Security Advisory-2018-003
+
+Issued: 11 September, 2018
+Topic: Denial of service due to excess resource consumption
+Affected: OpenAFS server versions 1.0 through 1.6.22.4 and 1.8.0 through 1.8.1.1
+
+Several data types used as RPC input variables were implemented as unbounded
+array types, limited only by the inherent 32-bit length field to 4GB.
+An unauthenticated attacker could send, or claim to send, large input
+values and consume server resources waiting for those inputs, denying service
+to other valid connections.
+
+SUMMARY
+=======
+
+OpenAFS uses the Rx RPC protocol for all remote operations; RPC inputs
+and outputs are described using the XDR data description language. XDR
+includes a facility for variable-length (up to 4GB) arrays, optionally
+subject to a smaller bound on their size. For several RPCs, OpenAFS
+servers would accept and attempt to unmarshall into memory input arrays
+up to the full 4GB limit; length and authentication checks in RPC handler
+functions would be applied only after low-level memory allocation and
+unmarshalling. This allows unauthenticated attackers to consume large
+amounts of server memory and network bandwidth, producing service degradation
+or denial of service to legitimate clients.
+
+IMPACT
+======
+
+In general, an unauthenticated attacker can cause the affected server process
+to allocate up to the maximum malloc size, or 4GB, whichever is smaller, per
+RPC. Some systems will use memory overcommit and not actually allocate the
+full storage request at malloc time, in which case the attacker can send
+junk data contents but trickle off the flow of data towards the end, effectively
+causing the full allocation to be made and stalled. By forcing such allocations
+but keeping the connection open, the attacker can consume large amounts of
+memory on the server and cause a denial of service condition. There is no
+particular amplifcation on the network bandwidth consumption other than that
+caused by keeping multiple connections open for bulk data transfer, but since
+the bandwidth consumption is prior to any authentication checks or rate limiting
+of failed RPCs, it may still present an impact in some operational environments.
+
+AFFECTED SOFTWARE
+=================
+
+All releases of OpenAFS prior to 1.6.23 are affected, as are OpenAFS
+1.8.0, 1.8.1, and 1.8.1.1.
+
+FIXES
+=====
+
+The OpenAFS project recommends that administrators upgrade all servers
+to the 1.8.2 or 1.6.23 releases. It is necessary to restart
+affected processes in order for the fixes to take effect.
+
+DETAILS
+=======
+
+The Rx RPCs used by OpenAFS include marshalling the XDR input types on
+the client into a stream of data over the network; the corresponding
+RPC server is responsible for unmarshalling the stream of bytes from the
+network into the corresponding data types/structures on the server. This
+unmarshalling occurs at a very low level and must happen before any
+application-level RPC handler code is run, since the RPC handler expects
+to be called with the assembled C data types as arguments. Authentication
+and certain sanity checking of input values is performed in the RPC handler
+layer, after data has been unmarshalled into memory. An attacker can use
+an Rx connection with the "null" security class (so no Rx-layer authentication
+requirement) to cause the unmarshalling to occur in an unauthenticated
+context, since enforcement of "authentication required" RPCs occurs in the
+application-layer RPC handler. As such, the limits in the XDR data description
+for the RPC inputs are the only limit on how much resources the low-level
+decoder will consume in processing the RPC. When an XDR unbounded array type
+is used, the resulting resource consumption can be quite large, even if the
+call is doomed to fail once control passes into the application's RPC handler.
+The attacker can continue responding to Rx keepalives while failing to send
+the final amount of data, in order to keep control flow in the low-level
+unmarshalling code for minutes while still consuming the large amount of memory.
+Even for data types for which there is no abstract limit on the amount of
+input data, implementations should place an arbitrary implementation-specific
+limit (that could be raised later as necessary) on input arrays, in order to
+limit the scope of this denial of service attack.
+
+The affected RPCs are as follows:
+
+AFSVolCreateVolume, AFSVolClone, AFSVolSignalRestore, and AFSVolSetIdsTypes
+accepted input volume names as unbounded string arrays, and
+AFSVolPartitionInfo and AFSVolPartitionInfo64 accepted partition names
+as unbounded string arrays.
+
+AFSVolForwardMultiple accepted an unbounded array of (replica) destinations,
+even though OpenAFS does not support more than 13 replicas in its database
+per volume.
+
+BUDB_SaveText accepted an unbounded character list array as input, even though
+all in-tree consumers are limited to about 2KB at most.
+
+VL_RegisterAddrs accepted an unbounded array of fileserver addresses.
+
+ACKNOWLEDGMENTS
+===============
+
+Thanks to Mark Vitale for the detailed report and patches.
+-----BEGIN PGP SIGNATURE-----
+
+iQG3BAEBCgAdFiEE2WGV4E2ARf9BYP0XKNmm82TrdRIFAluX/TwACgkQKNmm82Tr
+dRI1dAwfXCFDC6APwDkjIp3/uP6hHSxcbYl1ehKJn5oamhIKYUz1s3UNTaAoa0P+
+mDgd6xQ5itUi7X2JS5Ip8RWU6L2YsCgGXoEyIw0qgJF8tDT+qb2JAQQJ3AjtV8qP
+ehJxYEJ8OoIoVRjsL6dKNSgy2PfqbjEmgTgUbSTggQewe7FUNB6F90ELfAOdkyRD
+wSH6gPXxXKalRP5XdEP0ctWq9f8p6nyEAWdMADxL+Wg0nykCb7dEhw+ktmEZ3IJi
+yiPeqTF6G5Pj9S93WW3N+DD7qBjh/8bJGQKeFmUe2F+vKX/oQ1woJS8ww8ZxgGu2
+5mH1VglfCgg3Ibdg9ODKLb2HNFD8dlJT7CHlm581ScWbQZta9Mp2TjiTZphc8qlZ
++8XaT8OHYhArEir7InpcLhnNmVzveGCp4foL5RLvp2D3d52iCsOVmpVlxQQLM/Hl
+mJ3EcsapC8+2Fig8NQNG5c1BTdEpqpjeSeX2ockDFH43Y5i+s8P5r/NqlKgfuKW6
+gEazcle9sUsU+g==
+=LvrE
+-----END PGP SIGNATURE-----
with PGP.
<hr>
+<a name="OPENAFS-SA-2018-003"></a>
+<h2>OPENAFS-SA-2018-003 - denial of service due to excess resource consumption</h2>
+<table border=0>
+<tr><th align="left">Issued:</th><td>11-Sep-2018</td></tr>
+<tr><th align="left">Last Update:</th><td>11-Sep-2018</td></tr>
+<tr><th align="left">Severity:</th><td>Medium</td></tr>
+<tr><th align="left">Affected:</th><td>
+ OpenAFS server versions 1.0 through 1.6.22.4, 1.8.0 through 1.8.1.1 .</td></tr>
+<tr><th align="left">Patch:</th><td>
+ <a href="openafs-sa-2018-003-stable16.patch">
+ https://www.openafs.org/security/openafs-sa-2018-003-stable16.patch</a><br>
+</td><td>
+ <a href="openafs-sa-2018-003-stable18.patch">
+ https://www.openafs.org/security/openafs-sa-2018-003-stable18.patch</a><br>
+</td><td>
+ <a href="openafs-sa-2018-003-master.patch">
+ https://www.openafs.org/security/openafs-sa-2018-003-master.patch</a><br>
+</td></tr>
+<tr><th align="left">Full Text:</th><td>
+ <a href="OPENAFS-SA-2018-003.txt">
+ https://www.openafs.org/security/OPENAFS-SA-2018-003.txt</a>
+</td></tr>
+</table>
+<p>Several data types used as RPC input variables were implemented as unbounded
+array types, limited only by the inherent 32-bit length field to 4GB.
+An unauthenticated attacker could send, or claim to send, large input
+values and consume server resources waiting for those inputs, denying service
+to other valid connections.
+
+<a name="OPENAFS-SA-2018-002"></a>
+<h2>OPENAFS-SA-2018-002 - information leakage from uninitialized RPC output variables</h2>
+<table border=0>
+<tr><th align="left">Issued:</th><td>11-Sep-2018</td></tr>
+<tr><th align="left">Last Update:</th><td>11-Sep-2018</td></tr>
+<tr><th align="left">Severity:</th><td>Medium</td></tr>
+<tr><th align="left">Affected:</th><td>
+ OpenAFS client versions 1.0 through 1.6.22.4, 1.8.0 through 1.8.1.1 ;
+ OpenAFS server versions 1.0 through 1.6.22.4, 1.8.0 through 1.8.1.1 .</td></tr>
+<tr><th align="left">Patch:</th><td>
+ <a href="openafs-sa-2018-002-stable16.patch">
+ https://www.openafs.org/security/openafs-sa-2018-002-stable16.patch</a><br>
+</td><td>
+ <a href="openafs-sa-2018-002-stable18.patch">
+ https://www.openafs.org/security/openafs-sa-2018-002-stable18.patch</a><br>
+</td><td>
+ <a href="openafs-sa-2018-002-master.patch">
+ https://www.openafs.org/security/openafs-sa-2018-002-master.patch</a><br>
+</td></tr>
+<tr><th align="left">Full Text:</th><td>
+ <a href="OPENAFS-SA-2018-002.txt">
+ https://www.openafs.org/security/OPENAFS-SA-2018-002.txt</a>
+</td></tr>
+</table>
+<p>Several RPC server routines did not fully initialize their output variables
+before returning, leaking memory contents from both the stack and the heap.
+Because the OpenAFS cache manager functions as an Rx server for the AFSCB service,
+clients are also susceptible to information leakage.
+
+<a name="OPENAFS-SA-2018-001"></a>
+<h2>OPENAFS-SA-2018-001 - volume-level data replacement via unauthenticated butc connections</h2>
+<table border=0>
+<tr><th align="left">Issued:</th><td>11-Sep-2018</td></tr>
+<tr><th align="left">Last Update:</th><td>11-Sep-2018</td></tr>
+<tr><th align="left">Severity:</th><td>High</td></tr>
+<tr><th align="left">Affected:</th><td>
+ OpenAFS butc server versions 1.0 through 1.6.22.4, 1.8.0 through 1.8.1.1 .</td></tr>
+<tr><th align="left">Patch:</th><td>
+ <a href="openafs-sa-2018-001-stable16.patch">
+ https://www.openafs.org/security/openafs-sa-2018-001-stable16.patch</a><br>
+</td><td>
+ <a href="openafs-sa-2018-001-stable18.patch">
+ https://www.openafs.org/security/openafs-sa-2018-001-stable18.patch</a><br>
+</td><td>
+ <a href="openafs-sa-2018-001-master.patch">
+ https://www.openafs.org/security/openafs-sa-2018-001-master.patch</a><br>
+</td></tr>
+<tr><th align="left">Full Text:</th><td>
+ <a href="OPENAFS-SA-2018-001.txt">
+ https://www.openafs.org/security/OPENAFS-SA-2018-001.txt</a>
+</td></tr>
+</table>
+<p> The backup tape controller process accepts incoming RPCs but does not
+require (or allow for) authentication of those RPCs. Handling those RPCs
+results in operations being performed with administrator credentials,
+including dumping/restoring volume contents and manipulating the backup
+database.
+
<a name="OPENAFS-SA-2017-001"></a>
<h2>OPENAFS-SA-2017-001 - remote triggered rx assertion failure</h2>
<table border=0>
<td>High</td><td>1.0-1.6.21.1</td>
<td><a href="#OPENAFS-SA-2017-001">remotely triggerable assertion failure in Rx</a></td>
</tr>
+<tr>
+ <td>2018-001</td><td>11-Sep-2018</td><td>11-Sep-2018</td>
+ <td>High</td><td>1.0-1.6.22.4,1.8.0-1.8.1.1</td>
+ <td><a href="#OPENAFS-SA-2018-001">volume-level-data-replacement via unauthenticated butc connections</a></td>
+</tr>
+<tr>
+ <td>2018-002</td><td>11-Sep-2018</td><td>11-Sep-2018</td>
+ <td>Medium</td><td>1.0-1.6.22.4,1.8.0-1.8.1.1</td>
+ <td><a href="#OPENAFS-SA-2018-002">information leakage from uninitialized RPC output variables</a></td>
+</tr>
+<tr>
+ <td>2018-002</td><td>11-Sep-2018</td><td>11-Sep-2018</td>
+ <td>Medium</td><td>1.0-1.6.22.4,1.8.0-1.8.1.1</td>
+ <td><a href="#OPENAFS-SA-2018-003">denial of service due to excess resource consumption</a></td>
+</tr>
</table>
--- /dev/null
+diff --git a/Makefile.in b/Makefile.in
+index 5483d77e24..7a5401846a 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -158,7 +158,7 @@ util: opr $(DIR_roken) procmgmt hcrypto lwp
+ libafscp: util afs volser vlserver rx auth fsint
+ +${COMPILE_PART1} libafscp ${COMPILE_PART2}
+
+-audit: util rx rxkad fsint
++audit: util rx rxkad fsint bubasics
+ +${COMPILE_PART1} audit ${COMPILE_PART2} #TODO
+
+ comerr: util $(DIR_roken)
+diff --git a/doc/man-pages/pod8/backup.pod b/doc/man-pages/pod8/backup.pod
+index f8e2ebcc3f..0900fe6ede 100644
+--- a/doc/man-pages/pod8/backup.pod
++++ b/doc/man-pages/pod8/backup.pod
+@@ -194,6 +194,18 @@ interactive mode. The local identity and AFS tokens with which the
+ B<backup> command interpreter enters interactive mode apply to all
+ commands issued during the interactive session.
+
++=item B<-nobutcauth>
++
++Prior to the fix for OPENAFS-SA-2018-001, B<butc> did not allow incoming
++connections to be authenticated. As part of that fix, B<backup> was modified
++to authenticate to the B<butc> services when possible, but a B<backup> utility
++with the security fix will not interoperate with a B<butc> that lacks the fix
++unless this option is passed, which forces the use of unauthenticated
++connections to the B<butc>. Use of this option is strongly disrecommended,
++and it is provided only for backwards compatibility in environments where
++B<backup> and B<butc> communicate over a secure network environment that denies
++access to untrusted parties.
++
+ =item B<-portoffset> <I<TC port offset>>
+
+ Specifies the port offset number of the Tape Coordinator that is to
+diff --git a/doc/man-pages/pod8/butc.pod b/doc/man-pages/pod8/butc.pod
+index 730ce83975..42c72daf25 100644
+--- a/doc/man-pages/pod8/butc.pod
++++ b/doc/man-pages/pod8/butc.pod
+@@ -8,10 +8,14 @@ butc - Initializes the Tape Coordinator process
+ <div class="synopsis">
+
+ B<butc> S<<< [B<-port> <I<port offset>>] >>> S<<< [B<-debuglevel> (0 | 1 | 2)] >>>
+- S<<< [B<-cell> <I<cell name>>] >>> [B<-noautoquery>] [B<-rxbind>] [B<-localauth>] [B<-help>]
++ S<<< [B<-cell> <I<cell name>>] >>> [B<-noautoquery>] [B<-rxbind>] [B<-localauth>]
++ [B<-auditlog> <I<file | sysvmq>> [B<-audit-interface> <I<interface>>]]
++ [B<-allow_unauthenticated>] [B<-help>]
+
+ B<butc> S<<< [B<-p> <I<port offset>>] >>> S<<< [B<-d> (0 | 1 | 2)] >>>
+- S<<< [B<-c> <I<cell name>>] >>> [B<-n>] [B<-r>] [B<-l>] [B<-h>]
++ S<<< [B<-c> <I<cell name>>] >>> [B<-n>] [B<-r>] [B<-l>]
++ [B<-auditl> <I<file | sysvmq>> [-B<-audit-i> <I<interface>>]]
++ [B<-al>] [B<-h>]
+
+ =for html
+ </div>
+@@ -186,6 +190,29 @@ logged on to a server machine as the local superuser C<root>; client
+ machines do not have F</usr/afs/etc/KeyFile> or F</usr/afs/etc/KeyFileExt>
+ files.
+
++=item B<-auditlog> <I<log path>>
++
++Turns on audit logging, and sets the path for the audit log. The audit
++log records information about RPC calls, including the name of the RPC
++call, the host that submitted the call, the authenticated entity (user)
++that issued the call, the parameters for the call, and if the call
++succeeded or failed.
++
++=item B<-audit-interface> <(file | sysvmq)>
++
++Specifies what audit interface to use. Defaults to C<file>. See
++L<fileserver(8)> for an explanation of each interface.
++
++=item B<-allow_unauthenticated>
++
++By default the B<butc> requires clients performing TC_ RPCs to authenticate
++themselves, behavior introduced in the fix for OPENAFS-SA-2018-001.
++This option reverts to the historical behavior of only using the rxnull
++security class for incoming connections. Use of this option is strongly
++disrecommended; it is provided only for backwards compatibility with older
++clients in environments where B<backup> and B<butc> communicate over a secure
++network that denies access to untrusted parties.
++
+ =item B<-help>
+
+ Prints the online help for this command. All other valid options are
+diff --git a/src/audit/audit.c b/src/audit/audit.c
+index 4701d61d1d..1f641f0c52 100644
+--- a/src/audit/audit.c
++++ b/src/audit/audit.c
+@@ -24,6 +24,7 @@
+
+ #include <afs/opr.h>
+ #include "afs/afsint.h"
++#include "afs/butc.h"
+ #include <rx/rx.h>
+ #include <rx/rxkad.h>
+ #include "audit.h"
+@@ -138,6 +139,95 @@ audmakebuf(char *audEvent, va_list vaList)
+ bufferPtr += sizeof(struct AFSFid);
+ break;
+ }
++ /* butc tape label */
++ case AUD_TLBL:
++ {
++ struct tc_tapeLabel *label;
++
++ label = (struct tc_tapeLabel *)va_arg(vaList,
++ struct tc_tapeLabel *);
++ if (label)
++ memcpy(bufferPtr, label, sizeof(*label));
++ else
++ memset(bufferPtr, 0, sizeof(*label));
++ bufferPtr += sizeof(label);
++ break;
++ }
++ /* butc dump interface */
++ case AUD_TDI:
++ {
++ struct tc_dumpInterface *di;
++
++ di = (struct tc_dumpInterface *)
++ va_arg(vaList, struct tc_dumpInterface *);
++ if (di)
++ memcpy(bufferPtr, di, sizeof(*di));
++ else
++ memset(bufferPtr, 0, sizeof(*di));
++ bufferPtr += sizeof(*di);
++ break;
++ }
++ /*
++ * butc dump array
++ * An array of dump descriptions, but the AIX audit package assumes fixed
++ * length, so we can only do the first one for now.
++ */
++ case AUD_TDA:
++ {
++ struct tc_dumpArray *da;
++
++ da = (struct tc_dumpArray *)
++ va_arg(vaList, struct tc_dumpArray *);
++ if (da && da->tc_dumpArray_len) {
++ memcpy(bufferPtr, &da->tc_dumpArray_len, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memcpy(bufferPtr, da->tc_dumpArray_val,
++ sizeof(da->tc_dumpArray_val[0]));
++ } else {
++ memset(bufferPtr, 0, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memset(bufferPtr, 0, sizeof(da->tc_dumpArray_val[0]));
++ }
++ bufferPtr += sizeof(da->tc_dumpArray_val[0]);
++ break;
++ }
++ /*
++ * butc restore array
++ * An array of restore descriptions, but the AIX audit package assumes
++ * fixed length, so we can only do the first one for now.
++ */
++ case AUD_TRA:
++ {
++ struct tc_restoreArray *ra;
++
++ ra = (struct tc_restoreArray *)
++ va_arg(vaList, struct tc_restoreArray *);
++ if (ra && ra->tc_restoreArray_len) {
++ memcpy(bufferPtr, &ra->tc_restoreArray_len, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memcpy(bufferPtr, ra->tc_restoreArray_val,
++ sizeof(ra->tc_restoreArray_val[0]));
++ } else {
++ memset(bufferPtr, 0, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memset(bufferPtr, 0, sizeof(ra->tc_restoreArray_val[0]));
++ }
++ bufferPtr += sizeof(ra->tc_restoreArray_val[0]);
++ break;
++ }
++ /* butc tape controller status */
++ {
++ struct tciStatusS *status;
++
++ status = (struct tciStatusS *)va_arg(vaList,
++ struct tciStatusS *);
++ if (status)
++ memcpy(bufferPtr, status, sizeof(*status));
++ else
++ memset(bufferPtr, 0, sizeof(*status));
++ bufferPtr += sizeof(*status);
++ break;
++ }
+ default:
+ #ifdef AFS_AIX32_ENV
+ code =
+@@ -163,6 +253,11 @@ printbuf(int rec, char *audEvent, char *afsName, afs_int32 hostId,
+ char *vaStr;
+ struct AFSFid *vaFid;
+ struct AFSCBFids *vaFids;
++ struct tc_tapeLabel *vaLabel;
++ struct tc_dumpInterface *vaDI;
++ struct tc_dumpArray *vaDA;
++ struct tc_restoreArray *vaRA;
++ struct tciStatusS *vaTCstatus;
+ int num = LogThreadNum();
+ struct in_addr hostAddr;
+ time_t currenttime;
+@@ -258,6 +353,102 @@ printbuf(int rec, char *audEvent, char *afsName, afs_int32 hostId,
+
+ }
+ break;
++ case AUD_TLBL: /* butc tape label */
++ vaLabel = va_arg(vaList, struct tc_tapeLabel *);
++
++ if (vaLabel) {
++ audit_ops->append_msg("TAPELABEL %d:%.*s:%.*s:%u ",
++ vaLabel->size,
++ TC_MAXTAPELEN, vaLabel->afsname,
++ TC_MAXTAPELEN, vaLabel->pname,
++ vaLabel->tapeId);
++ } else {
++ audit_ops->append_msg("TAPELABEL <null>");
++ }
++ break;
++ case AUD_TDI:
++ vaDI = va_arg(vaList, struct tc_dumpInterface *);
++
++ if (vaDI) {
++ audit_ops->append_msg(
++ "TCDUMPINTERFACE %.*s:%.*s:%.*s:%d:%d:%d:%d:%.*s:%.*s:%d:%d:%d:%d:%d ",
++ TC_MAXDUMPPATH, vaDI->dumpPath, TC_MAXNAMELEN, vaDI->volumeSetName,
++ TC_MAXNAMELEN, vaDI->dumpName, vaDI->parentDumpId, vaDI->dumpLevel,
++ vaDI->doAppend,
++ vaDI->tapeSet.id, TC_MAXHOSTLEN, vaDI->tapeSet.tapeServer,
++ TC_MAXFORMATLEN, vaDI->tapeSet.format, vaDI->tapeSet.maxTapes,
++ vaDI->tapeSet.a, vaDI->tapeSet.b, vaDI->tapeSet.expDate,
++ vaDI->tapeSet.expType);
++ } else {
++ audit_ops->append_msg("TCDUMPINTERFACE <null>");
++ }
++ break;
++ case AUD_TDA:
++ vaDA = va_arg(vaList, struct tc_dumpArray *);
++
++ if (vaDA) {
++ u_int i;
++ struct tc_dumpDesc *desc;
++ struct in_addr hostAddr;
++
++ desc = vaDA->tc_dumpArray_val;
++ if (desc) {
++ audit_ops->append_msg("DUMPS %d ", vaDA->tc_dumpArray_len);
++ for (i = 0; i < vaDA->tc_dumpArray_len; i++, desc++) {
++ hostAddr.s_addr = desc->hostAddr;
++ audit_ops->append_msg("DUMP %d:%d:%.*s:%d:%d:%d:%s ",
++ desc->vid, desc->vtype, TC_MAXNAMELEN, desc->name,
++ desc->partition, desc->date, desc->cloneDate,
++ inet_ntoa(hostAddr));
++ }
++ } else {
++ audit_ops->append_msg("DUMPS 0 DUMP 0:0::0:0:0:0.0.0.0");
++ }
++ }
++ break;
++ case AUD_TRA:
++ vaRA = va_arg(vaList, struct tc_restoreArray *);
++
++ if (vaRA) {
++ u_int i;
++ struct tc_restoreDesc *desc;
++ struct in_addr hostAddr;
++
++ desc = vaRA->tc_restoreArray_val;
++ if (desc) {
++ audit_ops->append_msg("RESTORES %d ",
++ vaRA->tc_restoreArray_len);
++ for(i = 0; i < vaRA->tc_restoreArray_len; i++, desc++) {
++ hostAddr.s_addr = desc->hostAddr;
++ audit_ops->append_msg(
++ "RESTORE %d:%.*s:%d:%d:%d:%d:%d:%d:%d:%s:%.*s:%.*s ",
++ desc->flags, TC_MAXTAPELEN, desc->tapeName,
++ desc->dbDumpId, desc->initialDumpId,
++ desc->position, desc->origVid, desc->vid,
++ desc->partition, desc->dumpLevel,
++ inet_ntoa(hostAddr), TC_MAXNAMELEN,
++ desc->oldName, TC_MAXNAMELEN, desc->newName);
++ }
++ } else {
++ audit_ops->append_msg(
++ "RESTORES 0 RESTORE 0::0:0:0:0:0:0:0:0.0.0.0::: ");
++ }
++ }
++ break;
++ case AUD_TSTT:
++ vaTCstatus = va_arg(vaList, struct tciStatusS *);
++
++ if (vaTCstatus)
++ audit_ops->append_msg("TCSTATUS %.*s:%d:%d:%d:%d:%.*s:%d:%d ",
++ TC_MAXNAMELEN, vaTCstatus->taskName,
++ vaTCstatus->taskId, vaTCstatus->flags,
++ vaTCstatus->dbDumpId, vaTCstatus->nKBytes,
++ TC_MAXNAMELEN, vaTCstatus->volumeName,
++ vaTCstatus->volsFailed,
++ vaTCstatus->lastPolled);
++ else
++ audit_ops->append_msg("TCSTATUS <null>");
++ break;
+ default:
+ audit_ops->append_msg("--badval-- ");
+ break;
+diff --git a/src/audit/audit.h b/src/audit/audit.h
+index f8b4100b5a..6188cf60dd 100644
+--- a/src/audit/audit.h
++++ b/src/audit/audit.h
+@@ -23,6 +23,12 @@
+ #define AUD_RESID 20 /* resid in variable list */
+ #define AUD_RSSIZERANGE 21 /* rssizerange in variable list */
+ #define AUD_LOOKUPINFO 22 /* LookupInfo in variable list */
++/* next 5 lines for butc */
++#define AUD_TLBL 30 /* Tape Controller label */
++#define AUD_TDI 31 /* Tape Controller dump interface */
++#define AUD_TDA 32 /* Tape Controller dump array */
++#define AUD_TRA 33 /* Tape Controller restore array */
++#define AUD_TSTT 34 /* Tape Controller status struct */
+
+ /*
+ * Note: the master definitions of these error codes come from *.et
+@@ -296,6 +302,21 @@
+ #define SREMIORemoteGetHSMdata "AFS_RE_HSMdata"
+ #define SREMIOPrefetch "AFS_RE_Prefetch"
+
++#define TC_StartEvent "AFS_TC_Start"
++#define TC_LabelTapeEvent "AFS_TC_LabelTape"
++#define TC_PerformDumpEvent "AFS_TC_PerformDump"
++#define TC_PerformRestoreEvent "AFS_TC_PerformRestore"
++#define TC_ReadLabelEvent "AFS_TC_ReadLabel"
++#define TC_RestoreDbEvent "AFS_TC_RestoreDb"
++#define TC_SaveDbEvent "AFS_TC_SaveDb"
++#define TC_ScanDumpsEvent "AFS_TC_ScanDumps"
++#define TC_TCInfoEvent "AFS_TC_TCInfo"
++#define TC_DeleteDumpEvent "AFS_TC_DeleteDump"
++#define TC_GetStatusEvent "AFS_TC_GetStatus"
++#define TC_EndStatusEvent "AFS_TC_EndStatus"
++#define TC_RequestAbortEvent "AFS_TC_RequestAbort"
++#define TC_ScanStatusEvent "AFS_TC_ScanStatus"
++
+
+ /* prototypes for audit functions */
+ int osi_audit(char *audEvent, afs_int32 errCode, ...);
+diff --git a/src/bucoord/bucoord_internal.h b/src/bucoord/bucoord_internal.h
+index 543bf00090..7866f47cde 100644
+--- a/src/bucoord/bucoord_internal.h
++++ b/src/bucoord/bucoord_internal.h
+@@ -117,6 +117,8 @@ extern afs_int32 bc_UpdateDumpSchedule(void);
+ extern int bc_SaveDumpSchedule(void);
+
+ /* main.c */
++extern int localauth, nobutcauth;
++extern char tcell[];
+ extern time_t tokenExpires;
+ extern afs_int32 doDispatch(afs_int32, char *[], afs_int32);
+ extern void bc_HandleMisc(afs_int32 code);
+diff --git a/src/bucoord/dump.c b/src/bucoord/dump.c
+index 5d1f58aa3a..e0b3ced57a 100644
+--- a/src/bucoord/dump.c
++++ b/src/bucoord/dump.c
+@@ -17,6 +17,7 @@
+ #include <roken.h>
+
+ #include <afs/cmd.h>
++#include <afs/cellconfig.h>
+ #include <lwp.h>
+ #include <rx/rx.h>
+ #include <afs/bubasics.h>
+@@ -470,15 +471,40 @@ bc_GetConn(struct bc_config *aconfig, afs_int32 aport,
+ struct rx_connection **tconn)
+ {
+ afs_uint32 host;
++ afs_int32 code;
+ unsigned short port;
+ static struct rx_securityClass *rxsc;
++ static afs_int32 scIndex;
+ struct bc_hostEntry *te;
+
+ *tconn = (struct rx_connection *)0;
+
+ /* use non-secure connections to butc */
+- if (!rxsc)
+- rxsc = rxnull_NewClientSecurityObject();
++ if (!rxsc) {
++ struct afsconf_dir *dir;
++ afsconf_secflags flags = AFSCONF_SECOPTS_FALLBACK_NULL;
++ char *cname;
++
++ if (nobutcauth)
++ flags |= AFSCONF_SECOPTS_NOAUTH;
++ if (localauth) {
++ flags |= AFSCONF_SECOPTS_LOCALAUTH;
++ dir = afsconf_Open(AFSDIR_SERVER_ETC_DIRPATH);
++ } else {
++ dir = afsconf_Open(AFSDIR_CLIENT_ETC_DIRPATH);
++ }
++ if (tcell[0] == '\0')
++ cname = NULL;
++ else
++ cname = tcell;
++ /* No need for cell info since butc is not a registered service */
++ code = afsconf_PickClientSecObj(dir, flags, NULL, cname, &rxsc, &scIndex,
++ NULL);
++ if (dir)
++ afsconf_Close(dir);
++ if (code)
++ return -1;
++ }
+ if (!rxsc || !aconfig)
+ return (-1);
+
+@@ -491,8 +517,8 @@ bc_GetConn(struct bc_config *aconfig, afs_int32 aport,
+
+ port = htons(BC_TAPEPORT + aport);
+
+- /* servers is 1; sec index is 0 */
+- *tconn = rx_NewConnection(host, port, 1, rxsc, 0);
++ /* servers is 1 */
++ *tconn = rx_NewConnection(host, port, 1, rxsc, scIndex);
+ return ((*tconn ? 0 : -1));
+ }
+ }
+diff --git a/src/bucoord/main.c b/src/bucoord/main.c
+index 34a3a7926c..d2a5f368bd 100644
+--- a/src/bucoord/main.c
++++ b/src/bucoord/main.c
+@@ -41,7 +41,7 @@
+ #include "bucoord_internal.h"
+ #include "bucoord_prototypes.h"
+
+-int localauth, interact;
++int localauth, interact, nobutcauth;
+ char tcell[64];
+
+ /*
+@@ -292,6 +292,7 @@ MyBeforeProc(struct cmd_syndesc *as, void *arock)
+ /* Handling the command line opcode */
+ if (!bcInit) {
+ localauth = ((as && as->parms[14].items) ? 1 : 0);
++ nobutcauth = ((as && as->parms[16].items) ? 1 : 0);
+ if (as && as->parms[15].items)
+ strcpy(tcell, as->parms[15].items->data);
+ else
+@@ -432,6 +433,8 @@ add_std_args(struct cmd_syndesc *ts)
+ cmd_AddParm(ts, "-localauth", CMD_FLAG, CMD_OPTIONAL,
+ "local authentication");
+ cmd_AddParm(ts, "-cell", CMD_SINGLE, CMD_OPTIONAL, "cell name");
++ cmd_AddParm(ts, "-nobutcauth", CMD_FLAG, CMD_OPTIONAL,
++ "no authentication to butc");
+ }
+
+ int
+diff --git a/src/butc/Makefile.in b/src/butc/Makefile.in
+index 4648f96b7a..5d942222e7 100644
+--- a/src/butc/Makefile.in
++++ b/src/butc/Makefile.in
+@@ -38,6 +38,7 @@ LIBS=${TOP_LIBDIR}/libbudb.a \
+ ${TOP_LIBDIR}/libsys.a \
+ ${TOP_LIBDIR}/librx.a \
+ ${TOP_LIBDIR}/libsys.a \
++ ${TOP_LIBDIR}/libaudit.a \
+ ${TOP_LIBDIR}/liblwp.a \
+ ${TOP_LIBDIR}/libcmd.a \
+ ${TOP_LIBDIR}/libafscom_err.a \
+diff --git a/src/butc/butc_prototypes.h b/src/butc/butc_prototypes.h
+index 1c62c34b4d..85a379963a 100644
+--- a/src/butc/butc_prototypes.h
++++ b/src/butc/butc_prototypes.h
+@@ -32,5 +32,10 @@ extern void *saveDbToTape(void *);
+ extern void *restoreDbFromTape(void *);
+ extern void *KeepAlive(void *);
+
++/* tcmain.c */
++
++extern struct afsconf_dir *butc_confdir;
++extern int allow_unauth;
++
+ #endif
+
+diff --git a/src/butc/tcmain.c b/src/butc/tcmain.c
+index 197fe256a0..c5399396ee 100644
+--- a/src/butc/tcmain.c
++++ b/src/butc/tcmain.c
+@@ -41,6 +41,7 @@
+ #include <afs/keys.h>
+ #include <afs/volser.h>
+ #include <ubik.h>
++#include <afs/audit.h>
+ #include <afs/com_err.h>
+ #include <afs/cmd.h>
+ #include <afs/tcdata.h>
+@@ -105,18 +106,12 @@ afs_int32 BufferSize; /* Size in B stored for data */
+ char *centralLogFile;
+ afs_int32 lastLog; /* Log last pass info */
+ int rxBind = 0;
++struct afsconf_dir *butc_confdir;
++int allow_unauth = 0;
+
+ #define ADDRSPERSITE 16 /* Same global is in rx/rx_user.c */
+ afs_uint32 SHostAddrs[ADDRSPERSITE];
+
+-/* dummy routine for the audit work. It should do nothing since audits */
+-/* occur at the server level and bos is not a server. */
+-int
+-osi_audit(void)
+-{
+- return 0;
+-}
+-
+ static afs_int32
+ SafeATOL(char *anum)
+ {
+@@ -831,11 +826,26 @@ xbsa_shutdown(int x)
+ }
+ #endif
+
++static int
++tc_IsLocalRealmMatch(void *rock, char *name, char *inst, char *cell)
++{
++ struct afsconf_dir *dir = (struct afsconf_dir *)rock;
++ afs_int32 islocal = 0; /* default to no */
++ int code;
++
++ code = afsconf_IsLocalRealmMatch(dir, &islocal, name, inst, cell);
++ if (code) {
++ TLog(0, ("Failed local realm check; code=%d, name=%s, inst=%s, cell=%s\n",
++ code, name, inst, cell));
++ }
++ return islocal;
++}
++
+ static int
+ WorkerBee(struct cmd_syndesc *as, void *arock)
+ {
+- afs_int32 code;
+- struct rx_securityClass *(securityObjects[1]);
++ afs_int32 code, numClasses;
++ struct rx_securityClass *(nullObjects[1]), **secObjs, **allObjs;
+ struct rx_service *service;
+ time_t tokenExpires;
+ char cellName[64];
+@@ -850,6 +860,8 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+ PROCESS dbWatcherPid;
+ #endif
+ afs_uint32 host = htonl(INADDR_ANY);
++ char *auditFileName = NULL;
++ char *auditInterface = NULL;
+
+ debugLevel = 0;
+
+@@ -995,6 +1007,30 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+ }
+ }
+
++ /* Open the configuration directory */
++ butc_confdir = afsconf_Open(AFSDIR_SERVER_ETC_DIRPATH);
++ if (butc_confdir == NULL) {
++ TLog(0, "Failed to open server configuration directory");
++ exit(1);
++ }
++
++ /* Start auditing */
++ osi_audit_init();
++ if (as->parms[9].items) {
++ auditFileName = as->parms[9].items->data;
++ }
++ if (auditFileName != NULL)
++ osi_audit_file(auditFileName);
++ if (as->parms[10].items) {
++ auditInterface = as->parms[10].items->data;
++ if (osi_audit_interface(auditInterface)) {
++ TLog(0, "Invalid audit interface '%s'\n", auditInterface);
++ exit(1);
++ }
++ }
++ osi_audit(TC_StartEvent, 0, AUD_END);
++ osi_audit_set_user_check(butc_confdir, tc_IsLocalRealmMatch);
++
+ if (as->parms[1].items) {
+ debugLevel = SafeATOL(as->parms[1].items->data);
+ if (debugLevel == -1) {
+@@ -1035,6 +1071,13 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+
+ localauth = (as->parms[5].items ? 1 : 0);
+ rxBind = (as->parms[8].items ? 1 : 0);
++ allow_unauth = (as->parms[11].items ? 1 : 0);
++
++ if (!allow_unauth && !localauth) {
++ const char *errstr = "Neither -localauth nor -allow_unauthenticated was provided; refusing to start in unintended insecure configuration\n";
++ TLog(0, "%s", (char *)errstr);
++ exit(1);
++ }
+
+ if (rxBind) {
+ afs_int32 ccode;
+@@ -1079,19 +1122,48 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+
+ /* initialize database support, volume support, and logs */
+
+- /* Create a single security object, in this case the null security
+- * object, for unauthenticated connections, which will be used to control
+- * security on connections made to this server
++ /*
++ * Create security objects for the Rx server functionality. Historically
++ * this was a single rxnull security object, since the tape controller was
++ * run by an operator that had local access to the tape device and some
++ * administrative privilege in the cell (to be able to perform volume-level
++ * accesses), but on a machine that was not necessarily trusted to hold the
++ * cell-wide key.
++ *
++ * Such a configuration is, of course, insecure because anyone can make
++ * inbound RPCs and manipulate the database, including creating bogus
++ * dumps and restoring them! Additionally, in modern usage, butc is
++ * frequently run with -localauth to authenticate its outbound connections
++ * to the volservers and budb with the cell-wide key, in which case the
++ * cell-wide key is present and could be used to authenticate incoming
++ * connections as well.
++ *
++ * If -localauth is in use, create the full barrage of server security
++ * objects, including rxkad, so that inbound connections can be verified
++ * to only be made by authenticated clients. Otherwise, only the rxnull
++ * class is in use with a single server security object. Note that butc
++ * will refuse to start in this configuration unless the
++ * "-allow_unauthenticated" flag is provided, indicating that the operator
++ * has ensured that incoming connections are appropriately restricted by
++ * firewall configuration or network topology.
+ */
+
+- securityObjects[RX_SECIDX_NULL] = rxnull_NewServerSecurityObject();
+- if (!securityObjects[RX_SECIDX_NULL]) {
+- TLog(0, "rxnull_NewServerSecurityObject");
+- exit(1);
++ if (allow_unauth) {
++ nullObjects[RX_SECIDX_NULL] = rxnull_NewServerSecurityObject();
++ if (!nullObjects[RX_SECIDX_NULL]) {
++ TLog(0, "rxnull_NewServerSecurityObject");
++ exit(1);
++ }
++ numClasses = 1;
++ secObjs = nullObjects;
++ } else {
++ /* Must be -localauth, so the cell keys are available. */
++ afsconf_BuildServerSecurityObjects(butc_confdir, &allObjs, &numClasses);
++ secObjs = allObjs;
+ }
+
+ service =
+- rx_NewServiceHost(host, 0, 1, "BUTC", securityObjects, 1, TC_ExecuteRequest);
++ rx_NewServiceHost(host, 0, 1, "BUTC", secObjs, numClasses, TC_ExecuteRequest);
+ if (!service) {
+ TLog(0, "rx_NewService");
+ exit(1);
+@@ -1194,6 +1266,11 @@ main(int argc, char **argv)
+ "Force multiple XBSA server support");
+ cmd_AddParm(ts, "-rxbind", CMD_FLAG, CMD_OPTIONAL,
+ "bind Rx socket");
++ cmd_AddParm(ts, "-auditlog", CMD_SINGLE, CMD_OPTIONAL, "location of audit log");
++ cmd_AddParm(ts, "-audit-interface", CMD_SINGLE, CMD_OPTIONAL,
++ "interface to use for audit logging");
++ cmd_AddParm(ts, "-allow_unauthenticated", CMD_FLAG, CMD_OPTIONAL,
++ "allow unauthenticated inbound RPCs (requires firewalling)");
+
+ /* Initialize dirpaths */
+ if (!(initAFSDirPath() & AFSDIR_SERVER_PATHS_OK)) {
+diff --git a/src/butc/tcprocs.c b/src/butc/tcprocs.c
+index cc19e532d5..c51b1f14d2 100644
+--- a/src/butc/tcprocs.c
++++ b/src/butc/tcprocs.c
+@@ -33,21 +33,41 @@
+ #include "butc_xbsa.h"
+ #include "butc_prototypes.h"
+ #include "butc_internal.h"
++#include "afs/audit.h"
+
+ static int CopyDumpDesc(struct tc_dumpDesc *, tc_dumpArray *);
+ static int CopyRestoreDesc(struct tc_restoreDesc *, tc_restoreArray *);
+ static int CopyTapeSetDesc(struct tc_tapeSet *, struct tc_tapeSet *);
+
++/* Helpers implementing RPC backends */
++static afs_int32 SLabelTape(struct rx_call *acid, struct tc_tapeLabel *label,
++ afs_uint32 *taskId);
++static afs_int32 SPerformDump(struct rx_call *rxCallId,
++ struct tc_dumpInterface *tcdiPtr,
++ tc_dumpArray *tc_dumpArrayPtr, afs_int32 *taskId);
++static afs_int32 SPerformRestore(struct rx_call *acid, char *dumpSetName,
++ tc_restoreArray *arestores, afs_int32 *taskId);
++static afs_int32 SReadLabel(struct rx_call *acid, struct tc_tapeLabel *label,
++ afs_uint32 *taskId);
++static afs_int32 SRestoreDb(struct rx_call *rxCall, afs_uint32 *taskId);
++static afs_int32 SSaveDb(struct rx_call *rxCall, Date archiveTime,
++ afs_uint32 *taskId);
++static afs_int32 SScanDumps(struct rx_call *acid, afs_int32 addDbFlag,
++ afs_uint32 *taskId);
++static afs_int32 STCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr);
++static afs_int32 SDeleteDump(struct rx_call *acid, afs_uint32 dumpID,
++ afs_uint32 *taskId);
++
+ int
+ callPermitted(struct rx_call *call)
+ {
+ /*
+- * Before this code can be used, the rx connection, on the bucoord side,
+- * must be changed so that it will set up for token passing instead of
+- * using a simple rx connection that, below, returns a value of
+- * RX_SECIDX_NULL from rx_SecurityClassOf.
++ * If in backwards compat mode, allow anyone; otherwise, only
++ * superusers are allowed.
+ */
+- return 1;
++ if (allow_unauth)
++ return 1;
++ return afsconf_SuperIdentity(butc_confdir, call, NULL);
+ }
+
+ /* -----------------------------
+@@ -129,6 +149,17 @@ CopyTapeSetDesc(struct tc_tapeSet *toPtr, struct tc_tapeSet *fromPtr)
+
+ afs_int32
+ STC_LabelTape(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SLabelTape(acid, label, taskId);
++ osi_auditU(acid, TC_LabelTapeEvent, code,
++ AUD_TLBL, label, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SLabelTape(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -204,7 +235,20 @@ STC_LabelTape(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *task
+ */
+
+ afs_int32
+-STC_PerformDump(struct rx_call *rxCallId, struct tc_dumpInterface *tcdiPtr, tc_dumpArray *tc_dumpArrayPtr, afs_int32 *taskId)
++STC_PerformDump(struct rx_call *call, struct tc_dumpInterface *di,
++ tc_dumpArray *da, afs_int32 *taskId)
++{
++ afs_int32 code;
++
++ code = SPerformDump(call, di, da, taskId);
++ osi_auditU(call, TC_PerformDumpEvent, code,
++ AUD_TDI, di, AUD_TDA, da, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SPerformDump(struct rx_call *rxCallId, struct tc_dumpInterface *tcdiPtr,
++ tc_dumpArray *tc_dumpArrayPtr, afs_int32 *taskId)
+ {
+ struct dumpNode *newNode = 0;
+ statusP statusPtr = 0;
+@@ -295,7 +339,20 @@ STC_PerformDump(struct rx_call *rxCallId, struct tc_dumpInterface *tcdiPtr, tc_d
+ }
+
+ afs_int32
+-STC_PerformRestore(struct rx_call *acid, char *dumpSetName, tc_restoreArray *arestores, afs_int32 *taskID)
++STC_PerformRestore(struct rx_call *call, char *dumpSetName,
++ tc_restoreArray *ra, afs_int32 *taskId)
++{
++ afs_int32 code;
++
++ code = SPerformRestore(call, dumpSetName, ra, taskId);
++ osi_auditU(call, TC_PerformRestoreEvent, code,
++ AUD_STR, dumpSetName, AUD_TRA, ra, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SPerformRestore(struct rx_call *acid, char *dumpSetName,
++ tc_restoreArray *arestores, afs_int32 *taskID)
+ {
+ struct dumpNode *newNode;
+ statusP statusPtr;
+@@ -368,7 +425,18 @@ STC_PerformRestore(struct rx_call *acid, char *dumpSetName, tc_restoreArray *are
+ }
+
+ afs_int32
+-STC_ReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
++STC_ReadLabel(struct rx_call *call, struct tc_tapeLabel *label, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SReadLabel(call, label, taskId);
++ osi_auditU(call, TC_ReadLabelEvent, code,
++ AUD_TLBL, label, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
+ {
+ afs_int32 code;
+
+@@ -392,7 +460,17 @@ STC_ReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *task
+ */
+
+ afs_int32
+-STC_RestoreDb(struct rx_call *rxCall, afs_uint32 *taskId)
++STC_RestoreDb(struct rx_call *call, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SRestoreDb(call, taskId);
++ osi_auditU(call, TC_RestoreDbEvent, code, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SRestoreDb(struct rx_call *rxCall, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -458,7 +536,18 @@ STC_RestoreDb(struct rx_call *rxCall, afs_uint32 *taskId)
+ */
+
+ afs_int32
+-STC_SaveDb(struct rx_call *rxCall, Date archiveTime, afs_uint32 *taskId)
++STC_SaveDb(struct rx_call *call, Date archiveTime, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SSaveDb(call, archiveTime, taskId);
++ osi_auditU(call, TC_SaveDbEvent, code,
++ AUD_DATE, archiveTime, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SSaveDb(struct rx_call *rxCall, Date archiveTime, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -537,7 +626,18 @@ STC_SaveDb(struct rx_call *rxCall, Date archiveTime, afs_uint32 *taskId)
+ */
+
+ afs_int32
+-STC_ScanDumps(struct rx_call *acid, afs_int32 addDbFlag, afs_uint32 *taskId)
++STC_ScanDumps(struct rx_call *call, afs_int32 addDbFlag, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SScanDumps(call, addDbFlag, taskId);
++ osi_auditU(call, TC_ScanDumpsEvent, code,
++ AUD_INT, addDbFlag, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SScanDumps(struct rx_call *acid, afs_int32 addDbFlag, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -612,7 +712,17 @@ STC_ScanDumps(struct rx_call *acid, afs_int32 addDbFlag, afs_uint32 *taskId)
+ */
+
+ afs_int32
+-STC_TCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr)
++STC_TCInfo(struct rx_call *call, struct tc_tcInfo *ti)
++{
++ afs_int32 code;
++
++ code = STCInfo(call, ti);
++ osi_auditU(call, TC_TCInfoEvent, code, AUD_INT, ti->tcVersion, AUD_END);
++ return code;
++}
++
++static afs_int32
++STCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr)
+ {
+ if (callPermitted(acid) == 0)
+ return (TC_NOTPERMITTED);
+@@ -624,7 +734,18 @@ STC_TCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr)
+ /* STC_DeleteDump
+ */
+ afs_int32
+-STC_DeleteDump(struct rx_call *acid, afs_uint32 dumpID, afs_uint32 *taskId)
++STC_DeleteDump(struct rx_call *call, afs_uint32 dumpID, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SDeleteDump(call, dumpID, taskId);
++ osi_auditU(call, TC_DeleteDumpEvent, code,
++ AUD_DATE, dumpID, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SDeleteDump(struct rx_call *acid, afs_uint32 dumpID, afs_uint32 *taskId)
+ {
+ afs_int32 code = TC_BADTASK; /* If not compiled -Dxbsa then fail */
+ #ifdef xbsa
+diff --git a/src/butc/tcstatus.c b/src/butc/tcstatus.c
+index db06b514fe..b67ae1eee4 100644
+--- a/src/butc/tcstatus.c
++++ b/src/butc/tcstatus.c
+@@ -23,6 +23,7 @@
+ #include "butc_internal.h"
+ #include "error_macros.h"
+ #include "butc_xbsa.h"
++#include "afs/audit.h"
+
+ /* tape coordinator - task status management */
+ extern afs_int32 xbsaType;
+@@ -31,6 +32,13 @@ dlqlinkT statusHead;
+ struct Lock statusQueueLock;
+ struct Lock cmdLineLock;
+
++static afs_int32 SGetStatus(struct rx_call *call, afs_uint32 taskId,
++ struct tciStatusS *statusPtr);
++static afs_int32 SEndStatus(struct rx_call *call, afs_uint32 taskId);
++static afs_int32 SRequestAbort(struct rx_call *call, afs_uint32 taskId);
++static afs_int32 SScanStatus(struct rx_call *call, afs_uint32 *taskId,
++ struct tciStatusS *statusPtr, afs_uint32 *flags);
++
+ /* STC_GetStatus
+ * get the status of a task
+ * entry:
+@@ -41,7 +49,19 @@ struct Lock cmdLineLock;
+
+ afs_int32
+ STC_GetStatus(struct rx_call *call, afs_uint32 taskId,
+- struct tciStatusS *statusPtr)
++ struct tciStatusS *status)
++{
++ afs_int32 code;
++
++ code = SGetStatus(call, taskId, status);
++ osi_auditU(call, TC_GetStatusEvent, code,
++ AUD_INT, taskId, AUD_TSTT, status, AUD_END);
++ return code;
++}
++
++static afs_int32
++SGetStatus(struct rx_call *call, afs_uint32 taskId,
++ struct tciStatusS *statusPtr)
+ {
+ statusP ptr;
+ int retval = 0;
+@@ -71,6 +91,16 @@ STC_GetStatus(struct rx_call *call, afs_uint32 taskId,
+
+ afs_int32
+ STC_EndStatus(struct rx_call *call, afs_uint32 taskId)
++{
++ afs_int32 code;
++
++ code = SEndStatus(call, taskId);
++ osi_auditU(call, TC_EndStatusEvent, code, AUD_INT, taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SEndStatus(struct rx_call *call, afs_uint32 taskId)
+ {
+ statusP ptr;
+ int retval = 0;
+@@ -92,6 +122,16 @@ STC_EndStatus(struct rx_call *call, afs_uint32 taskId)
+
+ afs_int32
+ STC_RequestAbort(struct rx_call *call, afs_uint32 taskId)
++{
++ afs_int32 code;
++
++ code = SRequestAbort(call, taskId);
++ osi_auditU(call, TC_RequestAbortEvent, code, AUD_INT, taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SRequestAbort(struct rx_call *call, afs_uint32 taskId)
+ {
+ statusP ptr;
+ int retval = 0;
+@@ -127,7 +167,19 @@ STC_RequestAbort(struct rx_call *call, afs_uint32 taskId)
+
+ afs_int32
+ STC_ScanStatus(struct rx_call *call, afs_uint32 *taskId,
+- struct tciStatusS *statusPtr, afs_uint32 *flags)
++ struct tciStatusS *status, afs_uint32 *flags)
++{
++ afs_int32 code;
++
++ code = SScanStatus(call, taskId, status, flags);
++ osi_auditU(call, TC_ScanStatusEvent, code,
++ AUD_INT, *taskId, AUD_TSTT, status, AUD_INT, *flags, AUD_END);
++ return code;
++}
++
++static afs_int32
++SScanStatus(struct rx_call *call, afs_uint32 *taskId,
++ struct tciStatusS *statusPtr, afs_uint32 *flags)
+ {
+ statusP ptr = 0;
+ dlqlinkP dlqPtr;
--- /dev/null
+diff --git a/Makefile.in b/Makefile.in
+index 798d4da1c5..448ca7a630 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -157,7 +157,7 @@ util: procmgmt des lwp_depinstall rx_depinstall
+ libafscp: util afs volser vlserver rx auth fsint
+ +${COMPILE_PART1} libafscp ${COMPILE_PART2}
+
+-audit: util rx rxkad fsint
++audit: util rx rxkad fsint bubasics
+ +${COMPILE_PART1} audit ${COMPILE_PART2} #TODO
+
+ comerr: util
+diff --git a/doc/man-pages/pod8/backup.pod b/doc/man-pages/pod8/backup.pod
+index d59be27504..a443e37fc7 100644
+--- a/doc/man-pages/pod8/backup.pod
++++ b/doc/man-pages/pod8/backup.pod
+@@ -192,6 +192,18 @@ interactive mode. The local identity and AFS tokens with which the
+ B<backup> command interpreter enters interactive mode apply to all
+ commands issued during the interactive session.
+
++=item B<-nobutcauth>
++
++Prior to the fix for OPENAFS-SA-2018-001, B<butc> did not allow incoming
++connections to be authenticated. As part of that fix, B<backup> was modified
++to authenticate to the B<butc> services when possible, but a B<backup> utility
++with the security fix will not interoperate with a B<butc> that lacks the fix
++unless this option is passed, which forces the use of unauthenticated
++connections to the B<butc>. Use of this option is strongly disrecommended,
++and it is provided only for backwards compatibility in environments where
++B<backup> and B<butc> communicate over a secure network environment that denies
++access to untrusted parties.
++
+ =item B<-portoffset> <I<TC port offset>>
+
+ Specifies the port offset number of the Tape Coordinator that is to
+diff --git a/doc/man-pages/pod8/butc.pod b/doc/man-pages/pod8/butc.pod
+index 84efbf36c6..363b00aef8 100644
+--- a/doc/man-pages/pod8/butc.pod
++++ b/doc/man-pages/pod8/butc.pod
+@@ -8,10 +8,14 @@ butc - Initializes the Tape Coordinator process
+ <div class="synopsis">
+
+ B<butc> S<<< [B<-port> <I<port offset>>] >>> S<<< [B<-debuglevel> (0 | 1 | 2)] >>>
+- S<<< [B<-cell> <I<cell name>>] >>> [B<-noautoquery>] [B<-localauth>] [B<-help>]
++ S<<< [B<-cell> <I<cell name>>] >>> [B<-noautoquery>] [B<-localauth>]
++ [B<-auditlog> <I<file | sysvmq>> [B<-audit-interface> <I<interface>>]]
++ [B<-allow_unauthenticated>] [B<-help>]
+
+ B<butc> S<<< [B<-p> <I<port offset>>] >>> S<<< [B<-d> (0 | 1 | 2)] >>>
+- S<<< [B<-c> <I<cell name>>] >>> [B<-n>] [B<-l>] [B<-h>]
++ S<<< [B<-c> <I<cell name>>] >>> [B<-n>] [B<-l>]
++ [B<-auditl> <I<file | sysvmq>> [-B<-audit-i> <I<interface>>]]
++ [B<-al>] [B<-h>]
+
+ =for html
+ </div>
+@@ -179,6 +183,29 @@ Do not combine this argument with the B<-cell> flag, and use it only when
+ logged on to a server machine as the local superuser C<root>; client
+ machines do not have F</usr/afs/etc/KeyFile> file.
+
++=item B<-auditlog> <I<log path>>
++
++Turns on audit logging, and sets the path for the audit log. The audit
++log records information about RPC calls, including the name of the RPC
++call, the host that submitted the call, the authenticated entity (user)
++that issued the call, the parameters for the call, and if the call
++succeeded or failed.
++
++=item B<-audit-interface> <(file | sysvmq)>
++
++Specifies what audit interface to use. Defaults to C<file>. See
++L<fileserver(8)> for an explanation of each interface.
++
++=item B<-allow_unauthenticated>
++
++By default the B<butc> requires clients performing TC_ RPCs to authenticate
++themselves, behavior introduced in the fix for OPENAFS-SA-2018-001.
++This option reverts to the historical behavior of only using the rxnull
++security class for incoming connections. Use of this option is strongly
++disrecommended; it is provided only for backwards compatibility with older
++clients in environments where B<backup> and B<butc> communicate over a secure
++network that denies access to untrusted parties.
++
+ =item B<-help>
+
+ Prints the online help for this command. All other valid options are
+diff --git a/src/audit/audit.c b/src/audit/audit.c
+index 17652eeab8..a1e79f7de2 100644
+--- a/src/audit/audit.c
++++ b/src/audit/audit.c
+@@ -31,6 +31,7 @@
+ #include <errno.h>
+
+ #include "afs/afsint.h"
++#include "afs/butc.h"
+ #include <rx/rx.h>
+ #include <rx/rxkad.h>
+ #include "audit.h"
+@@ -152,6 +153,95 @@ audmakebuf(char *audEvent, va_list vaList)
+ bufferPtr += sizeof(struct AFSFid);
+ break;
+ }
++ /* butc tape label */
++ case AUD_TLBL:
++ {
++ struct tc_tapeLabel *label;
++
++ label = (struct tc_tapeLabel *)va_arg(vaList,
++ struct tc_tapeLabel *);
++ if (label)
++ memcpy(bufferPtr, label, sizeof(*label));
++ else
++ memset(bufferPtr, 0, sizeof(*label));
++ bufferPtr += sizeof(label);
++ break;
++ }
++ /* butc dump interface */
++ case AUD_TDI:
++ {
++ struct tc_dumpInterface *di;
++
++ di = (struct tc_dumpInterface *)
++ va_arg(vaList, struct tc_dumpInterface *);
++ if (di)
++ memcpy(bufferPtr, di, sizeof(*di));
++ else
++ memset(bufferPtr, 0, sizeof(*di));
++ bufferPtr += sizeof(*di);
++ break;
++ }
++ /*
++ * butc dump array
++ * An array of dump descriptions, but the AIX audit package assumes fixed
++ * length, so we can only do the first one for now.
++ */
++ case AUD_TDA:
++ {
++ struct tc_dumpArray *da;
++
++ da = (struct tc_dumpArray *)
++ va_arg(vaList, struct tc_dumpArray *);
++ if (da && da->tc_dumpArray_len) {
++ memcpy(bufferPtr, &da->tc_dumpArray_len, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memcpy(bufferPtr, da->tc_dumpArray_val,
++ sizeof(da->tc_dumpArray_val[0]));
++ } else {
++ memset(bufferPtr, 0, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memset(bufferPtr, 0, sizeof(da->tc_dumpArray_val[0]));
++ }
++ bufferPtr += sizeof(da->tc_dumpArray_val[0]);
++ break;
++ }
++ /*
++ * butc restore array
++ * An array of restore descriptions, but the AIX audit package assumes
++ * fixed length, so we can only do the first one for now.
++ */
++ case AUD_TRA:
++ {
++ struct tc_restoreArray *ra;
++
++ ra = (struct tc_restoreArray *)
++ va_arg(vaList, struct tc_restoreArray *);
++ if (ra && ra->tc_restoreArray_len) {
++ memcpy(bufferPtr, &ra->tc_restoreArray_len, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memcpy(bufferPtr, ra->tc_restoreArray_val,
++ sizeof(ra->tc_restoreArray_val[0]));
++ } else {
++ memset(bufferPtr, 0, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memset(bufferPtr, 0, sizeof(ra->tc_restoreArray_val[0]));
++ }
++ bufferPtr += sizeof(ra->tc_restoreArray_val[0]);
++ break;
++ }
++ /* butc tape controller status */
++ {
++ struct tciStatusS *status;
++
++ status = (struct tciStatusS *)va_arg(vaList,
++ struct tciStatusS *);
++ if (status)
++ memcpy(bufferPtr, status, sizeof(*status));
++ else
++ memset(bufferPtr, 0, sizeof(*status));
++ bufferPtr += sizeof(*status);
++ break;
++ }
+ default:
+ #ifdef AFS_AIX32_ENV
+ code =
+@@ -177,6 +267,11 @@ printbuf(int rec, char *audEvent, char *afsName, afs_int32 hostId,
+ char *vaStr;
+ struct AFSFid *vaFid;
+ struct AFSCBFids *vaFids;
++ struct tc_tapeLabel *vaLabel;
++ struct tc_dumpInterface *vaDI;
++ struct tc_dumpArray *vaDA;
++ struct tc_restoreArray *vaRA;
++ struct tciStatusS *vaTCstatus;
+ int num = LogThreadNum();
+ struct in_addr hostAddr;
+ time_t currenttime;
+@@ -273,6 +368,102 @@ printbuf(int rec, char *audEvent, char *afsName, afs_int32 hostId,
+
+ }
+ break;
++ case AUD_TLBL: /* butc tape label */
++ vaLabel = va_arg(vaList, struct tc_tapeLabel *);
++
++ if (vaLabel) {
++ audit_ops->append_msg("TAPELABEL %d:%.*s:%.*s:%u ",
++ vaLabel->size,
++ TC_MAXTAPELEN, vaLabel->afsname,
++ TC_MAXTAPELEN, vaLabel->pname,
++ vaLabel->tapeId);
++ } else {
++ audit_ops->append_msg("TAPELABEL <null>");
++ }
++ break;
++ case AUD_TDI:
++ vaDI = va_arg(vaList, struct tc_dumpInterface *);
++
++ if (vaDI) {
++ audit_ops->append_msg(
++ "TCDUMPINTERFACE %.*s:%.*s:%.*s:%d:%d:%d:%d:%.*s:%.*s:%d:%d:%d:%d:%d ",
++ TC_MAXDUMPPATH, vaDI->dumpPath, TC_MAXNAMELEN, vaDI->volumeSetName,
++ TC_MAXNAMELEN, vaDI->dumpName, vaDI->parentDumpId, vaDI->dumpLevel,
++ vaDI->doAppend,
++ vaDI->tapeSet.id, TC_MAXHOSTLEN, vaDI->tapeSet.tapeServer,
++ TC_MAXFORMATLEN, vaDI->tapeSet.format, vaDI->tapeSet.maxTapes,
++ vaDI->tapeSet.a, vaDI->tapeSet.b, vaDI->tapeSet.expDate,
++ vaDI->tapeSet.expType);
++ } else {
++ audit_ops->append_msg("TCDUMPINTERFACE <null>");
++ }
++ break;
++ case AUD_TDA:
++ vaDA = va_arg(vaList, struct tc_dumpArray *);
++
++ if (vaDA) {
++ u_int i;
++ struct tc_dumpDesc *desc;
++ struct in_addr hostAddr;
++
++ desc = vaDA->tc_dumpArray_val;
++ if (desc) {
++ audit_ops->append_msg("DUMPS %d ", vaDA->tc_dumpArray_len);
++ for (i = 0; i < vaDA->tc_dumpArray_len; i++, desc++) {
++ hostAddr.s_addr = desc->hostAddr;
++ audit_ops->append_msg("DUMP %d:%d:%.*s:%d:%d:%d:%s ",
++ desc->vid, desc->vtype, TC_MAXNAMELEN, desc->name,
++ desc->partition, desc->date, desc->cloneDate,
++ inet_ntoa(hostAddr));
++ }
++ } else {
++ audit_ops->append_msg("DUMPS 0 DUMP 0:0::0:0:0:0.0.0.0");
++ }
++ }
++ break;
++ case AUD_TRA:
++ vaRA = va_arg(vaList, struct tc_restoreArray *);
++
++ if (vaRA) {
++ u_int i;
++ struct tc_restoreDesc *desc;
++ struct in_addr hostAddr;
++
++ desc = vaRA->tc_restoreArray_val;
++ if (desc) {
++ audit_ops->append_msg("RESTORES %d ",
++ vaRA->tc_restoreArray_len);
++ for(i = 0; i < vaRA->tc_restoreArray_len; i++, desc++) {
++ hostAddr.s_addr = desc->hostAddr;
++ audit_ops->append_msg(
++ "RESTORE %d:%.*s:%d:%d:%d:%d:%d:%d:%d:%s:%.*s:%.*s ",
++ desc->flags, TC_MAXTAPELEN, desc->tapeName,
++ desc->dbDumpId, desc->initialDumpId,
++ desc->position, desc->origVid, desc->vid,
++ desc->partition, desc->dumpLevel,
++ inet_ntoa(hostAddr), TC_MAXNAMELEN,
++ desc->oldName, TC_MAXNAMELEN, desc->newName);
++ }
++ } else {
++ audit_ops->append_msg(
++ "RESTORES 0 RESTORE 0::0:0:0:0:0:0:0:0.0.0.0::: ");
++ }
++ }
++ break;
++ case AUD_TSTT:
++ vaTCstatus = va_arg(vaList, struct tciStatusS *);
++
++ if (vaTCstatus)
++ audit_ops->append_msg("TCSTATUS %.*s:%d:%d:%d:%d:%.*s:%d:%d ",
++ TC_MAXNAMELEN, vaTCstatus->taskName,
++ vaTCstatus->taskId, vaTCstatus->flags,
++ vaTCstatus->dbDumpId, vaTCstatus->nKBytes,
++ TC_MAXNAMELEN, vaTCstatus->volumeName,
++ vaTCstatus->volsFailed,
++ vaTCstatus->lastPolled);
++ else
++ audit_ops->append_msg("TCSTATUS <null>");
++ break;
+ default:
+ audit_ops->append_msg("--badval-- ");
+ break;
+diff --git a/src/audit/audit.h b/src/audit/audit.h
+index 7f2c4ca7dd..116d0e2be4 100644
+--- a/src/audit/audit.h
++++ b/src/audit/audit.h
+@@ -23,6 +23,12 @@
+ #define AUD_RESID 20 /* resid in variable list */
+ #define AUD_RSSIZERANGE 21 /* rssizerange in variable list */
+ #define AUD_LOOKUPINFO 22 /* LookupInfo in variable list */
++/* next 5 lines for butc */
++#define AUD_TLBL 30 /* Tape Controller label */
++#define AUD_TDI 31 /* Tape Controller dump interface */
++#define AUD_TDA 32 /* Tape Controller dump array */
++#define AUD_TRA 33 /* Tape Controller restore array */
++#define AUD_TSTT 34 /* Tape Controller status struct */
+
+ /*
+ * Note: the master definitions of these error codes come from *.et
+@@ -295,6 +301,21 @@
+ #define SREMIORemoteGetHSMdata "AFS_RE_HSMdata"
+ #define SREMIOPrefetch "AFS_RE_Prefetch"
+
++#define TC_StartEvent "AFS_TC_Start"
++#define TC_LabelTapeEvent "AFS_TC_LabelTape"
++#define TC_PerformDumpEvent "AFS_TC_PerformDump"
++#define TC_PerformRestoreEvent "AFS_TC_PerformRestore"
++#define TC_ReadLabelEvent "AFS_TC_ReadLabel"
++#define TC_RestoreDbEvent "AFS_TC_RestoreDb"
++#define TC_SaveDbEvent "AFS_TC_SaveDb"
++#define TC_ScanDumpsEvent "AFS_TC_ScanDumps"
++#define TC_TCInfoEvent "AFS_TC_TCInfo"
++#define TC_DeleteDumpEvent "AFS_TC_DeleteDump"
++#define TC_GetStatusEvent "AFS_TC_GetStatus"
++#define TC_EndStatusEvent "AFS_TC_EndStatus"
++#define TC_RequestAbortEvent "AFS_TC_RequestAbort"
++#define TC_ScanStatusEvent "AFS_TC_ScanStatus"
++
+
+ /* prototypes for audit functions */
+ int osi_audit(char *audEvent, afs_int32 errCode, ...);
+diff --git a/src/bucoord/bucoord_internal.h b/src/bucoord/bucoord_internal.h
+index 83622b42c1..d372ac2f24 100644
+--- a/src/bucoord/bucoord_internal.h
++++ b/src/bucoord/bucoord_internal.h
+@@ -119,6 +119,8 @@ struct cmd_parmdesc;
+ extern afs_int32 bc_ParseExpiration(struct cmd_parmdesc *paramPtr,
+ afs_int32 *expType, afs_int32 *expDate);
+ /* main.c */
++extern int localauth, nobutcauth;
++extern char tcell[];
+ extern time_t tokenExpires;
+ extern afs_int32 doDispatch(afs_int32, char *[], afs_int32);
+ extern void bc_HandleMisc(afs_int32 code);
+diff --git a/src/bucoord/dump.c b/src/bucoord/dump.c
+index cf0f474ced..54f0851c7b 100644
+--- a/src/bucoord/dump.c
++++ b/src/bucoord/dump.c
+@@ -17,6 +17,7 @@
+
+ #include <sys/types.h>
+ #include <afs/cmd.h>
++#include <afs/cellconfig.h>
+ #ifdef HAVE_STDINT_H
+ # include <stdint.h>
+ #endif
+@@ -478,15 +479,40 @@ bc_GetConn(struct bc_config *aconfig, afs_int32 aport,
+ struct rx_connection **tconn)
+ {
+ afs_uint32 host;
++ afs_int32 code;
+ unsigned short port;
+ static struct rx_securityClass *rxsc;
++ static afs_int32 scIndex;
+ struct bc_hostEntry *te;
+
+ *tconn = (struct rx_connection *)0;
+
+ /* use non-secure connections to butc */
+- if (!rxsc)
+- rxsc = rxnull_NewClientSecurityObject();
++ if (!rxsc) {
++ struct afsconf_dir *dir;
++ afsconf_secflags flags = AFSCONF_SECOPTS_FALLBACK_NULL;
++ char *cname;
++
++ if (nobutcauth)
++ flags |= AFSCONF_SECOPTS_NOAUTH;
++ if (localauth) {
++ flags |= AFSCONF_SECOPTS_LOCALAUTH;
++ dir = afsconf_Open(AFSDIR_SERVER_ETC_DIRPATH);
++ } else {
++ dir = afsconf_Open(AFSDIR_CLIENT_ETC_DIRPATH);
++ }
++ if (tcell[0] == '\0')
++ cname = NULL;
++ else
++ cname = tcell;
++ /* No need for cell info since butc is not a registered service */
++ code = afsconf_PickClientSecObj(dir, flags, NULL, cname, &rxsc, &scIndex,
++ NULL);
++ if (dir)
++ afsconf_Close(dir);
++ if (code)
++ return -1;
++ }
+ if (!rxsc || !aconfig)
+ return (-1);
+
+@@ -499,8 +525,8 @@ bc_GetConn(struct bc_config *aconfig, afs_int32 aport,
+
+ port = htons(BC_TAPEPORT + aport);
+
+- /* servers is 1; sec index is 0 */
+- *tconn = rx_NewConnection(host, port, 1, rxsc, 0);
++ /* servers is 1 */
++ *tconn = rx_NewConnection(host, port, 1, rxsc, scIndex);
+ return ((*tconn ? 0 : -1));
+ }
+ }
+diff --git a/src/bucoord/main.c b/src/bucoord/main.c
+index 13ddbe43ec..a3b2cc8e29 100644
+--- a/src/bucoord/main.c
++++ b/src/bucoord/main.c
+@@ -54,7 +54,7 @@
+ #include "bucoord_internal.h"
+ #include "bucoord_prototypes.h"
+
+-int localauth, interact;
++int localauth, interact, nobutcauth;
+ char tcell[64];
+
+ /*
+@@ -305,6 +305,7 @@ MyBeforeProc(struct cmd_syndesc *as, void *arock)
+ /* Handling the command line opcode */
+ if (!bcInit) {
+ localauth = ((as && as->parms[14].items) ? 1 : 0);
++ nobutcauth = ((as && as->parms[16].items) ? 1 : 0);
+ if (as && as->parms[15].items)
+ strcpy(tcell, as->parms[15].items->data);
+ else
+@@ -445,6 +446,8 @@ add_std_args(struct cmd_syndesc *ts)
+ cmd_AddParm(ts, "-localauth", CMD_FLAG, CMD_OPTIONAL,
+ "local authentication");
+ cmd_AddParm(ts, "-cell", CMD_SINGLE, CMD_OPTIONAL, "cell name");
++ cmd_AddParm(ts, "-nobutcauth", CMD_FLAG, CMD_OPTIONAL,
++ "no authentication to butc");
+ }
+
+ int
+diff --git a/src/butc/Makefile.in b/src/butc/Makefile.in
+index 078fd6efbb..0a97ba572f 100644
+--- a/src/butc/Makefile.in
++++ b/src/butc/Makefile.in
+@@ -41,6 +41,7 @@ LIBS=${TOP_LIBDIR}/libbudb.a \
+ ${TOP_LIBDIR}/libdes.a \
+ ${TOP_LIBDIR}/librx.a \
+ ${TOP_LIBDIR}/libsys.a \
++ ${TOP_LIBDIR}/libaudit.a \
+ ${TOP_LIBDIR}/liblwp.a \
+ ${TOP_LIBDIR}/libcmd.a \
+ ${TOP_LIBDIR}/libafscom_err.a \
+diff --git a/src/butc/butc_prototypes.h b/src/butc/butc_prototypes.h
+index 1c62c34b4d..85a379963a 100644
+--- a/src/butc/butc_prototypes.h
++++ b/src/butc/butc_prototypes.h
+@@ -32,5 +32,10 @@ extern void *saveDbToTape(void *);
+ extern void *restoreDbFromTape(void *);
+ extern void *KeepAlive(void *);
+
++/* tcmain.c */
++
++extern struct afsconf_dir *butc_confdir;
++extern int allow_unauth;
++
+ #endif
+
+diff --git a/src/butc/tcmain.c b/src/butc/tcmain.c
+index a4cc966417..cf84caba29 100644
+--- a/src/butc/tcmain.c
++++ b/src/butc/tcmain.c
+@@ -42,6 +42,7 @@
+ #include <afs/keys.h>
+ #include <afs/volser.h>
+ #include <ubik.h>
++#include <afs/audit.h>
+ #include <afs/com_err.h>
+ #include <errno.h>
+ #include <afs/cmd.h>
+@@ -107,18 +108,12 @@ afs_int32 BufferSize; /* Size in B stored for data */
+ char *centralLogFile;
+ afs_int32 lastLog; /* Log last pass info */
+ int rxBind = 0;
++struct afsconf_dir *butc_confdir;
++int allow_unauth = 0;
+
+ #define ADDRSPERSITE 16 /* Same global is in rx/rx_user.c */
+ afs_uint32 SHostAddrs[ADDRSPERSITE];
+
+-/* dummy routine for the audit work. It should do nothing since audits */
+-/* occur at the server level and bos is not a server. */
+-int
+-osi_audit(void)
+-{
+- return 0;
+-}
+-
+ static afs_int32
+ SafeATOL(char *anum)
+ {
+@@ -844,8 +839,8 @@ xbsa_shutdown(int x)
+ static int
+ WorkerBee(struct cmd_syndesc *as, void *arock)
+ {
+- afs_int32 code;
+- struct rx_securityClass *(securityObjects[1]);
++ afs_int32 code, numClasses;
++ struct rx_securityClass *(nullObjects[1]), **secObjs, **allObjs;
+ struct rx_service *service;
+ time_t tokenExpires;
+ char cellName[64];
+@@ -860,6 +855,8 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+ PROCESS dbWatcherPid;
+ #endif
+ afs_uint32 host = htonl(INADDR_ANY);
++ char *auditFileName = NULL;
++ char *auditInterface = NULL;
+
+ debugLevel = 0;
+
+@@ -1005,6 +1002,29 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+ }
+ }
+
++ /* Open the configuration directory */
++ butc_confdir = afsconf_Open(AFSDIR_SERVER_ETC_DIRPATH);
++ if (butc_confdir == NULL) {
++ TLog(0, "Failed to open server configuration directory");
++ exit(1);
++ }
++
++ /* Start auditing */
++ osi_audit_init();
++ if (as->parms[9].items) {
++ auditFileName = as->parms[9].items->data;
++ }
++ if (auditFileName != NULL)
++ osi_audit_file(auditFileName);
++ if (as->parms[10].items) {
++ auditInterface = as->parms[10].items->data;
++ if (osi_audit_interface(auditInterface)) {
++ TLog(0, "Invalid audit interface '%s'\n", auditInterface);
++ exit(1);
++ }
++ }
++ osi_audit(TC_StartEvent, 0, AUD_END);
++
+ if (as->parms[1].items) {
+ debugLevel = SafeATOL(as->parms[1].items->data);
+ if (debugLevel == -1) {
+@@ -1045,6 +1065,13 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+
+ localauth = (as->parms[5].items ? 1 : 0);
+ rxBind = (as->parms[8].items ? 1 : 0);
++ allow_unauth = (as->parms[11].items ? 1 : 0);
++
++ if (!allow_unauth && !localauth) {
++ const char *errstr = "Neither -localauth nor -allow_unauthenticated was provided; refusing to start in unintended insecure configuration\n";
++ TLog(0, "%s", (char *)errstr);
++ exit(1);
++ }
+
+ if (rxBind) {
+ afs_int32 ccode;
+@@ -1090,19 +1117,48 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+
+ /* initialize database support, volume support, and logs */
+
+- /* Create a single security object, in this case the null security
+- * object, for unauthenticated connections, which will be used to control
+- * security on connections made to this server
++ /*
++ * Create security objects for the Rx server functionality. Historically
++ * this was a single rxnull security object, since the tape controller was
++ * run by an operator that had local access to the tape device and some
++ * administrative privilege in the cell (to be able to perform volume-level
++ * accesses), but on a machine that was not necessarily trusted to hold the
++ * cell-wide key.
++ *
++ * Such a configuration is, of course, insecure because anyone can make
++ * inbound RPCs and manipulate the database, including creating bogus
++ * dumps and restoring them! Additionally, in modern usage, butc is
++ * frequently run with -localauth to authenticate its outbound connections
++ * to the volservers and budb with the cell-wide key, in which case the
++ * cell-wide key is present and could be used to authenticate incoming
++ * connections as well.
++ *
++ * If -localauth is in use, create the full barrage of server security
++ * objects, including rxkad, so that inbound connections can be verified
++ * to only be made by authenticated clients. Otherwise, only the rxnull
++ * class is in use with a single server security object. Note that butc
++ * will refuse to start in this configuration unless the
++ * "-allow_unauthenticated" flag is provided, indicating that the operator
++ * has ensured that incoming connections are appropriately restricted by
++ * firewall configuration or network topology.
+ */
+
+- securityObjects[0] = rxnull_NewServerSecurityObject();
+- if (!securityObjects[0]) {
+- TLog(0, "rxnull_NewServerSecurityObject");
+- exit(1);
++ if (allow_unauth) {
++ nullObjects[RX_SECIDX_NULL] = rxnull_NewServerSecurityObject();
++ if (!nullObjects[RX_SECIDX_NULL]) {
++ TLog(0, "rxnull_NewServerSecurityObject");
++ exit(1);
++ }
++ numClasses = 1;
++ secObjs = nullObjects;
++ } else {
++ /* Must be -localauth, so the cell keys are available. */
++ afsconf_BuildServerSecurityObjects(butc_confdir, 0, &allObjs, &numClasses);
++ secObjs = allObjs;
+ }
+
+ service =
+- rx_NewServiceHost(host, 0, 1, "BUTC", securityObjects, 1, TC_ExecuteRequest);
++ rx_NewServiceHost(host, 0, 1, "BUTC", secObjs, numClasses, TC_ExecuteRequest);
+ if (!service) {
+ TLog(0, "rx_NewService");
+ exit(1);
+@@ -1205,6 +1261,11 @@ main(int argc, char **argv)
+ "Force multiple XBSA server support");
+ cmd_AddParm(ts, "-rxbind", CMD_FLAG, CMD_OPTIONAL,
+ "bind Rx socket");
++ cmd_AddParm(ts, "-auditlog", CMD_SINGLE, CMD_OPTIONAL, "location of audit log");
++ cmd_AddParm(ts, "-audit-interface", CMD_SINGLE, CMD_OPTIONAL,
++ "interface to use for audit logging");
++ cmd_AddParm(ts, "-allow_unauthenticated", CMD_FLAG, CMD_OPTIONAL,
++ "allow unauthenticated inbound RPCs (requires firewalling)");
+
+ /* Initialize dirpaths */
+ if (!(initAFSDirPath() & AFSDIR_SERVER_PATHS_OK)) {
+diff --git a/src/butc/tcprocs.c b/src/butc/tcprocs.c
+index c1177808c1..25b6d5b2fa 100644
+--- a/src/butc/tcprocs.c
++++ b/src/butc/tcprocs.c
+@@ -46,18 +46,41 @@
+ #include "butc_xbsa.h"
+ #include "butc_prototypes.h"
+ #include "butc_internal.h"
++#include "afs/audit.h"
+
+ static int CopyDumpDesc(struct tc_dumpDesc *, tc_dumpArray *);
+ static int CopyRestoreDesc(struct tc_restoreDesc *, tc_restoreArray *);
+ static int CopyTapeSetDesc(struct tc_tapeSet *, struct tc_tapeSet *);
+
++/* Helpers implementing RPC backends */
++static afs_int32 SLabelTape(struct rx_call *acid, struct tc_tapeLabel *label,
++ afs_uint32 *taskId);
++static afs_int32 SPerformDump(struct rx_call *rxCallId,
++ struct tc_dumpInterface *tcdiPtr,
++ tc_dumpArray *tc_dumpArrayPtr, afs_int32 *taskId);
++static afs_int32 SPerformRestore(struct rx_call *acid, char *dumpSetName,
++ tc_restoreArray *arestores, afs_int32 *taskId);
++static afs_int32 SReadLabel(struct rx_call *acid, struct tc_tapeLabel *label,
++ afs_uint32 *taskId);
++static afs_int32 SRestoreDb(struct rx_call *rxCall, afs_uint32 *taskId);
++static afs_int32 SSaveDb(struct rx_call *rxCall, Date archiveTime,
++ afs_uint32 *taskId);
++static afs_int32 SScanDumps(struct rx_call *acid, afs_int32 addDbFlag,
++ afs_uint32 *taskId);
++static afs_int32 STCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr);
++static afs_int32 SDeleteDump(struct rx_call *acid, afs_uint32 dumpID,
++ afs_uint32 *taskId);
++
+ int
+ callPermitted(struct rx_call *call)
+ {
+- /* before this code can be used, the rx connection, on the bucoord side, must */
+- /* be changed so that it will set up for token passing instead of using a */
+- /* simple rx connection that, below, returns a value of 0 from rx_SecurityClassOf */
+- return 1;
++ /*
++ * If in backwards compat mode, allow anyone; otherwise, only
++ * superusers are allowed.
++ */
++ if (allow_unauth)
++ return 1;
++ return afsconf_SuperUser(butc_confdir, call, NULL);
+ }
+
+ /* -----------------------------
+@@ -139,6 +162,17 @@ CopyTapeSetDesc(struct tc_tapeSet *toPtr, struct tc_tapeSet *fromPtr)
+
+ afs_int32
+ STC_LabelTape(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SLabelTape(acid, label, taskId);
++ osi_auditU(acid, TC_LabelTapeEvent, code,
++ AUD_TLBL, label, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SLabelTape(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -214,7 +248,20 @@ STC_LabelTape(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *task
+ */
+
+ afs_int32
+-STC_PerformDump(struct rx_call *rxCallId, struct tc_dumpInterface *tcdiPtr, tc_dumpArray *tc_dumpArrayPtr, afs_int32 *taskId)
++STC_PerformDump(struct rx_call *call, struct tc_dumpInterface *di,
++ tc_dumpArray *da, afs_int32 *taskId)
++{
++ afs_int32 code;
++
++ code = SPerformDump(call, di, da, taskId);
++ osi_auditU(call, TC_PerformDumpEvent, code,
++ AUD_TDI, di, AUD_TDA, da, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SPerformDump(struct rx_call *rxCallId, struct tc_dumpInterface *tcdiPtr,
++ tc_dumpArray *tc_dumpArrayPtr, afs_int32 *taskId)
+ {
+ struct dumpNode *newNode = 0;
+ statusP statusPtr = 0;
+@@ -310,7 +357,20 @@ STC_PerformDump(struct rx_call *rxCallId, struct tc_dumpInterface *tcdiPtr, tc_d
+ }
+
+ afs_int32
+-STC_PerformRestore(struct rx_call *acid, char *dumpSetName, tc_restoreArray *arestores, afs_int32 *taskID)
++STC_PerformRestore(struct rx_call *call, char *dumpSetName,
++ tc_restoreArray *ra, afs_int32 *taskId)
++{
++ afs_int32 code;
++
++ code = SPerformRestore(call, dumpSetName, ra, taskId);
++ osi_auditU(call, TC_PerformRestoreEvent, code,
++ AUD_STR, dumpSetName, AUD_TRA, ra, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SPerformRestore(struct rx_call *acid, char *dumpSetName,
++ tc_restoreArray *arestores, afs_int32 *taskID)
+ {
+ struct dumpNode *newNode;
+ statusP statusPtr;
+@@ -384,7 +444,18 @@ STC_PerformRestore(struct rx_call *acid, char *dumpSetName, tc_restoreArray *are
+ }
+
+ afs_int32
+-STC_ReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
++STC_ReadLabel(struct rx_call *call, struct tc_tapeLabel *label, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SReadLabel(call, label, taskId);
++ osi_auditU(call, TC_ReadLabelEvent, code,
++ AUD_TLBL, label, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
+ {
+ afs_int32 code;
+
+@@ -408,7 +479,17 @@ STC_ReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *task
+ */
+
+ afs_int32
+-STC_RestoreDb(struct rx_call *rxCall, afs_uint32 *taskId)
++STC_RestoreDb(struct rx_call *call, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SRestoreDb(call, taskId);
++ osi_auditU(call, TC_RestoreDbEvent, code, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SRestoreDb(struct rx_call *rxCall, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -474,7 +555,18 @@ STC_RestoreDb(struct rx_call *rxCall, afs_uint32 *taskId)
+ */
+
+ afs_int32
+-STC_SaveDb(struct rx_call *rxCall, Date archiveTime, afs_uint32 *taskId)
++STC_SaveDb(struct rx_call *call, Date archiveTime, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SSaveDb(call, archiveTime, taskId);
++ osi_auditU(call, TC_SaveDbEvent, code,
++ AUD_DATE, archiveTime, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SSaveDb(struct rx_call *rxCall, Date archiveTime, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -553,7 +645,18 @@ STC_SaveDb(struct rx_call *rxCall, Date archiveTime, afs_uint32 *taskId)
+ */
+
+ afs_int32
+-STC_ScanDumps(struct rx_call *acid, afs_int32 addDbFlag, afs_uint32 *taskId)
++STC_ScanDumps(struct rx_call *call, afs_int32 addDbFlag, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SScanDumps(call, addDbFlag, taskId);
++ osi_auditU(call, TC_ScanDumpsEvent, code,
++ AUD_INT, addDbFlag, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SScanDumps(struct rx_call *acid, afs_int32 addDbFlag, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -628,7 +731,17 @@ STC_ScanDumps(struct rx_call *acid, afs_int32 addDbFlag, afs_uint32 *taskId)
+ */
+
+ afs_int32
+-STC_TCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr)
++STC_TCInfo(struct rx_call *call, struct tc_tcInfo *ti)
++{
++ afs_int32 code;
++
++ code = STCInfo(call, ti);
++ osi_auditU(call, TC_TCInfoEvent, code, AUD_INT, ti->tcVersion, AUD_END);
++ return code;
++}
++
++static afs_int32
++STCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr)
+ {
+ if (callPermitted(acid) == 0)
+ return (TC_NOTPERMITTED);
+@@ -640,7 +753,18 @@ STC_TCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr)
+ /* STC_DeleteDump
+ */
+ afs_int32
+-STC_DeleteDump(struct rx_call *acid, afs_uint32 dumpID, afs_uint32 *taskId)
++STC_DeleteDump(struct rx_call *call, afs_uint32 dumpID, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SDeleteDump(call, dumpID, taskId);
++ osi_auditU(call, TC_DeleteDumpEvent, code,
++ AUD_DATE, dumpID, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SDeleteDump(struct rx_call *acid, afs_uint32 dumpID, afs_uint32 *taskId)
+ {
+ afs_int32 code = TC_BADTASK; /* If not compiled -Dxbsa then fail */
+ #ifdef xbsa
+diff --git a/src/butc/tcstatus.c b/src/butc/tcstatus.c
+index 3f98ba31d5..dcc4d19e7c 100644
+--- a/src/butc/tcstatus.c
++++ b/src/butc/tcstatus.c
+@@ -34,6 +34,8 @@
+
+ #include "error_macros.h"
+ #include "butc_xbsa.h"
++#include "afs/audit.h"
++
+ /* tape coordinator - task status management */
+ extern afs_int32 xbsaType;
+
+@@ -41,6 +43,13 @@ dlqlinkT statusHead;
+ struct Lock statusQueueLock;
+ struct Lock cmdLineLock;
+
++static afs_int32 SGetStatus(struct rx_call *call, afs_uint32 taskId,
++ struct tciStatusS *statusPtr);
++static afs_int32 SEndStatus(struct rx_call *call, afs_uint32 taskId);
++static afs_int32 SRequestAbort(struct rx_call *call, afs_uint32 taskId);
++static afs_int32 SScanStatus(struct rx_call *call, afs_uint32 *taskId,
++ struct tciStatusS *statusPtr, afs_uint32 *flags);
++
+ /* STC_GetStatus
+ * get the status of a task
+ * entry:
+@@ -51,7 +60,19 @@ struct Lock cmdLineLock;
+
+ afs_int32
+ STC_GetStatus(struct rx_call *call, afs_uint32 taskId,
+- struct tciStatusS *statusPtr)
++ struct tciStatusS *status)
++{
++ afs_int32 code;
++
++ code = SGetStatus(call, taskId, status);
++ osi_auditU(call, TC_GetStatusEvent, code,
++ AUD_INT, taskId, AUD_TSTT, status, AUD_END);
++ return code;
++}
++
++static afs_int32
++SGetStatus(struct rx_call *call, afs_uint32 taskId,
++ struct tciStatusS *statusPtr)
+ {
+ statusP ptr;
+ int retval = 0;
+@@ -81,6 +102,16 @@ STC_GetStatus(struct rx_call *call, afs_uint32 taskId,
+
+ afs_int32
+ STC_EndStatus(struct rx_call *call, afs_uint32 taskId)
++{
++ afs_int32 code;
++
++ code = SEndStatus(call, taskId);
++ osi_auditU(call, TC_EndStatusEvent, code, AUD_INT, taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SEndStatus(struct rx_call *call, afs_uint32 taskId)
+ {
+ statusP ptr;
+ int retval = 0;
+@@ -102,6 +133,16 @@ STC_EndStatus(struct rx_call *call, afs_uint32 taskId)
+
+ afs_int32
+ STC_RequestAbort(struct rx_call *call, afs_uint32 taskId)
++{
++ afs_int32 code;
++
++ code = SRequestAbort(call, taskId);
++ osi_auditU(call, TC_RequestAbortEvent, code, AUD_INT, taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SRequestAbort(struct rx_call *call, afs_uint32 taskId)
+ {
+ statusP ptr;
+ int retval = 0;
+@@ -137,7 +178,19 @@ STC_RequestAbort(struct rx_call *call, afs_uint32 taskId)
+
+ afs_int32
+ STC_ScanStatus(struct rx_call *call, afs_uint32 *taskId,
+- struct tciStatusS *statusPtr, afs_uint32 *flags)
++ struct tciStatusS *status, afs_uint32 *flags)
++{
++ afs_int32 code;
++
++ code = SScanStatus(call, taskId, status, flags);
++ osi_auditU(call, TC_ScanStatusEvent, code,
++ AUD_INT, *taskId, AUD_TSTT, status, AUD_INT, *flags, AUD_END);
++ return code;
++}
++
++static afs_int32
++SScanStatus(struct rx_call *call, afs_uint32 *taskId,
++ struct tciStatusS *statusPtr, afs_uint32 *flags)
+ {
+ statusP ptr = 0;
+ dlqlinkP dlqPtr;
--- /dev/null
+diff --git a/Makefile.in b/Makefile.in
+index 064d316aae..eda3914319 100644
+--- a/Makefile.in
++++ b/Makefile.in
+@@ -157,7 +157,7 @@ util: opr $(DIR_roken) procmgmt hcrypto lwp
+ libafscp: util afs volser vlserver rx auth fsint
+ +${COMPILE_PART1} libafscp ${COMPILE_PART2}
+
+-audit: util rx rxkad fsint
++audit: util rx rxkad fsint bubasics
+ +${COMPILE_PART1} audit ${COMPILE_PART2} #TODO
+
+ comerr: util $(DIR_roken)
+diff --git a/doc/man-pages/pod8/backup.pod b/doc/man-pages/pod8/backup.pod
+index f8e2ebcc3f..0900fe6ede 100644
+--- a/doc/man-pages/pod8/backup.pod
++++ b/doc/man-pages/pod8/backup.pod
+@@ -194,6 +194,18 @@ interactive mode. The local identity and AFS tokens with which the
+ B<backup> command interpreter enters interactive mode apply to all
+ commands issued during the interactive session.
+
++=item B<-nobutcauth>
++
++Prior to the fix for OPENAFS-SA-2018-001, B<butc> did not allow incoming
++connections to be authenticated. As part of that fix, B<backup> was modified
++to authenticate to the B<butc> services when possible, but a B<backup> utility
++with the security fix will not interoperate with a B<butc> that lacks the fix
++unless this option is passed, which forces the use of unauthenticated
++connections to the B<butc>. Use of this option is strongly disrecommended,
++and it is provided only for backwards compatibility in environments where
++B<backup> and B<butc> communicate over a secure network environment that denies
++access to untrusted parties.
++
+ =item B<-portoffset> <I<TC port offset>>
+
+ Specifies the port offset number of the Tape Coordinator that is to
+diff --git a/doc/man-pages/pod8/butc.pod b/doc/man-pages/pod8/butc.pod
+index 730ce83975..42c72daf25 100644
+--- a/doc/man-pages/pod8/butc.pod
++++ b/doc/man-pages/pod8/butc.pod
+@@ -8,10 +8,14 @@ butc - Initializes the Tape Coordinator process
+ <div class="synopsis">
+
+ B<butc> S<<< [B<-port> <I<port offset>>] >>> S<<< [B<-debuglevel> (0 | 1 | 2)] >>>
+- S<<< [B<-cell> <I<cell name>>] >>> [B<-noautoquery>] [B<-rxbind>] [B<-localauth>] [B<-help>]
++ S<<< [B<-cell> <I<cell name>>] >>> [B<-noautoquery>] [B<-rxbind>] [B<-localauth>]
++ [B<-auditlog> <I<file | sysvmq>> [B<-audit-interface> <I<interface>>]]
++ [B<-allow_unauthenticated>] [B<-help>]
+
+ B<butc> S<<< [B<-p> <I<port offset>>] >>> S<<< [B<-d> (0 | 1 | 2)] >>>
+- S<<< [B<-c> <I<cell name>>] >>> [B<-n>] [B<-r>] [B<-l>] [B<-h>]
++ S<<< [B<-c> <I<cell name>>] >>> [B<-n>] [B<-r>] [B<-l>]
++ [B<-auditl> <I<file | sysvmq>> [-B<-audit-i> <I<interface>>]]
++ [B<-al>] [B<-h>]
+
+ =for html
+ </div>
+@@ -186,6 +190,29 @@ logged on to a server machine as the local superuser C<root>; client
+ machines do not have F</usr/afs/etc/KeyFile> or F</usr/afs/etc/KeyFileExt>
+ files.
+
++=item B<-auditlog> <I<log path>>
++
++Turns on audit logging, and sets the path for the audit log. The audit
++log records information about RPC calls, including the name of the RPC
++call, the host that submitted the call, the authenticated entity (user)
++that issued the call, the parameters for the call, and if the call
++succeeded or failed.
++
++=item B<-audit-interface> <(file | sysvmq)>
++
++Specifies what audit interface to use. Defaults to C<file>. See
++L<fileserver(8)> for an explanation of each interface.
++
++=item B<-allow_unauthenticated>
++
++By default the B<butc> requires clients performing TC_ RPCs to authenticate
++themselves, behavior introduced in the fix for OPENAFS-SA-2018-001.
++This option reverts to the historical behavior of only using the rxnull
++security class for incoming connections. Use of this option is strongly
++disrecommended; it is provided only for backwards compatibility with older
++clients in environments where B<backup> and B<butc> communicate over a secure
++network that denies access to untrusted parties.
++
+ =item B<-help>
+
+ Prints the online help for this command. All other valid options are
+diff --git a/src/audit/audit.c b/src/audit/audit.c
+index 4701d61d1d..1f641f0c52 100644
+--- a/src/audit/audit.c
++++ b/src/audit/audit.c
+@@ -24,6 +24,7 @@
+
+ #include <afs/opr.h>
+ #include "afs/afsint.h"
++#include "afs/butc.h"
+ #include <rx/rx.h>
+ #include <rx/rxkad.h>
+ #include "audit.h"
+@@ -138,6 +139,95 @@ audmakebuf(char *audEvent, va_list vaList)
+ bufferPtr += sizeof(struct AFSFid);
+ break;
+ }
++ /* butc tape label */
++ case AUD_TLBL:
++ {
++ struct tc_tapeLabel *label;
++
++ label = (struct tc_tapeLabel *)va_arg(vaList,
++ struct tc_tapeLabel *);
++ if (label)
++ memcpy(bufferPtr, label, sizeof(*label));
++ else
++ memset(bufferPtr, 0, sizeof(*label));
++ bufferPtr += sizeof(label);
++ break;
++ }
++ /* butc dump interface */
++ case AUD_TDI:
++ {
++ struct tc_dumpInterface *di;
++
++ di = (struct tc_dumpInterface *)
++ va_arg(vaList, struct tc_dumpInterface *);
++ if (di)
++ memcpy(bufferPtr, di, sizeof(*di));
++ else
++ memset(bufferPtr, 0, sizeof(*di));
++ bufferPtr += sizeof(*di);
++ break;
++ }
++ /*
++ * butc dump array
++ * An array of dump descriptions, but the AIX audit package assumes fixed
++ * length, so we can only do the first one for now.
++ */
++ case AUD_TDA:
++ {
++ struct tc_dumpArray *da;
++
++ da = (struct tc_dumpArray *)
++ va_arg(vaList, struct tc_dumpArray *);
++ if (da && da->tc_dumpArray_len) {
++ memcpy(bufferPtr, &da->tc_dumpArray_len, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memcpy(bufferPtr, da->tc_dumpArray_val,
++ sizeof(da->tc_dumpArray_val[0]));
++ } else {
++ memset(bufferPtr, 0, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memset(bufferPtr, 0, sizeof(da->tc_dumpArray_val[0]));
++ }
++ bufferPtr += sizeof(da->tc_dumpArray_val[0]);
++ break;
++ }
++ /*
++ * butc restore array
++ * An array of restore descriptions, but the AIX audit package assumes
++ * fixed length, so we can only do the first one for now.
++ */
++ case AUD_TRA:
++ {
++ struct tc_restoreArray *ra;
++
++ ra = (struct tc_restoreArray *)
++ va_arg(vaList, struct tc_restoreArray *);
++ if (ra && ra->tc_restoreArray_len) {
++ memcpy(bufferPtr, &ra->tc_restoreArray_len, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memcpy(bufferPtr, ra->tc_restoreArray_val,
++ sizeof(ra->tc_restoreArray_val[0]));
++ } else {
++ memset(bufferPtr, 0, sizeof(u_int));
++ bufferPtr += sizeof(u_int);
++ memset(bufferPtr, 0, sizeof(ra->tc_restoreArray_val[0]));
++ }
++ bufferPtr += sizeof(ra->tc_restoreArray_val[0]);
++ break;
++ }
++ /* butc tape controller status */
++ {
++ struct tciStatusS *status;
++
++ status = (struct tciStatusS *)va_arg(vaList,
++ struct tciStatusS *);
++ if (status)
++ memcpy(bufferPtr, status, sizeof(*status));
++ else
++ memset(bufferPtr, 0, sizeof(*status));
++ bufferPtr += sizeof(*status);
++ break;
++ }
+ default:
+ #ifdef AFS_AIX32_ENV
+ code =
+@@ -163,6 +253,11 @@ printbuf(int rec, char *audEvent, char *afsName, afs_int32 hostId,
+ char *vaStr;
+ struct AFSFid *vaFid;
+ struct AFSCBFids *vaFids;
++ struct tc_tapeLabel *vaLabel;
++ struct tc_dumpInterface *vaDI;
++ struct tc_dumpArray *vaDA;
++ struct tc_restoreArray *vaRA;
++ struct tciStatusS *vaTCstatus;
+ int num = LogThreadNum();
+ struct in_addr hostAddr;
+ time_t currenttime;
+@@ -258,6 +353,102 @@ printbuf(int rec, char *audEvent, char *afsName, afs_int32 hostId,
+
+ }
+ break;
++ case AUD_TLBL: /* butc tape label */
++ vaLabel = va_arg(vaList, struct tc_tapeLabel *);
++
++ if (vaLabel) {
++ audit_ops->append_msg("TAPELABEL %d:%.*s:%.*s:%u ",
++ vaLabel->size,
++ TC_MAXTAPELEN, vaLabel->afsname,
++ TC_MAXTAPELEN, vaLabel->pname,
++ vaLabel->tapeId);
++ } else {
++ audit_ops->append_msg("TAPELABEL <null>");
++ }
++ break;
++ case AUD_TDI:
++ vaDI = va_arg(vaList, struct tc_dumpInterface *);
++
++ if (vaDI) {
++ audit_ops->append_msg(
++ "TCDUMPINTERFACE %.*s:%.*s:%.*s:%d:%d:%d:%d:%.*s:%.*s:%d:%d:%d:%d:%d ",
++ TC_MAXDUMPPATH, vaDI->dumpPath, TC_MAXNAMELEN, vaDI->volumeSetName,
++ TC_MAXNAMELEN, vaDI->dumpName, vaDI->parentDumpId, vaDI->dumpLevel,
++ vaDI->doAppend,
++ vaDI->tapeSet.id, TC_MAXHOSTLEN, vaDI->tapeSet.tapeServer,
++ TC_MAXFORMATLEN, vaDI->tapeSet.format, vaDI->tapeSet.maxTapes,
++ vaDI->tapeSet.a, vaDI->tapeSet.b, vaDI->tapeSet.expDate,
++ vaDI->tapeSet.expType);
++ } else {
++ audit_ops->append_msg("TCDUMPINTERFACE <null>");
++ }
++ break;
++ case AUD_TDA:
++ vaDA = va_arg(vaList, struct tc_dumpArray *);
++
++ if (vaDA) {
++ u_int i;
++ struct tc_dumpDesc *desc;
++ struct in_addr hostAddr;
++
++ desc = vaDA->tc_dumpArray_val;
++ if (desc) {
++ audit_ops->append_msg("DUMPS %d ", vaDA->tc_dumpArray_len);
++ for (i = 0; i < vaDA->tc_dumpArray_len; i++, desc++) {
++ hostAddr.s_addr = desc->hostAddr;
++ audit_ops->append_msg("DUMP %d:%d:%.*s:%d:%d:%d:%s ",
++ desc->vid, desc->vtype, TC_MAXNAMELEN, desc->name,
++ desc->partition, desc->date, desc->cloneDate,
++ inet_ntoa(hostAddr));
++ }
++ } else {
++ audit_ops->append_msg("DUMPS 0 DUMP 0:0::0:0:0:0.0.0.0");
++ }
++ }
++ break;
++ case AUD_TRA:
++ vaRA = va_arg(vaList, struct tc_restoreArray *);
++
++ if (vaRA) {
++ u_int i;
++ struct tc_restoreDesc *desc;
++ struct in_addr hostAddr;
++
++ desc = vaRA->tc_restoreArray_val;
++ if (desc) {
++ audit_ops->append_msg("RESTORES %d ",
++ vaRA->tc_restoreArray_len);
++ for(i = 0; i < vaRA->tc_restoreArray_len; i++, desc++) {
++ hostAddr.s_addr = desc->hostAddr;
++ audit_ops->append_msg(
++ "RESTORE %d:%.*s:%d:%d:%d:%d:%d:%d:%d:%s:%.*s:%.*s ",
++ desc->flags, TC_MAXTAPELEN, desc->tapeName,
++ desc->dbDumpId, desc->initialDumpId,
++ desc->position, desc->origVid, desc->vid,
++ desc->partition, desc->dumpLevel,
++ inet_ntoa(hostAddr), TC_MAXNAMELEN,
++ desc->oldName, TC_MAXNAMELEN, desc->newName);
++ }
++ } else {
++ audit_ops->append_msg(
++ "RESTORES 0 RESTORE 0::0:0:0:0:0:0:0:0.0.0.0::: ");
++ }
++ }
++ break;
++ case AUD_TSTT:
++ vaTCstatus = va_arg(vaList, struct tciStatusS *);
++
++ if (vaTCstatus)
++ audit_ops->append_msg("TCSTATUS %.*s:%d:%d:%d:%d:%.*s:%d:%d ",
++ TC_MAXNAMELEN, vaTCstatus->taskName,
++ vaTCstatus->taskId, vaTCstatus->flags,
++ vaTCstatus->dbDumpId, vaTCstatus->nKBytes,
++ TC_MAXNAMELEN, vaTCstatus->volumeName,
++ vaTCstatus->volsFailed,
++ vaTCstatus->lastPolled);
++ else
++ audit_ops->append_msg("TCSTATUS <null>");
++ break;
+ default:
+ audit_ops->append_msg("--badval-- ");
+ break;
+diff --git a/src/audit/audit.h b/src/audit/audit.h
+index f8b4100b5a..6188cf60dd 100644
+--- a/src/audit/audit.h
++++ b/src/audit/audit.h
+@@ -23,6 +23,12 @@
+ #define AUD_RESID 20 /* resid in variable list */
+ #define AUD_RSSIZERANGE 21 /* rssizerange in variable list */
+ #define AUD_LOOKUPINFO 22 /* LookupInfo in variable list */
++/* next 5 lines for butc */
++#define AUD_TLBL 30 /* Tape Controller label */
++#define AUD_TDI 31 /* Tape Controller dump interface */
++#define AUD_TDA 32 /* Tape Controller dump array */
++#define AUD_TRA 33 /* Tape Controller restore array */
++#define AUD_TSTT 34 /* Tape Controller status struct */
+
+ /*
+ * Note: the master definitions of these error codes come from *.et
+@@ -296,6 +302,21 @@
+ #define SREMIORemoteGetHSMdata "AFS_RE_HSMdata"
+ #define SREMIOPrefetch "AFS_RE_Prefetch"
+
++#define TC_StartEvent "AFS_TC_Start"
++#define TC_LabelTapeEvent "AFS_TC_LabelTape"
++#define TC_PerformDumpEvent "AFS_TC_PerformDump"
++#define TC_PerformRestoreEvent "AFS_TC_PerformRestore"
++#define TC_ReadLabelEvent "AFS_TC_ReadLabel"
++#define TC_RestoreDbEvent "AFS_TC_RestoreDb"
++#define TC_SaveDbEvent "AFS_TC_SaveDb"
++#define TC_ScanDumpsEvent "AFS_TC_ScanDumps"
++#define TC_TCInfoEvent "AFS_TC_TCInfo"
++#define TC_DeleteDumpEvent "AFS_TC_DeleteDump"
++#define TC_GetStatusEvent "AFS_TC_GetStatus"
++#define TC_EndStatusEvent "AFS_TC_EndStatus"
++#define TC_RequestAbortEvent "AFS_TC_RequestAbort"
++#define TC_ScanStatusEvent "AFS_TC_ScanStatus"
++
+
+ /* prototypes for audit functions */
+ int osi_audit(char *audEvent, afs_int32 errCode, ...);
+diff --git a/src/bucoord/bucoord_internal.h b/src/bucoord/bucoord_internal.h
+index 543bf00090..7866f47cde 100644
+--- a/src/bucoord/bucoord_internal.h
++++ b/src/bucoord/bucoord_internal.h
+@@ -117,6 +117,8 @@ extern afs_int32 bc_UpdateDumpSchedule(void);
+ extern int bc_SaveDumpSchedule(void);
+
+ /* main.c */
++extern int localauth, nobutcauth;
++extern char tcell[];
+ extern time_t tokenExpires;
+ extern afs_int32 doDispatch(afs_int32, char *[], afs_int32);
+ extern void bc_HandleMisc(afs_int32 code);
+diff --git a/src/bucoord/dump.c b/src/bucoord/dump.c
+index 5d1f58aa3a..e0b3ced57a 100644
+--- a/src/bucoord/dump.c
++++ b/src/bucoord/dump.c
+@@ -17,6 +17,7 @@
+ #include <roken.h>
+
+ #include <afs/cmd.h>
++#include <afs/cellconfig.h>
+ #include <lwp.h>
+ #include <rx/rx.h>
+ #include <afs/bubasics.h>
+@@ -470,15 +471,40 @@ bc_GetConn(struct bc_config *aconfig, afs_int32 aport,
+ struct rx_connection **tconn)
+ {
+ afs_uint32 host;
++ afs_int32 code;
+ unsigned short port;
+ static struct rx_securityClass *rxsc;
++ static afs_int32 scIndex;
+ struct bc_hostEntry *te;
+
+ *tconn = (struct rx_connection *)0;
+
+ /* use non-secure connections to butc */
+- if (!rxsc)
+- rxsc = rxnull_NewClientSecurityObject();
++ if (!rxsc) {
++ struct afsconf_dir *dir;
++ afsconf_secflags flags = AFSCONF_SECOPTS_FALLBACK_NULL;
++ char *cname;
++
++ if (nobutcauth)
++ flags |= AFSCONF_SECOPTS_NOAUTH;
++ if (localauth) {
++ flags |= AFSCONF_SECOPTS_LOCALAUTH;
++ dir = afsconf_Open(AFSDIR_SERVER_ETC_DIRPATH);
++ } else {
++ dir = afsconf_Open(AFSDIR_CLIENT_ETC_DIRPATH);
++ }
++ if (tcell[0] == '\0')
++ cname = NULL;
++ else
++ cname = tcell;
++ /* No need for cell info since butc is not a registered service */
++ code = afsconf_PickClientSecObj(dir, flags, NULL, cname, &rxsc, &scIndex,
++ NULL);
++ if (dir)
++ afsconf_Close(dir);
++ if (code)
++ return -1;
++ }
+ if (!rxsc || !aconfig)
+ return (-1);
+
+@@ -491,8 +517,8 @@ bc_GetConn(struct bc_config *aconfig, afs_int32 aport,
+
+ port = htons(BC_TAPEPORT + aport);
+
+- /* servers is 1; sec index is 0 */
+- *tconn = rx_NewConnection(host, port, 1, rxsc, 0);
++ /* servers is 1 */
++ *tconn = rx_NewConnection(host, port, 1, rxsc, scIndex);
+ return ((*tconn ? 0 : -1));
+ }
+ }
+diff --git a/src/bucoord/main.c b/src/bucoord/main.c
+index 34a3a7926c..d2a5f368bd 100644
+--- a/src/bucoord/main.c
++++ b/src/bucoord/main.c
+@@ -41,7 +41,7 @@
+ #include "bucoord_internal.h"
+ #include "bucoord_prototypes.h"
+
+-int localauth, interact;
++int localauth, interact, nobutcauth;
+ char tcell[64];
+
+ /*
+@@ -292,6 +292,7 @@ MyBeforeProc(struct cmd_syndesc *as, void *arock)
+ /* Handling the command line opcode */
+ if (!bcInit) {
+ localauth = ((as && as->parms[14].items) ? 1 : 0);
++ nobutcauth = ((as && as->parms[16].items) ? 1 : 0);
+ if (as && as->parms[15].items)
+ strcpy(tcell, as->parms[15].items->data);
+ else
+@@ -432,6 +433,8 @@ add_std_args(struct cmd_syndesc *ts)
+ cmd_AddParm(ts, "-localauth", CMD_FLAG, CMD_OPTIONAL,
+ "local authentication");
+ cmd_AddParm(ts, "-cell", CMD_SINGLE, CMD_OPTIONAL, "cell name");
++ cmd_AddParm(ts, "-nobutcauth", CMD_FLAG, CMD_OPTIONAL,
++ "no authentication to butc");
+ }
+
+ int
+diff --git a/src/butc/Makefile.in b/src/butc/Makefile.in
+index 4648f96b7a..5d942222e7 100644
+--- a/src/butc/Makefile.in
++++ b/src/butc/Makefile.in
+@@ -38,6 +38,7 @@ LIBS=${TOP_LIBDIR}/libbudb.a \
+ ${TOP_LIBDIR}/libsys.a \
+ ${TOP_LIBDIR}/librx.a \
+ ${TOP_LIBDIR}/libsys.a \
++ ${TOP_LIBDIR}/libaudit.a \
+ ${TOP_LIBDIR}/liblwp.a \
+ ${TOP_LIBDIR}/libcmd.a \
+ ${TOP_LIBDIR}/libafscom_err.a \
+diff --git a/src/butc/butc_prototypes.h b/src/butc/butc_prototypes.h
+index 1c62c34b4d..85a379963a 100644
+--- a/src/butc/butc_prototypes.h
++++ b/src/butc/butc_prototypes.h
+@@ -32,5 +32,10 @@ extern void *saveDbToTape(void *);
+ extern void *restoreDbFromTape(void *);
+ extern void *KeepAlive(void *);
+
++/* tcmain.c */
++
++extern struct afsconf_dir *butc_confdir;
++extern int allow_unauth;
++
+ #endif
+
+diff --git a/src/butc/tcmain.c b/src/butc/tcmain.c
+index 197fe256a0..c5399396ee 100644
+--- a/src/butc/tcmain.c
++++ b/src/butc/tcmain.c
+@@ -41,6 +41,7 @@
+ #include <afs/keys.h>
+ #include <afs/volser.h>
+ #include <ubik.h>
++#include <afs/audit.h>
+ #include <afs/com_err.h>
+ #include <afs/cmd.h>
+ #include <afs/tcdata.h>
+@@ -105,18 +106,12 @@ afs_int32 BufferSize; /* Size in B stored for data */
+ char *centralLogFile;
+ afs_int32 lastLog; /* Log last pass info */
+ int rxBind = 0;
++struct afsconf_dir *butc_confdir;
++int allow_unauth = 0;
+
+ #define ADDRSPERSITE 16 /* Same global is in rx/rx_user.c */
+ afs_uint32 SHostAddrs[ADDRSPERSITE];
+
+-/* dummy routine for the audit work. It should do nothing since audits */
+-/* occur at the server level and bos is not a server. */
+-int
+-osi_audit(void)
+-{
+- return 0;
+-}
+-
+ static afs_int32
+ SafeATOL(char *anum)
+ {
+@@ -831,11 +826,26 @@ xbsa_shutdown(int x)
+ }
+ #endif
+
++static int
++tc_IsLocalRealmMatch(void *rock, char *name, char *inst, char *cell)
++{
++ struct afsconf_dir *dir = (struct afsconf_dir *)rock;
++ afs_int32 islocal = 0; /* default to no */
++ int code;
++
++ code = afsconf_IsLocalRealmMatch(dir, &islocal, name, inst, cell);
++ if (code) {
++ TLog(0, ("Failed local realm check; code=%d, name=%s, inst=%s, cell=%s\n",
++ code, name, inst, cell));
++ }
++ return islocal;
++}
++
+ static int
+ WorkerBee(struct cmd_syndesc *as, void *arock)
+ {
+- afs_int32 code;
+- struct rx_securityClass *(securityObjects[1]);
++ afs_int32 code, numClasses;
++ struct rx_securityClass *(nullObjects[1]), **secObjs, **allObjs;
+ struct rx_service *service;
+ time_t tokenExpires;
+ char cellName[64];
+@@ -850,6 +860,8 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+ PROCESS dbWatcherPid;
+ #endif
+ afs_uint32 host = htonl(INADDR_ANY);
++ char *auditFileName = NULL;
++ char *auditInterface = NULL;
+
+ debugLevel = 0;
+
+@@ -995,6 +1007,30 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+ }
+ }
+
++ /* Open the configuration directory */
++ butc_confdir = afsconf_Open(AFSDIR_SERVER_ETC_DIRPATH);
++ if (butc_confdir == NULL) {
++ TLog(0, "Failed to open server configuration directory");
++ exit(1);
++ }
++
++ /* Start auditing */
++ osi_audit_init();
++ if (as->parms[9].items) {
++ auditFileName = as->parms[9].items->data;
++ }
++ if (auditFileName != NULL)
++ osi_audit_file(auditFileName);
++ if (as->parms[10].items) {
++ auditInterface = as->parms[10].items->data;
++ if (osi_audit_interface(auditInterface)) {
++ TLog(0, "Invalid audit interface '%s'\n", auditInterface);
++ exit(1);
++ }
++ }
++ osi_audit(TC_StartEvent, 0, AUD_END);
++ osi_audit_set_user_check(butc_confdir, tc_IsLocalRealmMatch);
++
+ if (as->parms[1].items) {
+ debugLevel = SafeATOL(as->parms[1].items->data);
+ if (debugLevel == -1) {
+@@ -1035,6 +1071,13 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+
+ localauth = (as->parms[5].items ? 1 : 0);
+ rxBind = (as->parms[8].items ? 1 : 0);
++ allow_unauth = (as->parms[11].items ? 1 : 0);
++
++ if (!allow_unauth && !localauth) {
++ const char *errstr = "Neither -localauth nor -allow_unauthenticated was provided; refusing to start in unintended insecure configuration\n";
++ TLog(0, "%s", (char *)errstr);
++ exit(1);
++ }
+
+ if (rxBind) {
+ afs_int32 ccode;
+@@ -1079,19 +1122,48 @@ WorkerBee(struct cmd_syndesc *as, void *arock)
+
+ /* initialize database support, volume support, and logs */
+
+- /* Create a single security object, in this case the null security
+- * object, for unauthenticated connections, which will be used to control
+- * security on connections made to this server
++ /*
++ * Create security objects for the Rx server functionality. Historically
++ * this was a single rxnull security object, since the tape controller was
++ * run by an operator that had local access to the tape device and some
++ * administrative privilege in the cell (to be able to perform volume-level
++ * accesses), but on a machine that was not necessarily trusted to hold the
++ * cell-wide key.
++ *
++ * Such a configuration is, of course, insecure because anyone can make
++ * inbound RPCs and manipulate the database, including creating bogus
++ * dumps and restoring them! Additionally, in modern usage, butc is
++ * frequently run with -localauth to authenticate its outbound connections
++ * to the volservers and budb with the cell-wide key, in which case the
++ * cell-wide key is present and could be used to authenticate incoming
++ * connections as well.
++ *
++ * If -localauth is in use, create the full barrage of server security
++ * objects, including rxkad, so that inbound connections can be verified
++ * to only be made by authenticated clients. Otherwise, only the rxnull
++ * class is in use with a single server security object. Note that butc
++ * will refuse to start in this configuration unless the
++ * "-allow_unauthenticated" flag is provided, indicating that the operator
++ * has ensured that incoming connections are appropriately restricted by
++ * firewall configuration or network topology.
+ */
+
+- securityObjects[RX_SECIDX_NULL] = rxnull_NewServerSecurityObject();
+- if (!securityObjects[RX_SECIDX_NULL]) {
+- TLog(0, "rxnull_NewServerSecurityObject");
+- exit(1);
++ if (allow_unauth) {
++ nullObjects[RX_SECIDX_NULL] = rxnull_NewServerSecurityObject();
++ if (!nullObjects[RX_SECIDX_NULL]) {
++ TLog(0, "rxnull_NewServerSecurityObject");
++ exit(1);
++ }
++ numClasses = 1;
++ secObjs = nullObjects;
++ } else {
++ /* Must be -localauth, so the cell keys are available. */
++ afsconf_BuildServerSecurityObjects(butc_confdir, &allObjs, &numClasses);
++ secObjs = allObjs;
+ }
+
+ service =
+- rx_NewServiceHost(host, 0, 1, "BUTC", securityObjects, 1, TC_ExecuteRequest);
++ rx_NewServiceHost(host, 0, 1, "BUTC", secObjs, numClasses, TC_ExecuteRequest);
+ if (!service) {
+ TLog(0, "rx_NewService");
+ exit(1);
+@@ -1194,6 +1266,11 @@ main(int argc, char **argv)
+ "Force multiple XBSA server support");
+ cmd_AddParm(ts, "-rxbind", CMD_FLAG, CMD_OPTIONAL,
+ "bind Rx socket");
++ cmd_AddParm(ts, "-auditlog", CMD_SINGLE, CMD_OPTIONAL, "location of audit log");
++ cmd_AddParm(ts, "-audit-interface", CMD_SINGLE, CMD_OPTIONAL,
++ "interface to use for audit logging");
++ cmd_AddParm(ts, "-allow_unauthenticated", CMD_FLAG, CMD_OPTIONAL,
++ "allow unauthenticated inbound RPCs (requires firewalling)");
+
+ /* Initialize dirpaths */
+ if (!(initAFSDirPath() & AFSDIR_SERVER_PATHS_OK)) {
+diff --git a/src/butc/tcprocs.c b/src/butc/tcprocs.c
+index cc19e532d5..c51b1f14d2 100644
+--- a/src/butc/tcprocs.c
++++ b/src/butc/tcprocs.c
+@@ -33,21 +33,41 @@
+ #include "butc_xbsa.h"
+ #include "butc_prototypes.h"
+ #include "butc_internal.h"
++#include "afs/audit.h"
+
+ static int CopyDumpDesc(struct tc_dumpDesc *, tc_dumpArray *);
+ static int CopyRestoreDesc(struct tc_restoreDesc *, tc_restoreArray *);
+ static int CopyTapeSetDesc(struct tc_tapeSet *, struct tc_tapeSet *);
+
++/* Helpers implementing RPC backends */
++static afs_int32 SLabelTape(struct rx_call *acid, struct tc_tapeLabel *label,
++ afs_uint32 *taskId);
++static afs_int32 SPerformDump(struct rx_call *rxCallId,
++ struct tc_dumpInterface *tcdiPtr,
++ tc_dumpArray *tc_dumpArrayPtr, afs_int32 *taskId);
++static afs_int32 SPerformRestore(struct rx_call *acid, char *dumpSetName,
++ tc_restoreArray *arestores, afs_int32 *taskId);
++static afs_int32 SReadLabel(struct rx_call *acid, struct tc_tapeLabel *label,
++ afs_uint32 *taskId);
++static afs_int32 SRestoreDb(struct rx_call *rxCall, afs_uint32 *taskId);
++static afs_int32 SSaveDb(struct rx_call *rxCall, Date archiveTime,
++ afs_uint32 *taskId);
++static afs_int32 SScanDumps(struct rx_call *acid, afs_int32 addDbFlag,
++ afs_uint32 *taskId);
++static afs_int32 STCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr);
++static afs_int32 SDeleteDump(struct rx_call *acid, afs_uint32 dumpID,
++ afs_uint32 *taskId);
++
+ int
+ callPermitted(struct rx_call *call)
+ {
+ /*
+- * Before this code can be used, the rx connection, on the bucoord side,
+- * must be changed so that it will set up for token passing instead of
+- * using a simple rx connection that, below, returns a value of
+- * RX_SECIDX_NULL from rx_SecurityClassOf.
++ * If in backwards compat mode, allow anyone; otherwise, only
++ * superusers are allowed.
+ */
+- return 1;
++ if (allow_unauth)
++ return 1;
++ return afsconf_SuperIdentity(butc_confdir, call, NULL);
+ }
+
+ /* -----------------------------
+@@ -129,6 +149,17 @@ CopyTapeSetDesc(struct tc_tapeSet *toPtr, struct tc_tapeSet *fromPtr)
+
+ afs_int32
+ STC_LabelTape(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SLabelTape(acid, label, taskId);
++ osi_auditU(acid, TC_LabelTapeEvent, code,
++ AUD_TLBL, label, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SLabelTape(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -204,7 +235,20 @@ STC_LabelTape(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *task
+ */
+
+ afs_int32
+-STC_PerformDump(struct rx_call *rxCallId, struct tc_dumpInterface *tcdiPtr, tc_dumpArray *tc_dumpArrayPtr, afs_int32 *taskId)
++STC_PerformDump(struct rx_call *call, struct tc_dumpInterface *di,
++ tc_dumpArray *da, afs_int32 *taskId)
++{
++ afs_int32 code;
++
++ code = SPerformDump(call, di, da, taskId);
++ osi_auditU(call, TC_PerformDumpEvent, code,
++ AUD_TDI, di, AUD_TDA, da, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SPerformDump(struct rx_call *rxCallId, struct tc_dumpInterface *tcdiPtr,
++ tc_dumpArray *tc_dumpArrayPtr, afs_int32 *taskId)
+ {
+ struct dumpNode *newNode = 0;
+ statusP statusPtr = 0;
+@@ -295,7 +339,20 @@ STC_PerformDump(struct rx_call *rxCallId, struct tc_dumpInterface *tcdiPtr, tc_d
+ }
+
+ afs_int32
+-STC_PerformRestore(struct rx_call *acid, char *dumpSetName, tc_restoreArray *arestores, afs_int32 *taskID)
++STC_PerformRestore(struct rx_call *call, char *dumpSetName,
++ tc_restoreArray *ra, afs_int32 *taskId)
++{
++ afs_int32 code;
++
++ code = SPerformRestore(call, dumpSetName, ra, taskId);
++ osi_auditU(call, TC_PerformRestoreEvent, code,
++ AUD_STR, dumpSetName, AUD_TRA, ra, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SPerformRestore(struct rx_call *acid, char *dumpSetName,
++ tc_restoreArray *arestores, afs_int32 *taskID)
+ {
+ struct dumpNode *newNode;
+ statusP statusPtr;
+@@ -368,7 +425,18 @@ STC_PerformRestore(struct rx_call *acid, char *dumpSetName, tc_restoreArray *are
+ }
+
+ afs_int32
+-STC_ReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
++STC_ReadLabel(struct rx_call *call, struct tc_tapeLabel *label, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SReadLabel(call, label, taskId);
++ osi_auditU(call, TC_ReadLabelEvent, code,
++ AUD_TLBL, label, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *taskId)
+ {
+ afs_int32 code;
+
+@@ -392,7 +460,17 @@ STC_ReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *task
+ */
+
+ afs_int32
+-STC_RestoreDb(struct rx_call *rxCall, afs_uint32 *taskId)
++STC_RestoreDb(struct rx_call *call, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SRestoreDb(call, taskId);
++ osi_auditU(call, TC_RestoreDbEvent, code, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SRestoreDb(struct rx_call *rxCall, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -458,7 +536,18 @@ STC_RestoreDb(struct rx_call *rxCall, afs_uint32 *taskId)
+ */
+
+ afs_int32
+-STC_SaveDb(struct rx_call *rxCall, Date archiveTime, afs_uint32 *taskId)
++STC_SaveDb(struct rx_call *call, Date archiveTime, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SSaveDb(call, archiveTime, taskId);
++ osi_auditU(call, TC_SaveDbEvent, code,
++ AUD_DATE, archiveTime, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SSaveDb(struct rx_call *rxCall, Date archiveTime, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -537,7 +626,18 @@ STC_SaveDb(struct rx_call *rxCall, Date archiveTime, afs_uint32 *taskId)
+ */
+
+ afs_int32
+-STC_ScanDumps(struct rx_call *acid, afs_int32 addDbFlag, afs_uint32 *taskId)
++STC_ScanDumps(struct rx_call *call, afs_int32 addDbFlag, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SScanDumps(call, addDbFlag, taskId);
++ osi_auditU(call, TC_ScanDumpsEvent, code,
++ AUD_INT, addDbFlag, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SScanDumps(struct rx_call *acid, afs_int32 addDbFlag, afs_uint32 *taskId)
+ {
+ #ifdef AFS_PTHREAD_ENV
+ pthread_t pid;
+@@ -612,7 +712,17 @@ STC_ScanDumps(struct rx_call *acid, afs_int32 addDbFlag, afs_uint32 *taskId)
+ */
+
+ afs_int32
+-STC_TCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr)
++STC_TCInfo(struct rx_call *call, struct tc_tcInfo *ti)
++{
++ afs_int32 code;
++
++ code = STCInfo(call, ti);
++ osi_auditU(call, TC_TCInfoEvent, code, AUD_INT, ti->tcVersion, AUD_END);
++ return code;
++}
++
++static afs_int32
++STCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr)
+ {
+ if (callPermitted(acid) == 0)
+ return (TC_NOTPERMITTED);
+@@ -624,7 +734,18 @@ STC_TCInfo(struct rx_call *acid, struct tc_tcInfo *tciptr)
+ /* STC_DeleteDump
+ */
+ afs_int32
+-STC_DeleteDump(struct rx_call *acid, afs_uint32 dumpID, afs_uint32 *taskId)
++STC_DeleteDump(struct rx_call *call, afs_uint32 dumpID, afs_uint32 *taskId)
++{
++ afs_int32 code;
++
++ code = SDeleteDump(call, dumpID, taskId);
++ osi_auditU(call, TC_DeleteDumpEvent, code,
++ AUD_DATE, dumpID, AUD_INT, *taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SDeleteDump(struct rx_call *acid, afs_uint32 dumpID, afs_uint32 *taskId)
+ {
+ afs_int32 code = TC_BADTASK; /* If not compiled -Dxbsa then fail */
+ #ifdef xbsa
+diff --git a/src/butc/tcstatus.c b/src/butc/tcstatus.c
+index db06b514fe..b67ae1eee4 100644
+--- a/src/butc/tcstatus.c
++++ b/src/butc/tcstatus.c
+@@ -23,6 +23,7 @@
+ #include "butc_internal.h"
+ #include "error_macros.h"
+ #include "butc_xbsa.h"
++#include "afs/audit.h"
+
+ /* tape coordinator - task status management */
+ extern afs_int32 xbsaType;
+@@ -31,6 +32,13 @@ dlqlinkT statusHead;
+ struct Lock statusQueueLock;
+ struct Lock cmdLineLock;
+
++static afs_int32 SGetStatus(struct rx_call *call, afs_uint32 taskId,
++ struct tciStatusS *statusPtr);
++static afs_int32 SEndStatus(struct rx_call *call, afs_uint32 taskId);
++static afs_int32 SRequestAbort(struct rx_call *call, afs_uint32 taskId);
++static afs_int32 SScanStatus(struct rx_call *call, afs_uint32 *taskId,
++ struct tciStatusS *statusPtr, afs_uint32 *flags);
++
+ /* STC_GetStatus
+ * get the status of a task
+ * entry:
+@@ -41,7 +49,19 @@ struct Lock cmdLineLock;
+
+ afs_int32
+ STC_GetStatus(struct rx_call *call, afs_uint32 taskId,
+- struct tciStatusS *statusPtr)
++ struct tciStatusS *status)
++{
++ afs_int32 code;
++
++ code = SGetStatus(call, taskId, status);
++ osi_auditU(call, TC_GetStatusEvent, code,
++ AUD_INT, taskId, AUD_TSTT, status, AUD_END);
++ return code;
++}
++
++static afs_int32
++SGetStatus(struct rx_call *call, afs_uint32 taskId,
++ struct tciStatusS *statusPtr)
+ {
+ statusP ptr;
+ int retval = 0;
+@@ -71,6 +91,16 @@ STC_GetStatus(struct rx_call *call, afs_uint32 taskId,
+
+ afs_int32
+ STC_EndStatus(struct rx_call *call, afs_uint32 taskId)
++{
++ afs_int32 code;
++
++ code = SEndStatus(call, taskId);
++ osi_auditU(call, TC_EndStatusEvent, code, AUD_INT, taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SEndStatus(struct rx_call *call, afs_uint32 taskId)
+ {
+ statusP ptr;
+ int retval = 0;
+@@ -92,6 +122,16 @@ STC_EndStatus(struct rx_call *call, afs_uint32 taskId)
+
+ afs_int32
+ STC_RequestAbort(struct rx_call *call, afs_uint32 taskId)
++{
++ afs_int32 code;
++
++ code = SRequestAbort(call, taskId);
++ osi_auditU(call, TC_RequestAbortEvent, code, AUD_INT, taskId, AUD_END);
++ return code;
++}
++
++static afs_int32
++SRequestAbort(struct rx_call *call, afs_uint32 taskId)
+ {
+ statusP ptr;
+ int retval = 0;
+@@ -127,7 +167,19 @@ STC_RequestAbort(struct rx_call *call, afs_uint32 taskId)
+
+ afs_int32
+ STC_ScanStatus(struct rx_call *call, afs_uint32 *taskId,
+- struct tciStatusS *statusPtr, afs_uint32 *flags)
++ struct tciStatusS *status, afs_uint32 *flags)
++{
++ afs_int32 code;
++
++ code = SScanStatus(call, taskId, status, flags);
++ osi_auditU(call, TC_ScanStatusEvent, code,
++ AUD_INT, *taskId, AUD_TSTT, status, AUD_INT, *flags, AUD_END);
++ return code;
++}
++
++static afs_int32
++SScanStatus(struct rx_call *call, afs_uint32 *taskId,
++ struct tciStatusS *statusPtr, afs_uint32 *flags)
+ {
+ statusP ptr = 0;
+ dlqlinkP dlqPtr;
--- /dev/null
+diff --git a/src/WINNT/afsd/cm_callback.c b/src/WINNT/afsd/cm_callback.c
+index 5816bbe3cc..a5db1403e5 100644
+--- a/src/WINNT/afsd/cm_callback.c
++++ b/src/WINNT/afsd/cm_callback.c
+@@ -1256,6 +1256,7 @@ SRXAFSCB_TellMeAboutYourself( struct rx_call *callp,
+ }
+
+ /* return all network interface addresses */
++ memset(addr, 0, sizeof(*addr));
+ addr->numberOfInterfaces = cm_noIPAddr;
+ addr->uuid = cm_data.Uuid;
+ for ( i=0; i < cm_noIPAddr; i++ ) {
+diff --git a/src/afs/afs_callback.c b/src/afs/afs_callback.c
+index 61b2a75b7a..038cddab21 100644
+--- a/src/afs/afs_callback.c
++++ b/src/afs/afs_callback.c
+@@ -306,6 +306,7 @@ SRXAFSCB_GetLock(struct rx_call *a_call, afs_int32 a_index,
+ XSTATS_START_CMTIME(AFS_STATS_CM_RPCIDX_GETLOCK);
+
+ AFS_STATCNT(SRXAFSCB_GetLock);
++ memset(a_result, 0, sizeof(*a_result));
+ nentries = sizeof(ltable) / sizeof(struct ltable);
+ if (a_index < 0 || a_index >= nentries+afs_cellindex) {
+ /*
+@@ -1618,6 +1619,7 @@ SRXAFSCB_TellMeAboutYourself(struct rx_call *a_call,
+ ObtainReadLock(&afs_xinterface);
+
+ /* return all network interface addresses */
++ memset(addr, 0, sizeof(*addr));
+ addr->numberOfInterfaces = afs_cb_interface.numberOfInterfaces;
+ addr->uuid = afs_cb_interface.uuid;
+ for (i = 0; i < afs_cb_interface.numberOfInterfaces; i++) {
+diff --git a/src/budb/procs.c b/src/budb/procs.c
+index f2987d3115..f976ff3aa3 100644
+--- a/src/budb/procs.c
++++ b/src/budb/procs.c
+@@ -424,6 +424,7 @@ FillDumpEntry(struct ubik_trans *ut, dbadr da, void *rock)
+ struct budb_dumpEntry *dump = (struct budb_dumpEntry *)rock;
+ struct dump d, ad;
+
++ memset(dump, 0, sizeof(*dump));
+ if (dbread(ut, da, &d, sizeof(d)))
+ return BUDB_IO;
+ dump->id = ntohl(d.id);
+diff --git a/src/butc/tcprocs.c b/src/butc/tcprocs.c
+index d41112a806..cc19e532d5 100644
+--- a/src/butc/tcprocs.c
++++ b/src/butc/tcprocs.c
+@@ -372,6 +372,9 @@ STC_ReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *task
+ {
+ afs_int32 code;
+
++ memset(label, 0, sizeof(*label));
++ /* Synchronous, so no "real" ID; don't send stack garbage on the wire */
++ *taskId = 0;
+ #ifdef xbsa
+ if (CONF_XBSA)
+ return (TC_BADTASK); /* ReadLabel does not apply if XBSA */
+diff --git a/src/butc/tcstatus.c b/src/butc/tcstatus.c
+index fbe46a49f9..db06b514fe 100644
+--- a/src/butc/tcstatus.c
++++ b/src/butc/tcstatus.c
+@@ -46,14 +46,13 @@ STC_GetStatus(struct rx_call *call, afs_uint32 taskId,
+ statusP ptr;
+ int retval = 0;
+
++ memset(statusPtr, 0, sizeof(*statusPtr));
+ if (callPermitted(call) == 0)
+ return (TC_NOTPERMITTED);
+
+ lock_Status();
+ ptr = findStatus(taskId);
+ if (ptr) {
+- /* strcpy(statusPtr->status, ptr->status); */
+-
+ strcpy(statusPtr->taskName, ptr->taskName);
+ strcpy(statusPtr->volumeName, ptr->volumeName);
+ statusPtr->taskId = ptr->taskId;
+@@ -133,6 +132,7 @@ STC_ScanStatus(struct rx_call *call, afs_uint32 *taskId,
+ statusP ptr = 0;
+ dlqlinkP dlqPtr;
+
++ memset(statusPtr, 0, sizeof(*statusPtr));
+ if (callPermitted(call) == 0)
+ return (TC_NOTPERMITTED);
+
+diff --git a/src/kauth/kaprocs.c b/src/kauth/kaprocs.c
+index 315096a461..1c6c68f972 100644
+--- a/src/kauth/kaprocs.c
++++ b/src/kauth/kaprocs.c
+@@ -1700,6 +1700,7 @@ kamListEntry(struct rx_call *call,
+ afs_int32 caller;
+ struct kaentry tentry;
+
++ memset(name, 0, sizeof(*name));
+ COUNT_REQ(ListEntry);
+ if ((code = InitAuthServ(&tt, LOCKREAD, this_op)))
+ return code;
+diff --git a/src/ptserver/ptprocs.c b/src/ptserver/ptprocs.c
+index 3045ef45f5..4ce1cbf92f 100644
+--- a/src/ptserver/ptprocs.c
++++ b/src/ptserver/ptprocs.c
+@@ -651,7 +651,7 @@ idToName(struct rx_call *call, idlist *aid, namelist *aname, afs_int32 *cid)
+ return 0;
+ if (size < 0 || size > INT_MAX / PR_MAXNAMELEN)
+ return PRTOOMANY;
+- aname->namelist_val = malloc(size * PR_MAXNAMELEN);
++ aname->namelist_val = calloc(size, PR_MAXNAMELEN);
+ aname->namelist_len = 0;
+ if (aname->namelist_val == 0)
+ return PRNOMEM;
+@@ -1538,6 +1538,7 @@ put_prentries(struct prentry *tentry, prentries *bulkentries)
+ entry = bulkentries->prentries_val;
+ entry += bulkentries->prentries_len;
+
++ memset(entry, 0, sizeof(*entry));
+ entry->flags = tentry->flags >> PRIVATE_SHIFT;
+ if (entry->flags == 0) {
+ entry->flags =
+@@ -1552,7 +1553,6 @@ put_prentries(struct prentry *tentry, prentries *bulkentries)
+ entry->nusers = tentry->nusers;
+ entry->count = tentry->count;
+ strncpy(entry->name, tentry->name, PR_MAXNAMELEN);
+- memset(entry->reserved, 0, sizeof(entry->reserved));
+ bulkentries->prentries_len++;
+ return 0;
+ }
+diff --git a/src/ubik/vote.c b/src/ubik/vote.c
+index 3b771f0bc8..34f285dcd2 100644
+--- a/src/ubik/vote.c
++++ b/src/ubik/vote.c
+@@ -405,6 +405,7 @@ SVOTE_Debug(struct rx_call * rxcall, struct ubik_debug * aparm)
+ /* fill in the basic debug structure. Note the the RPC protocol transfers,
+ * integers in host order. */
+
++ memset(aparm, 0, sizeof(*aparm));
+ aparm->now = FT_ApproxTime();
+ aparm->lastYesTime = vote_globals.ubik_lastYesTime;
+ aparm->lastYesHost = ntohl(vote_globals.lastYesHost);
+diff --git a/src/volser/volprocs.c b/src/volser/volprocs.c
+index 07ba5b46e0..3947803197 100644
+--- a/src/volser/volprocs.c
++++ b/src/volser/volprocs.c
+@@ -382,6 +382,7 @@ SAFSVolPartitionInfo(struct rx_call *acid, char *pname, struct diskPartition
+ afs_int32 code;
+ struct diskPartition64 *dp = malloc(sizeof(struct diskPartition64));
+
++ memset(partition, 0, sizeof(*partition));
+ code = VolPartitionInfo(acid, pname, dp);
+ if (!code) {
+ strncpy(partition->name, dp->name, 32);
+@@ -401,6 +402,7 @@ SAFSVolPartitionInfo64(struct rx_call *acid, char *pname, struct diskPartition64
+ {
+ afs_int32 code;
+
++ memset(partition, 0, sizeof(*partition));
+ code = VolPartitionInfo(acid, pname, partition);
+ osi_auditU(acid, VS_ParInfEvent, code, AUD_STR, pname, AUD_END);
+ return code;
+@@ -2806,6 +2808,7 @@ VolMonitor(struct rx_call *acid, transDebugEntries *transInfo)
+ goto done; /*no active transactions */
+ for (tt = allTrans; tt; tt = nt) { /*copy relevant info into pntr */
+ nt = tt->next;
++ memset(pntr, 0, sizeof(*pntr));
+ VTRANS_OBJ_LOCK(tt);
+ pntr->tid = tt->tid;
+ pntr->time = tt->time;
--- /dev/null
+diff --git a/src/WINNT/afsd/cm_callback.c b/src/WINNT/afsd/cm_callback.c
+index 3ef9f258b4..0118f7b3fa 100644
+--- a/src/WINNT/afsd/cm_callback.c
++++ b/src/WINNT/afsd/cm_callback.c
+@@ -1243,6 +1243,7 @@ SRXAFSCB_TellMeAboutYourself( struct rx_call *callp,
+ }
+
+ /* return all network interface addresses */
++ memset(addr, 0, sizeof(*addr));
+ addr->numberOfInterfaces = cm_noIPAddr;
+ addr->uuid = cm_data.Uuid;
+ for ( i=0; i < cm_noIPAddr; i++ ) {
+diff --git a/src/afs/afs_callback.c b/src/afs/afs_callback.c
+index 51b6b61d35..6a95ed491e 100644
+--- a/src/afs/afs_callback.c
++++ b/src/afs/afs_callback.c
+@@ -309,6 +309,7 @@ SRXAFSCB_GetLock(struct rx_call *a_call, afs_int32 a_index,
+ XSTATS_START_CMTIME(AFS_STATS_CM_RPCIDX_GETLOCK);
+
+ AFS_STATCNT(SRXAFSCB_GetLock);
++ memset(a_result, 0, sizeof(*a_result));
+ nentries = sizeof(ltable) / sizeof(struct ltable);
+ if (a_index < 0 || a_index >= nentries+afs_cellindex) {
+ /*
+@@ -1632,6 +1633,7 @@ SRXAFSCB_TellMeAboutYourself(struct rx_call *a_call,
+ ObtainReadLock(&afs_xinterface);
+
+ /* return all network interface addresses */
++ memset(addr, 0, sizeof(*addr));
+ addr->numberOfInterfaces = afs_cb_interface.numberOfInterfaces;
+ addr->uuid = afs_cb_interface.uuid;
+ for (i = 0; i < afs_cb_interface.numberOfInterfaces; i++) {
+diff --git a/src/budb/procs.c b/src/budb/procs.c
+index 51f3102636..de55834475 100644
+--- a/src/budb/procs.c
++++ b/src/budb/procs.c
+@@ -437,6 +437,7 @@ FillDumpEntry(struct ubik_trans *ut, dbadr da, void *rock)
+ struct budb_dumpEntry *dump = (struct budb_dumpEntry *)rock;
+ struct dump d, ad;
+
++ memset(dump, 0, sizeof(*dump));
+ if (dbread(ut, da, &d, sizeof(d)))
+ return BUDB_IO;
+ dump->id = ntohl(d.id);
+diff --git a/src/butc/tcprocs.c b/src/butc/tcprocs.c
+index 9efcbe89a5..c1177808c1 100644
+--- a/src/butc/tcprocs.c
++++ b/src/butc/tcprocs.c
+@@ -388,6 +388,9 @@ STC_ReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *task
+ {
+ afs_int32 code;
+
++ memset(label, 0, sizeof(*label));
++ /* Synchronous, so no "real" ID; don't send stack garbage on the wire */
++ *taskId = 0;
+ #ifdef xbsa
+ if (CONF_XBSA)
+ return (TC_BADTASK); /* ReadLabel does not apply if XBSA */
+diff --git a/src/butc/tcstatus.c b/src/butc/tcstatus.c
+index 1bd280f2d1..3f98ba31d5 100644
+--- a/src/butc/tcstatus.c
++++ b/src/butc/tcstatus.c
+@@ -56,14 +56,13 @@ STC_GetStatus(struct rx_call *call, afs_uint32 taskId,
+ statusP ptr;
+ int retval = 0;
+
++ memset(statusPtr, 0, sizeof(*statusPtr));
+ if (callPermitted(call) == 0)
+ return (TC_NOTPERMITTED);
+
+ lock_Status();
+ ptr = findStatus(taskId);
+ if (ptr) {
+- /* strcpy(statusPtr->status, ptr->status); */
+-
+ strcpy(statusPtr->taskName, ptr->taskName);
+ strcpy(statusPtr->volumeName, ptr->volumeName);
+ statusPtr->taskId = ptr->taskId;
+@@ -143,6 +142,7 @@ STC_ScanStatus(struct rx_call *call, afs_uint32 *taskId,
+ statusP ptr = 0;
+ dlqlinkP dlqPtr;
+
++ memset(statusPtr, 0, sizeof(*statusPtr));
+ if (callPermitted(call) == 0)
+ return (TC_NOTPERMITTED);
+
+diff --git a/src/kauth/kaprocs.c b/src/kauth/kaprocs.c
+index e2c058c6fb..faaad1ee6d 100644
+--- a/src/kauth/kaprocs.c
++++ b/src/kauth/kaprocs.c
+@@ -1695,6 +1695,7 @@ kamListEntry(struct rx_call *call,
+ afs_int32 caller;
+ struct kaentry tentry;
+
++ memset(name, 0, sizeof(*name));
+ COUNT_REQ(ListEntry);
+ if ((code = InitAuthServ(&tt, LOCKREAD, this_op)))
+ return code;
+diff --git a/src/ptserver/ptprocs.c b/src/ptserver/ptprocs.c
+index f9f48fc689..2303c7399d 100644
+--- a/src/ptserver/ptprocs.c
++++ b/src/ptserver/ptprocs.c
+@@ -687,7 +687,7 @@ idToName(struct rx_call *call, idlist *aid, namelist *aname)
+ return 0;
+ if (size < 0 || size > INT_MAX / PR_MAXNAMELEN)
+ return PRTOOMANY;
+- aname->namelist_val = (prname *) malloc(size * PR_MAXNAMELEN);
++ aname->namelist_val = (prname *) calloc(size, PR_MAXNAMELEN);
+ aname->namelist_len = 0;
+ if (aname->namelist_val == 0)
+ return PRNOMEM;
+@@ -1647,6 +1647,7 @@ put_prentries(struct prentry *tentry, prentries *bulkentries)
+ entry = (struct prlistentries *)bulkentries->prentries_val;
+ entry += bulkentries->prentries_len;
+
++ memset(entry, 0, sizeof(*entry));
+ entry->flags = tentry->flags >> PRIVATE_SHIFT;
+ if (entry->flags == 0) {
+ entry->flags =
+@@ -1661,7 +1662,6 @@ put_prentries(struct prentry *tentry, prentries *bulkentries)
+ entry->nusers = tentry->nusers;
+ entry->count = tentry->count;
+ strncpy(entry->name, tentry->name, PR_MAXNAMELEN);
+- memset(entry->reserved, 0, sizeof(entry->reserved));
+ bulkentries->prentries_len++;
+ return 0;
+ }
+diff --git a/src/ubik/vote.c b/src/ubik/vote.c
+index e4fbdef219..b2d7c00997 100644
+--- a/src/ubik/vote.c
++++ b/src/ubik/vote.c
+@@ -412,6 +412,7 @@ SVOTE_Debug(struct rx_call * rxcall, struct ubik_debug * aparm)
+ /* fill in the basic debug structure. Note the the RPC protocol transfers,
+ * integers in host order. */
+
++ memset(aparm, 0, sizeof(*aparm));
+ aparm->now = FT_ApproxTime();
+ aparm->lastYesTime = ubik_lastYesTime;
+ aparm->lastYesHost = ntohl(lastYesHost);
+diff --git a/src/volser/volprocs.c b/src/volser/volprocs.c
+index d6cd1047a6..e4a459c107 100644
+--- a/src/volser/volprocs.c
++++ b/src/volser/volprocs.c
+@@ -432,6 +432,7 @@ SAFSVolPartitionInfo(struct rx_call *acid, char *pname, struct diskPartition
+ struct diskPartition64 *dp = (struct diskPartition64 *)
+ malloc(sizeof(struct diskPartition64));
+
++ memset(partition, 0, sizeof(*partition));
+ code = VolPartitionInfo(acid, pname, dp);
+ if (!code) {
+ strncpy(partition->name, dp->name, 32);
+@@ -451,6 +452,7 @@ SAFSVolPartitionInfo64(struct rx_call *acid, char *pname, struct diskPartition64
+ {
+ afs_int32 code;
+
++ memset(partition, 0, sizeof(*partition));
+ code = VolPartitionInfo(acid, pname, partition);
+ osi_auditU(acid, VS_ParInfEvent, code, AUD_STR, pname, AUD_END);
+ return code;
+@@ -2851,6 +2853,7 @@ VolMonitor(struct rx_call *acid, transDebugEntries *transInfo)
+ goto done; /*no active transactions */
+ for (tt = allTrans; tt; tt = nt) { /*copy relevant info into pntr */
+ nt = tt->next;
++ memset(pntr, 0, sizeof(*pntr));
+ VTRANS_OBJ_LOCK(tt);
+ pntr->tid = tt->tid;
+ pntr->time = tt->time;
--- /dev/null
+diff --git a/src/WINNT/afsd/cm_callback.c b/src/WINNT/afsd/cm_callback.c
+index 5816bbe3cc..a5db1403e5 100644
+--- a/src/WINNT/afsd/cm_callback.c
++++ b/src/WINNT/afsd/cm_callback.c
+@@ -1256,6 +1256,7 @@ SRXAFSCB_TellMeAboutYourself( struct rx_call *callp,
+ }
+
+ /* return all network interface addresses */
++ memset(addr, 0, sizeof(*addr));
+ addr->numberOfInterfaces = cm_noIPAddr;
+ addr->uuid = cm_data.Uuid;
+ for ( i=0; i < cm_noIPAddr; i++ ) {
+diff --git a/src/afs/afs_callback.c b/src/afs/afs_callback.c
+index 61b2a75b7a..038cddab21 100644
+--- a/src/afs/afs_callback.c
++++ b/src/afs/afs_callback.c
+@@ -306,6 +306,7 @@ SRXAFSCB_GetLock(struct rx_call *a_call, afs_int32 a_index,
+ XSTATS_START_CMTIME(AFS_STATS_CM_RPCIDX_GETLOCK);
+
+ AFS_STATCNT(SRXAFSCB_GetLock);
++ memset(a_result, 0, sizeof(*a_result));
+ nentries = sizeof(ltable) / sizeof(struct ltable);
+ if (a_index < 0 || a_index >= nentries+afs_cellindex) {
+ /*
+@@ -1618,6 +1619,7 @@ SRXAFSCB_TellMeAboutYourself(struct rx_call *a_call,
+ ObtainReadLock(&afs_xinterface);
+
+ /* return all network interface addresses */
++ memset(addr, 0, sizeof(*addr));
+ addr->numberOfInterfaces = afs_cb_interface.numberOfInterfaces;
+ addr->uuid = afs_cb_interface.uuid;
+ for (i = 0; i < afs_cb_interface.numberOfInterfaces; i++) {
+diff --git a/src/budb/procs.c b/src/budb/procs.c
+index f2987d3115..f976ff3aa3 100644
+--- a/src/budb/procs.c
++++ b/src/budb/procs.c
+@@ -424,6 +424,7 @@ FillDumpEntry(struct ubik_trans *ut, dbadr da, void *rock)
+ struct budb_dumpEntry *dump = (struct budb_dumpEntry *)rock;
+ struct dump d, ad;
+
++ memset(dump, 0, sizeof(*dump));
+ if (dbread(ut, da, &d, sizeof(d)))
+ return BUDB_IO;
+ dump->id = ntohl(d.id);
+diff --git a/src/butc/tcprocs.c b/src/butc/tcprocs.c
+index d41112a806..cc19e532d5 100644
+--- a/src/butc/tcprocs.c
++++ b/src/butc/tcprocs.c
+@@ -372,6 +372,9 @@ STC_ReadLabel(struct rx_call *acid, struct tc_tapeLabel *label, afs_uint32 *task
+ {
+ afs_int32 code;
+
++ memset(label, 0, sizeof(*label));
++ /* Synchronous, so no "real" ID; don't send stack garbage on the wire */
++ *taskId = 0;
+ #ifdef xbsa
+ if (CONF_XBSA)
+ return (TC_BADTASK); /* ReadLabel does not apply if XBSA */
+diff --git a/src/butc/tcstatus.c b/src/butc/tcstatus.c
+index fbe46a49f9..db06b514fe 100644
+--- a/src/butc/tcstatus.c
++++ b/src/butc/tcstatus.c
+@@ -46,14 +46,13 @@ STC_GetStatus(struct rx_call *call, afs_uint32 taskId,
+ statusP ptr;
+ int retval = 0;
+
++ memset(statusPtr, 0, sizeof(*statusPtr));
+ if (callPermitted(call) == 0)
+ return (TC_NOTPERMITTED);
+
+ lock_Status();
+ ptr = findStatus(taskId);
+ if (ptr) {
+- /* strcpy(statusPtr->status, ptr->status); */
+-
+ strcpy(statusPtr->taskName, ptr->taskName);
+ strcpy(statusPtr->volumeName, ptr->volumeName);
+ statusPtr->taskId = ptr->taskId;
+@@ -133,6 +132,7 @@ STC_ScanStatus(struct rx_call *call, afs_uint32 *taskId,
+ statusP ptr = 0;
+ dlqlinkP dlqPtr;
+
++ memset(statusPtr, 0, sizeof(*statusPtr));
+ if (callPermitted(call) == 0)
+ return (TC_NOTPERMITTED);
+
+diff --git a/src/kauth/kaprocs.c b/src/kauth/kaprocs.c
+index 315096a461..1c6c68f972 100644
+--- a/src/kauth/kaprocs.c
++++ b/src/kauth/kaprocs.c
+@@ -1700,6 +1700,7 @@ kamListEntry(struct rx_call *call,
+ afs_int32 caller;
+ struct kaentry tentry;
+
++ memset(name, 0, sizeof(*name));
+ COUNT_REQ(ListEntry);
+ if ((code = InitAuthServ(&tt, LOCKREAD, this_op)))
+ return code;
+diff --git a/src/ptserver/ptprocs.c b/src/ptserver/ptprocs.c
+index 3045ef45f5..4ce1cbf92f 100644
+--- a/src/ptserver/ptprocs.c
++++ b/src/ptserver/ptprocs.c
+@@ -651,7 +651,7 @@ idToName(struct rx_call *call, idlist *aid, namelist *aname, afs_int32 *cid)
+ return 0;
+ if (size < 0 || size > INT_MAX / PR_MAXNAMELEN)
+ return PRTOOMANY;
+- aname->namelist_val = malloc(size * PR_MAXNAMELEN);
++ aname->namelist_val = calloc(size, PR_MAXNAMELEN);
+ aname->namelist_len = 0;
+ if (aname->namelist_val == 0)
+ return PRNOMEM;
+@@ -1538,6 +1538,7 @@ put_prentries(struct prentry *tentry, prentries *bulkentries)
+ entry = bulkentries->prentries_val;
+ entry += bulkentries->prentries_len;
+
++ memset(entry, 0, sizeof(*entry));
+ entry->flags = tentry->flags >> PRIVATE_SHIFT;
+ if (entry->flags == 0) {
+ entry->flags =
+@@ -1552,7 +1553,6 @@ put_prentries(struct prentry *tentry, prentries *bulkentries)
+ entry->nusers = tentry->nusers;
+ entry->count = tentry->count;
+ strncpy(entry->name, tentry->name, PR_MAXNAMELEN);
+- memset(entry->reserved, 0, sizeof(entry->reserved));
+ bulkentries->prentries_len++;
+ return 0;
+ }
+diff --git a/src/ubik/vote.c b/src/ubik/vote.c
+index ba4ff5a807..2cf1887ecb 100644
+--- a/src/ubik/vote.c
++++ b/src/ubik/vote.c
+@@ -403,6 +403,7 @@ SVOTE_Debug(struct rx_call * rxcall, struct ubik_debug * aparm)
+ /* fill in the basic debug structure. Note the the RPC protocol transfers,
+ * integers in host order. */
+
++ memset(aparm, 0, sizeof(*aparm));
+ aparm->now = FT_ApproxTime();
+ aparm->lastYesTime = vote_globals.ubik_lastYesTime;
+ aparm->lastYesHost = ntohl(vote_globals.lastYesHost);
+diff --git a/src/volser/volprocs.c b/src/volser/volprocs.c
+index 2c9c3001c1..985c0a01da 100644
+--- a/src/volser/volprocs.c
++++ b/src/volser/volprocs.c
+@@ -420,6 +420,7 @@ SAFSVolPartitionInfo(struct rx_call *acid, char *pname, struct diskPartition
+ afs_int32 code;
+ struct diskPartition64 *dp = malloc(sizeof(struct diskPartition64));
+
++ memset(partition, 0, sizeof(*partition));
+ code = VolPartitionInfo(acid, pname, dp);
+ if (!code) {
+ strncpy(partition->name, dp->name, 32);
+@@ -439,6 +440,7 @@ SAFSVolPartitionInfo64(struct rx_call *acid, char *pname, struct diskPartition64
+ {
+ afs_int32 code;
+
++ memset(partition, 0, sizeof(*partition));
+ code = VolPartitionInfo(acid, pname, partition);
+ osi_auditU(acid, VS_ParInfEvent, code, AUD_STR, pname, AUD_END);
+ return code;
+@@ -2844,6 +2846,7 @@ VolMonitor(struct rx_call *acid, transDebugEntries *transInfo)
+ goto done; /*no active transactions */
+ for (tt = allTrans; tt; tt = nt) { /*copy relevant info into pntr */
+ nt = tt->next;
++ memset(pntr, 0, sizeof(*pntr));
+ VTRANS_OBJ_LOCK(tt);
+ pntr->tid = tt->tid;
+ pntr->time = tt->time;
--- /dev/null
+diff --git a/src/budb/budb.rg b/src/budb/budb.rg
+index 1ec45f1cac..6e61652ff2 100644
+--- a/src/budb/budb.rg
++++ b/src/budb/budb.rg
+@@ -224,7 +224,7 @@ typedef struct budb_volumeEntry budb_volumeList<BUDB_MAX_RETURN_LIST>;
+ typedef struct budb_dumpEntry budb_dumpList<BUDB_MAX_RETURN_LIST>;
+ typedef struct budb_tapeEntry budb_tapeList<BUDB_MAX_RETURN_LIST>;
+ typedef afs_int32 budb_dumpsList<BUDB_MAX_RETURN_LIST>;
+-typedef char charListT<>;
++typedef char charListT<4096>;
+
+ %#define BUDB_TEXT_COMPLETE 1
+
+diff --git a/src/rxgen/rpc_parse.c b/src/rxgen/rpc_parse.c
+index ab26c954a4..f5d7c70338 100644
+--- a/src/rxgen/rpc_parse.c
++++ b/src/rxgen/rpc_parse.c
+@@ -411,6 +411,9 @@ get_declaration(declaration * dec, defkind dkind)
+ }
+ dec->rel = REL_ARRAY;
+ if (peekscan(TOK_RANGLE, &tok)) {
++ if ((dkind == DEF_INPARAM) || (dkind == DEF_INOUTPARAM)) {
++ error("input arrays must specify a max size");
++ }
+ dec->array_max = "~0u"; /* unspecified size, use max */
+ } else {
+ scan_num(&tok);
+@@ -953,7 +956,7 @@ hdle_param_tok(definition * defp, declaration * dec, token * tokp,
+ Proc_list->component_kind = DEF_PARAM;
+ Proc_list->code = alloc(250);
+ Proc_list->scode = alloc(250);
+- get_declaration(dec, DEF_PARAM);
++ get_declaration(dec, par_kind);
+ Proc_list->pl.param_name = dec->name;
+ get1_param_type(defp, dec, &Proc_list->pl.param_type);
+ print_param(dec);
+diff --git a/src/vlserver/vldbint.xg b/src/vlserver/vldbint.xg
+index ff34d12d45..523fe801e7 100644
+--- a/src/vlserver/vldbint.xg
++++ b/src/vlserver/vldbint.xg
+@@ -208,7 +208,12 @@ const VLOP_DUMP = 0x100;
+ typedef vldbentry bulkentries<>;
+ typedef nvldbentry nbulkentries<>;
+ typedef uvldbentry ubulkentries<>;
+-typedef afs_uint32 bulkaddrs<>;
++/*
++ * 500 is an arbitrary implementation limit, larger than what we support storing.
++ * It lets the XDR decoder detect an attack (excessively large input) and reject
++ * it without incurring excessive resource usage.
++ */
++typedef afs_uint32 bulkaddrs<500>;
+
+ struct VLCallBack {
+ afs_uint32 CallBackVersion;
+diff --git a/src/volser/volint.xg b/src/volser/volint.xg
+index 5febc63308..3c982e5cdd 100644
+--- a/src/volser/volint.xg
++++ b/src/volser/volint.xg
+@@ -65,6 +65,7 @@ statindex 16
+ %#define VOLDUMPV2_OMITDIRS 1
+
+ const SIZE = 1024;
++const NMAXNSERVERS = 13;
+
+ struct volser_status {
+ afs_uint32 volID; /* Volume id--unique over all systems */
+@@ -247,7 +248,7 @@ struct volintSize {
+ afs_uint64 dump_size;
+ };
+
+-typedef replica manyDests<>;
++typedef replica manyDests<NMAXNSERVERS>;
+ typedef afs_int32 manyResults<>;
+ typedef transDebugInfo transDebugEntries<>;
+ typedef volintInfo volEntries<>;
+@@ -256,7 +257,7 @@ typedef volintXInfo volXEntries<>;
+
+ proc CreateVolume(
+ IN afs_int32 partition,
+- string name<>,
++ string name<VNAMESIZE>,
+ IN afs_int32 type,
+ IN afs_uint32 parent,
+ INOUT afs_uint32 *volid,
+@@ -290,7 +291,7 @@ proc Clone(
+ IN afs_int32 trans,
+ IN afs_uint32 purgeVol,
+ IN afs_int32 newType,
+- IN string newName<>,
++ IN string newName<VNAMESIZE>,
+ INOUT afs_uint32 *newVol
+ ) = VOLCLONE;
+
+@@ -338,7 +339,7 @@ proc GetStatus(
+ ) = VOLGETSTATUS;
+
+ proc SignalRestore(
+- IN string name<>,
++ IN string name<VNAMESIZE>,
+ int type,
+ afs_uint32 pid,
+ afs_uint32 cloneid
+@@ -356,7 +357,7 @@ proc ListVolumes(
+
+ proc SetIdsTypes(
+ IN afs_int32 tId,
+- string name<>,
++ string name<VNAMESIZE>,
+ afs_int32 type,
+ afs_uint32 pId,
+ afs_uint32 cloneId,
+@@ -368,7 +369,7 @@ proc Monitor(
+ ) = VOLMONITOR;
+
+ proc PartitionInfo(
+- IN string name<>,
++ IN string name<4096>,
+ OUT struct diskPartition *partition
+ ) = VOLDISKPART;
+
+@@ -441,7 +442,7 @@ proc DumpV2(
+ ) split = VOLDUMPV2;
+
+ proc PartitionInfo64(
+- IN string name<>,
++ IN string name<4096>,
+ OUT struct diskPartition64 *partition
+ ) = VOLDISKPART64;
+
--- /dev/null
+diff --git a/src/budb/budb.rg b/src/budb/budb.rg
+index 1ec45f1cac..6e61652ff2 100644
+--- a/src/budb/budb.rg
++++ b/src/budb/budb.rg
+@@ -224,7 +224,7 @@ typedef struct budb_volumeEntry budb_volumeList<BUDB_MAX_RETURN_LIST>;
+ typedef struct budb_dumpEntry budb_dumpList<BUDB_MAX_RETURN_LIST>;
+ typedef struct budb_tapeEntry budb_tapeList<BUDB_MAX_RETURN_LIST>;
+ typedef afs_int32 budb_dumpsList<BUDB_MAX_RETURN_LIST>;
+-typedef char charListT<>;
++typedef char charListT<4096>;
+
+ %#define BUDB_TEXT_COMPLETE 1
+
+diff --git a/src/rxgen/rpc_parse.c b/src/rxgen/rpc_parse.c
+index 79c120727b..5a3f27d845 100644
+--- a/src/rxgen/rpc_parse.c
++++ b/src/rxgen/rpc_parse.c
+@@ -458,6 +458,9 @@ get_declaration(declaration * dec, defkind dkind)
+ }
+ dec->rel = REL_ARRAY;
+ if (peekscan(TOK_RANGLE, &tok)) {
++ if ((dkind == DEF_INPARAM) || (dkind == DEF_INOUTPARAM)) {
++ error("input arrays must specify a max size");
++ }
+ dec->array_max = "~0"; /* unspecified size, use max */
+ } else {
+ scan_num(&tok);
+@@ -998,7 +1001,7 @@ hdle_param_tok(definition * defp, declaration * dec, token * tokp,
+ Proc_list->component_kind = DEF_PARAM;
+ Proc_list->code = alloc(250);
+ Proc_list->scode = alloc(250);
+- get_declaration(dec, DEF_PARAM);
++ get_declaration(dec, par_kind);
+ Proc_list->pl.param_name = dec->name;
+ get1_param_type(defp, dec, &Proc_list->pl.param_type);
+ print_param(dec);
+diff --git a/src/vlserver/vldbint.xg b/src/vlserver/vldbint.xg
+index d97e092c48..d955a0da51 100644
+--- a/src/vlserver/vldbint.xg
++++ b/src/vlserver/vldbint.xg
+@@ -200,7 +200,12 @@ const VLOP_DUMP = 0x100;
+ typedef vldbentry bulkentries<>;
+ typedef nvldbentry nbulkentries<>;
+ typedef uvldbentry ubulkentries<>;
+-typedef afs_uint32 bulkaddrs<>;
++/*
++ * 500 is an arbitrary implementation limit, larger than what we support storing.
++ * It lets the XDR decoder detect an attack (excessively large input) and reject
++ * it without incurring excessive resource usage.
++ */
++typedef afs_uint32 bulkaddrs<500>;
+
+ struct VLCallBack {
+ afs_uint32 CallBackVersion;
+diff --git a/src/volser/volint.xg b/src/volser/volint.xg
+index 4d20f0a7a2..8c48da36b4 100644
+--- a/src/volser/volint.xg
++++ b/src/volser/volint.xg
+@@ -65,6 +65,7 @@ statindex 16
+ %#define VOLDUMPV2_OMITDIRS 1
+
+ const SIZE = 1024;
++const NMAXNSERVERS = 13;
+
+ struct volser_status {
+ afs_uint32 volID; /* Volume id--unique over all systems */
+@@ -246,7 +247,7 @@ struct volintSize {
+ afs_uint64 dump_size;
+ };
+
+-typedef replica manyDests<>;
++typedef replica manyDests<NMAXNSERVERS>;
+ typedef afs_int32 manyResults<>;
+ typedef transDebugInfo transDebugEntries<>;
+ typedef volintInfo volEntries<>;
+@@ -255,7 +256,7 @@ typedef volintXInfo volXEntries<>;
+
+ proc CreateVolume(
+ IN afs_int32 partition,
+- string name<>,
++ string name<VNAMESIZE>,
+ IN afs_int32 type,
+ IN afs_uint32 parent,
+ INOUT afs_uint32 *volid,
+@@ -289,7 +290,7 @@ proc Clone(
+ IN afs_int32 trans,
+ IN afs_uint32 purgeVol,
+ IN afs_int32 newType,
+- IN string newName<>,
++ IN string newName<VNAMESIZE>,
+ INOUT afs_uint32 *newVol
+ ) = VOLCLONE;
+
+@@ -337,7 +338,7 @@ proc GetStatus(
+ ) = VOLGETSTATUS;
+
+ proc SignalRestore(
+- IN string name<>,
++ IN string name<VNAMESIZE>,
+ int type,
+ afs_uint32 pid,
+ afs_uint32 cloneid
+@@ -355,7 +356,7 @@ proc ListVolumes(
+
+ proc SetIdsTypes(
+ IN afs_int32 tId,
+- string name<>,
++ string name<VNAMESIZE>,
+ afs_int32 type,
+ afs_uint32 pId,
+ afs_uint32 cloneId,
+@@ -367,7 +368,7 @@ proc Monitor(
+ ) = VOLMONITOR;
+
+ proc PartitionInfo(
+- IN string name<>,
++ IN string name<4096>,
+ OUT struct diskPartition *partition
+ ) = VOLDISKPART;
+
+@@ -440,7 +441,7 @@ proc DumpV2(
+ ) split = VOLDUMPV2;
+
+ proc PartitionInfo64(
+- IN string name<>,
++ IN string name<4096>,
+ OUT struct diskPartition64 *partition
+ ) = VOLDISKPART64;
+
--- /dev/null
+diff --git a/src/budb/budb.rg b/src/budb/budb.rg
+index 1ec45f1cac..6e61652ff2 100644
+--- a/src/budb/budb.rg
++++ b/src/budb/budb.rg
+@@ -224,7 +224,7 @@ typedef struct budb_volumeEntry budb_volumeList<BUDB_MAX_RETURN_LIST>;
+ typedef struct budb_dumpEntry budb_dumpList<BUDB_MAX_RETURN_LIST>;
+ typedef struct budb_tapeEntry budb_tapeList<BUDB_MAX_RETURN_LIST>;
+ typedef afs_int32 budb_dumpsList<BUDB_MAX_RETURN_LIST>;
+-typedef char charListT<>;
++typedef char charListT<4096>;
+
+ %#define BUDB_TEXT_COMPLETE 1
+
+diff --git a/src/rxgen/rpc_parse.c b/src/rxgen/rpc_parse.c
+index ab26c954a4..f5d7c70338 100644
+--- a/src/rxgen/rpc_parse.c
++++ b/src/rxgen/rpc_parse.c
+@@ -411,6 +411,9 @@ get_declaration(declaration * dec, defkind dkind)
+ }
+ dec->rel = REL_ARRAY;
+ if (peekscan(TOK_RANGLE, &tok)) {
++ if ((dkind == DEF_INPARAM) || (dkind == DEF_INOUTPARAM)) {
++ error("input arrays must specify a max size");
++ }
+ dec->array_max = "~0u"; /* unspecified size, use max */
+ } else {
+ scan_num(&tok);
+@@ -953,7 +956,7 @@ hdle_param_tok(definition * defp, declaration * dec, token * tokp,
+ Proc_list->component_kind = DEF_PARAM;
+ Proc_list->code = alloc(250);
+ Proc_list->scode = alloc(250);
+- get_declaration(dec, DEF_PARAM);
++ get_declaration(dec, par_kind);
+ Proc_list->pl.param_name = dec->name;
+ get1_param_type(defp, dec, &Proc_list->pl.param_type);
+ print_param(dec);
+diff --git a/src/vlserver/vldbint.xg b/src/vlserver/vldbint.xg
+index ff34d12d45..523fe801e7 100644
+--- a/src/vlserver/vldbint.xg
++++ b/src/vlserver/vldbint.xg
+@@ -208,7 +208,12 @@ const VLOP_DUMP = 0x100;
+ typedef vldbentry bulkentries<>;
+ typedef nvldbentry nbulkentries<>;
+ typedef uvldbentry ubulkentries<>;
+-typedef afs_uint32 bulkaddrs<>;
++/*
++ * 500 is an arbitrary implementation limit, larger than what we support storing.
++ * It lets the XDR decoder detect an attack (excessively large input) and reject
++ * it without incurring excessive resource usage.
++ */
++typedef afs_uint32 bulkaddrs<500>;
+
+ struct VLCallBack {
+ afs_uint32 CallBackVersion;
+diff --git a/src/volser/volint.xg b/src/volser/volint.xg
+index 5febc63308..3c982e5cdd 100644
+--- a/src/volser/volint.xg
++++ b/src/volser/volint.xg
+@@ -65,6 +65,7 @@ statindex 16
+ %#define VOLDUMPV2_OMITDIRS 1
+
+ const SIZE = 1024;
++const NMAXNSERVERS = 13;
+
+ struct volser_status {
+ afs_uint32 volID; /* Volume id--unique over all systems */
+@@ -247,7 +248,7 @@ struct volintSize {
+ afs_uint64 dump_size;
+ };
+
+-typedef replica manyDests<>;
++typedef replica manyDests<NMAXNSERVERS>;
+ typedef afs_int32 manyResults<>;
+ typedef transDebugInfo transDebugEntries<>;
+ typedef volintInfo volEntries<>;
+@@ -256,7 +257,7 @@ typedef volintXInfo volXEntries<>;
+
+ proc CreateVolume(
+ IN afs_int32 partition,
+- string name<>,
++ string name<VNAMESIZE>,
+ IN afs_int32 type,
+ IN afs_uint32 parent,
+ INOUT afs_uint32 *volid,
+@@ -290,7 +291,7 @@ proc Clone(
+ IN afs_int32 trans,
+ IN afs_uint32 purgeVol,
+ IN afs_int32 newType,
+- IN string newName<>,
++ IN string newName<VNAMESIZE>,
+ INOUT afs_uint32 *newVol
+ ) = VOLCLONE;
+
+@@ -338,7 +339,7 @@ proc GetStatus(
+ ) = VOLGETSTATUS;
+
+ proc SignalRestore(
+- IN string name<>,
++ IN string name<VNAMESIZE>,
+ int type,
+ afs_uint32 pid,
+ afs_uint32 cloneid
+@@ -356,7 +357,7 @@ proc ListVolumes(
+
+ proc SetIdsTypes(
+ IN afs_int32 tId,
+- string name<>,
++ string name<VNAMESIZE>,
+ afs_int32 type,
+ afs_uint32 pId,
+ afs_uint32 cloneId,
+@@ -368,7 +369,7 @@ proc Monitor(
+ ) = VOLMONITOR;
+
+ proc PartitionInfo(
+- IN string name<>,
++ IN string name<4096>,
+ OUT struct diskPartition *partition
+ ) = VOLDISKPART;
+
+@@ -441,7 +442,7 @@ proc DumpV2(
+ ) split = VOLDUMPV2;
+
+ proc PartitionInfo64(
+- IN string name<>,
++ IN string name<4096>,
+ OUT struct diskPartition64 *partition
+ ) = VOLDISKPART64;
+