Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Honoring server-reported and client-enabled capabilities #49

Open
4 of 8 tasks
Tracked by #12
nevans opened this issue Nov 23, 2021 · 3 comments
Open
4 of 8 tasks
Tracked by #12

Honoring server-reported and client-enabled capabilities #49

nevans opened this issue Nov 23, 2021 · 3 comments
Labels
IMAP4rev1 Requirement for IMAP4rev1, RFC3501 IMAP4rev2 Requirement for IMAP4rev2, RFC9051

Comments

@nevans
Copy link
Collaborator

nevans commented Nov 23, 2021

Using capabilities information

The client should track whenever it is knowingly violating capabilitied, either server reported or client enabled. At the very least, this should include checking capabilities before sending specific commands or command arguments.

Client-enabled capabilties

There will be no API to work around client.enable(capability). Users who are determined to evade this limitation can figure out how cheat with client.instance_variable_set(...).

Server reported capabilities

The server capabilties cache will always be kept up-to-date when capabilities are checked. If the server hasn't sent its capabilities unsolicited, the client will request capabilties whenever capabilities are checked.

If the capabilities check fails:

  • For new commands or new arguments to existing commands.
    • A CapabilityError will be raised without issuing any command to the server,
  • Where there is a security risk.
    • A CapabilityError will be raised without issuing any command to the server,
    • This is backwards incompatible!
  • For other existing commands with existing command arguments.
    • A warning will be printed to $stderr but the command will still be sent

In some future version of Net::IMAP (perhaps the version released with ruby 3.2 or 3.3), the default for existing commands should change to match the behavior for new commands.

Configuration

To opt-out of the new behavior or to opt-in to more strict behavior:

  1. Net::IMAP#enforce_capabilities=
    • nil -- Uses the current default behavior, which can change in future releases.
    • true -- Always raise an exception for any failed capabilities checks. Never knowingly sends unsupported commands or command arguments to the server.
    • :warn -- Prints a warning to $stderr for non-security-related capabilities, regardless of the command or the current Net::IMAP default. Still raises for security-related capabilities checks.
    • false -- Restores old default behavior: client doesn't care about server capabilities. Still warns for security-related capabilities checks.
    • def []: (CapabilityError, Net::IMAP) -> (nil | true | false | :warn), eg. a Proc. May change security-related capabilities handling.. The command, command args, required capabilities, etc will all be available on CapabilityError.
  2. Net::IMAP#initialize(*args, **kwargs, enforce_capabilities: cfg)
    • Shorthand for Net::IMAP.new(...).tap {|client| client.enforce_capabilities = cfg }
  3. Net::IMAP#cmdname(..., enforce_capabilities: cfg)
    • #cmdname represents any command which might check server capability.
    • This kwarg also changes security-related capabilities handling for #starttls, #authenticate, #login.

opting out of security errors

enforce_capabilities: false will still raise a warning for security related violations. These warnings can only be disabled with a proc.

client.authenticate("PLAIN", username, password, enforce_capabilities: false)
# $stderr << "warning: Ignoring server capability \"AUTH=PLAIN\"" unless capability?("AUTH=PLAIN")
# The server might respond with a tagged `NO` => `NoResponseError`
client.login(username, password, enforce_capabilities: false)
# $stderr << "warning: Ignoring server capability \"LOGINDISABLED\"" if capability?("LOGINDISABLED")
# The server might respond with a tagged `NO` => `NoResponseError`

# Always ignore server capabilities. Please don't do this!
client = Net::IMAP.new(..., enforce_capabilities: -> _ { false })
client.enforce_capabilities = -> _ { false }

Allow fine-grained configuration with a Proc

The arguments will be a CapabilityError exception (which can be raised) and the Net::IMAP client object (in case the proc is shared between multiple clients). CapabilityError should have at least one sub-class, SecurityCapabilityError, to represent LOGINDISABLED, AUTH=, STARTTLS capabilities, etc. CapabilityError and its subclasses should allow pattern matching via #deconstruct and/or #deconstruct_keys

This could be used to:

  • add support for capabilities which haven't been added to net-imap yet
  • log unexpectedly missing security related capabilities errors.
  • log all capabilities errors.
client.enforce_capabilities = proc do |error|
  case error
  in AuthCapabilityError{sasl_mechanism: "FOOBAR"}
    raise "Server doesn't support our pretend SASL mechanism"
  in SecurityCapabilityError{command: "AUTHENTICATE" | "STARTTLS"}
    raise SecureAuthUnsupportedError
  in SecurityCapabilityError
    raise error
  in command: "SELECT" | "EXAMINE", capability: "QRESYNC"
    error.run_alternate_commands do
      # a theoretical API for gracefully degrading
    end
  in capability: "X_PLEASE_WARN"
    logger.warn { "X_WARNING: error.warning" }
  in capability: "X_PLEASE_WARN_2"
    # let Net::IMAP handle warning via $stderr
    # just like client.enforce_capabilities = :warn
    :warn
  in capability: "BINARY"
    # let Net::IMAP raise the error
    # just like client.enforce_capabilities = true
    true
  in capability: "X_THIS_CLIENT_ALLOWS_IT"
    # ignore the missing capability
    # just like client.enforce_capabilities = false
    false
  else
    # use the default behavior for this version of Net::IMAP, i.e. warn now, raise later
    # just like client.enforce_capabilities = nil
    nil
  end
end
@nevans
Copy link
Collaborator Author

nevans commented Nov 23, 2021

FWIW, I've already implemented much of this in my forked IMAP client. But I haven't implemented the (configurable) backward-compatible error handling yet. Capabilities handling is the next thing on my list to merge & implement, because it's needed for several of the other extensions.

@nevans
Copy link
Collaborator Author

nevans commented Nov 23, 2021

The API for Net::IMAP#ignore_server_capabilities= in the description is just a proposal. Please let me know what you think. Hopefully I'll have code and proper documentation soon.

@nevans
Copy link
Collaborator Author

nevans commented Oct 27, 2022

replaced ignore_server_capabilities with enforce_capabilities

@nevans nevans added IMAP4rev2 Requirement for IMAP4rev2, RFC9051 IMAP4rev1 Requirement for IMAP4rev1, RFC3501 labels Feb 12, 2023
@nevans nevans pinned this issue Feb 17, 2023
@nevans nevans unpinned this issue Oct 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
IMAP4rev1 Requirement for IMAP4rev1, RFC3501 IMAP4rev2 Requirement for IMAP4rev2, RFC9051
Development

No branches or pull requests

1 participant