diff --git a/src/keyring.rs b/src/keyring.rs index b1388a5..005d220 100644 --- a/src/keyring.rs +++ b/src/keyring.rs @@ -223,6 +223,70 @@ impl KeyRing { Ok(()) } + /// Link another keyring to this keyring. + /// + /// Behaves similarly to link_key, but links a KeyRing instead. The caller + /// must have link permission on the keyring being added as a link, and + /// write permission on this keyring. + pub fn link_keyring(&self, keyring: KeyRing) -> Result<(), KeyError> { + _ = ffi::keyctl!( + KeyCtlOperation::Link, + keyring.id.as_raw_id() as libc::c_ulong, + self.id.as_raw_id() as libc::c_ulong + )?; + Ok(()) + } + + /// Unlink another keyring from this keyring. + /// + /// Behaves similarly to unlink_key, but unlinks a KeyRing instead. The + /// caller must have write permission on the keyring to remove links + /// from it. + pub fn unlink_keyring(&self, keyring: KeyRing) -> Result<(), KeyError> { + _ = ffi::keyctl!( + KeyCtlOperation::Unlink, + keyring.id.as_raw_id() as libc::c_ulong, + self.id.as_raw_id() as libc::c_ulong + )?; + Ok(()) + } + + /// Link a default keyring from this keyring. + /// + /// This method does the same thing as link_keyring, but links one of the + /// special keyrings defined by the system. This is useful when you + /// don't want to have to open a keyring before linking it. + /// + /// The caller must have link permissions on the added keyring, and write + /// permission on this keyring. Requesting to link to a non-existent default + /// keyring will result in that keyring being created automatically. + pub fn link_keyring_id(&self, keyringid: KeyRingIdentifier) -> Result<(), KeyError> { + _ = ffi::keyctl!( + KeyCtlOperation::Link, + keyringid as libc::c_ulong, + self.id.as_raw_id() as libc::c_ulong + )?; + Ok(()) + } + + /// Unlink a default keyring from this keyring. + /// + /// This method does the same thing as unlink_keyring, but unlinks one of + /// the special keyrings defined by the system. This is useful when you + /// don't want to have to open a keyring before unlinking it. + /// + /// The caller must have write permission on this keyring. In addition, this + /// method will return KeyError::KeyDoesNotExist if the target keyring has + /// not yet been created. + pub fn unlink_keyring_id(&self, keyringid: KeyRingIdentifier) -> Result<(), KeyError> { + _ = ffi::keyctl!( + KeyCtlOperation::Unlink, + keyringid as libc::c_ulong, + self.id.as_raw_id() as libc::c_ulong + )?; + Ok(()) + } + /// Clear the contents of (i.e., unlink all keys from) this keyring. /// /// The caller must have write permission on the keyring. @@ -357,6 +421,81 @@ mod test { assert_eq!(result.unwrap_err(), KeyError::KeyDoesNotExist); } + #[test] + fn test_link_unlink_keyrings() { + // Get a couple of unlinked keyrings to test + let sess = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap(); + assert!(sess.id.as_raw_id() > 0); + let thread = KeyRing::from_special_id(KeyRingIdentifier::Thread, true).unwrap(); + assert!(thread.id.as_raw_id() > 0); + + // Assert that the keyrings are not linked + let items = sess.get_links(200).unwrap(); + assert!(!items.contains(&thread)); + + // Link the keyrings + let _ = sess.link_keyring(thread).unwrap(); + + // Assert that the keyrings are now linked + let items = sess.get_links(200).unwrap(); + assert!(items.contains(&thread)); + + // Unlink the keyrings + let _ = sess.unlink_keyring(thread).unwrap(); + + // Assert that the keyrings are unlinked again + let items = sess.get_links(200).unwrap(); + assert!(!items.contains(&thread)); + } + #[test] + fn test_link_unlink_keyrings_with_id() { + // Get a couple of unlinked keyrings to test + let sess = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap(); + assert!(sess.id.as_raw_id() > 0); + let thread = KeyRing::from_special_id(KeyRingIdentifier::Thread, true).unwrap(); + assert!(thread.id.as_raw_id() > 0); + + // Assert that the keyrings are not linked + let items = sess.get_links(200).unwrap(); + assert!(!items.contains(&thread)); + + // Link the keyrings + let _ = sess.link_keyring_id(KeyRingIdentifier::Thread).unwrap(); + + // Assert that the keyrings are now linked + let items = sess.get_links(200).unwrap(); + assert!(items.contains(&thread)); + + // Unlink the keyrings + let _ = sess.unlink_keyring_id(KeyRingIdentifier::Thread).unwrap(); + + // Assert that the keyrings are unlinked again + let items = sess.get_links(200).unwrap(); + assert!(!items.contains(&thread)); + } + + #[test] + fn test_linking_nonexistent_keyrings() { + // Get existent keyring + let sess = KeyRing::from_special_id(KeyRingIdentifier::Session, false).unwrap(); + assert!(sess.id.as_raw_id() > 0); + + // Test that the target keyring doesn't exist + let thread = KeyRing::from_special_id(KeyRingIdentifier::Thread, false); + assert!(matches!(thread, Err(KeyError::KeyDoesNotExist))); + + // Unlinking a non-existent keyring + let result = sess.unlink_keyring_id(KeyRingIdentifier::Thread); + assert!(matches!(result, Err(KeyError::KeyDoesNotExist))); + + // Linking a non-existent keyring + sess.link_keyring_id(KeyRingIdentifier::Thread).unwrap(); + + // After attempting to link the special keyring, it will have been created + let sess = KeyRing::from_special_id(KeyRingIdentifier::Thread, false).unwrap(); + assert!(sess.id.as_raw_id() > 0); + } + #[test] fn test_get_linked_items() { // Test that a keyring that should already exist is returned