Line data Source code
1 : //! Simple Authentication and Security Layer.
2 : //!
3 : //! RFC: <https://datatracker.ietf.org/doc/html/rfc4422>.
4 : //!
5 : //! Reference implementation:
6 : //! * <https://github.com/postgres/postgres/blob/94226d4506e66d6e7cbf4b391f1e7393c1962841/src/backend/libpq/auth-sasl.c>
7 : //! * <https://github.com/postgres/postgres/blob/94226d4506e66d6e7cbf4b391f1e7393c1962841/src/interfaces/libpq/fe-auth.c>
8 :
9 : mod channel_binding;
10 : mod messages;
11 : mod stream;
12 :
13 : use crate::error::{ReportableError, UserFacingError};
14 : use std::io;
15 : use thiserror::Error;
16 :
17 : pub use channel_binding::ChannelBinding;
18 : pub use messages::FirstMessage;
19 : pub use stream::{Outcome, SaslStream};
20 :
21 : /// Fine-grained auth errors help in writing tests.
22 0 : #[derive(Error, Debug)]
23 : pub enum Error {
24 : #[error("Channel binding failed: {0}")]
25 : ChannelBindingFailed(&'static str),
26 :
27 : #[error("Unsupported channel binding method: {0}")]
28 : ChannelBindingBadMethod(Box<str>),
29 :
30 : #[error("Bad client message: {0}")]
31 : BadClientMessage(&'static str),
32 :
33 : #[error("Internal error: missing digest")]
34 : MissingBinding,
35 :
36 : #[error(transparent)]
37 : Io(#[from] io::Error),
38 : }
39 :
40 : impl UserFacingError for Error {
41 0 : fn to_string_client(&self) -> String {
42 0 : use Error::*;
43 0 : match self {
44 0 : ChannelBindingFailed(m) => m.to_string(),
45 0 : ChannelBindingBadMethod(m) => format!("unsupported channel binding method {m}"),
46 0 : _ => "authentication protocol violation".to_string(),
47 : }
48 0 : }
49 : }
50 :
51 : impl ReportableError for Error {
52 0 : fn get_error_kind(&self) -> crate::error::ErrorKind {
53 0 : match self {
54 0 : Error::ChannelBindingFailed(_) => crate::error::ErrorKind::User,
55 0 : Error::ChannelBindingBadMethod(_) => crate::error::ErrorKind::User,
56 0 : Error::BadClientMessage(_) => crate::error::ErrorKind::User,
57 0 : Error::MissingBinding => crate::error::ErrorKind::Service,
58 0 : Error::Io(_) => crate::error::ErrorKind::ClientDisconnect,
59 : }
60 0 : }
61 : }
62 :
63 : /// A convenient result type for SASL exchange.
64 : pub type Result<T> = std::result::Result<T, Error>;
65 :
66 : /// A result of one SASL exchange.
67 : #[must_use]
68 : pub enum Step<T, R> {
69 : /// We should continue exchanging messages.
70 : Continue(T, String),
71 : /// The client has been authenticated successfully.
72 : Success(R, String),
73 : /// Authentication failed (reason attached).
74 : Failure(&'static str),
75 : }
76 :
77 : /// Every SASL mechanism (e.g. [SCRAM](crate::scram)) is expected to implement this trait.
78 : pub trait Mechanism: Sized {
79 : /// What's produced as a result of successful authentication.
80 : type Output;
81 :
82 : /// Produce a server challenge to be sent to the client.
83 : /// This is how this method is called in PostgreSQL (`libpq/sasl.h`).
84 : fn exchange(self, input: &str) -> Result<Step<Self, Self::Output>>;
85 : }
|