1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//! # Mechanism registry
//!
//! The Registry allows users to configure which mechanisms are enabled and their order of
//! importance.
//! By default the registry will collect and enable all known Mechanisms. It will prefer external
//! (i.e. coming from 3rd party downstream crates) Mechanisms over the built-in ones and prefers
//! built-in ones roughly by their cryptographic strength, preferring SSO-enabled mechanism.
//! An exception is made for DIGEST_MD5 and CRAM_MD5 which are always given the least priority.
//!
//! So the rough default priority goes:
//! - OPENID20, SAML20, GS2-*, GSSAPI
//! - SCRAM-SHA-256(-PLUS)
//! - SCRAM-SHA-1(-PLUS)
//! - PLAIN, SECURID
//! - LOGIN
//! - ANONYMOUS, EXTERNAL
//! - CRAM_MD5, DIGEST_MD5
//!
//! ## Static compile-time registry using dtolnay's `linkme` crate
//!
//! When compiled with the `registry_static` feature flag rsasl has a static registry collecting
//! all available mechanisms at linking time. To submit a crate you need to define a pub `static`
//! [`MechanismClient`] and/or [`MechanismServer`] that are annotated with the
//! [`distributed_slice`] proc-macro (re-exported from `linkme` by this module):
//!
//! ```rust
//! # use std::io::Write;
//! # use rsasl::mechanism::Authentication;
//! use rsasl::session::{SessionData, StepResult, Side};
//! # use rsasl::mechname::Mechname;
//!
//! // X-MYCOOLMECHANISM doesn't store any data between steps so it's an empty struct here
//! pub struct MyCoolMechanism;
//! impl Authentication for MyCoolMechanism {
//! # fn step(&mut self, session: &mut SessionData, input: Option<&[u8]>, writer: &mut dyn Write) -> StepResult {
//! #     unimplemented!()
//! # }
//! }
//!
//! use rsasl::registry::Mechanism;
//!
//! // Since the static registry requires a feature flag, downstream crates should gate
//! // automatic registration the same way. Either by matching on `feature = "rsasl/registry_static"
//! // or by adding a feature flag that enables "rsasl/registry_static".
//! #[cfg(feature = "rsasl/registry_static")]
//! use rsasl::registry::{distributed_slice, MECHANISMS};
//!
//! #[cfg_attr(feature = "rsasl/registry_static", distributed_slice(MECHANISMS))]
//! // It is *crucial* that these `static`s are marked `pub` and reachable by dependent crates, see
//! // the Note below.
//! pub static MYCOOLMECHANISM: Mechanism = Mechanism {
//!     mechanism: Mechname::const_new_unchecked(b"X-MYCOOLMECHANISM"),
//!     priority: 1100,
//!     client: Some(|_sasl| Ok(Box::new(MyCoolMechanism))),
//!     // In this case only the client side is implemented
//!     server: None,
//!     first: Side::Client,
//! };
//! ```
//!
//! Note: Due to [rustc issue #47384](https://github.com/rust-lang/rust/issues/47384) the static(s)
//! for your Mechanism MUST be marked `pub` and be reachable by dependent crates, otherwise they
//! may be silently dropped by the compiler.

use crate::mechanism::Authentication;
use crate::mechname::Mechname;
use crate::{SASLError, Side, SASL};
use std::fmt::{Debug, Display, Formatter};

#[cfg(feature = "registry_static")]
pub use registry_static::*;

pub type MatchFn = fn(name: &Mechname) -> bool;
pub type StartFn = fn(sasl: &SASL) -> Result<Box<dyn Authentication>, SASLError>;

#[derive(Copy, Clone)]
/// Mechanism Implementation
///
/// All mechanisms need to export a `static Mechanism` to be usable by rsasl, see the [module
/// documentation][crate::registry] for details.
pub struct Mechanism {
    /// The Mechanism served by this implementation.
    pub mechanism: &'static Mechname,

    pub priority: usize,

    pub client: Option<StartFn>,
    pub server: Option<StartFn>,

    pub first: Side,
}

pub struct MechanismSecurityFactors {
    /// Maximum possible Security Strength Factor (SSF) of the security layers installed
    ///
    /// SSF is a very fuzzy value but in general equates to the numbers of 'bits' of security,
    /// usually being linked to the key size. E.g. encryption using DES has `56`, 3DES `112`,
    /// AES128 `128`, and so on.
    /// Security layers that do not provide confidentiality (i.e. encryption) but integrity
    /// protection (via e.g. HMAC) usually have a SSF of 1.
    pub max_ssf: u16,

    /// This mechanism doesn't transfer secrets in plain text and is thus not susceptible to
    /// simple eavesdropping attacks.
    pub noplain: bool,
    /// This mechanism supports mutual authentication, i.e. if the authentication exchange
    /// succeeds then both the client and server have verified the identity of the other.
    pub mutual: bool,
}

impl Mechanism {
    pub fn client(&self, sasl: &SASL) -> Option<Result<Box<dyn Authentication>, SASLError>> {
        self.client.map(|f| f(sasl))
    }

    pub fn server(&self, sasl: &SASL) -> Option<Result<Box<dyn Authentication>, SASLError>> {
        self.server.map(|f| f(sasl))
    }
}

impl Debug for Mechanism {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("Mechanism")
            .field("name", &self.mechanism)
            .field("has client", &self.client.is_some())
            .field("has server", &self.server.is_some())
            .finish()
    }
}

impl Display for Mechanism {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.mechanism)
    }
}

#[cfg(feature = "registry_static")]
mod registry_static {
    use super::Mechanism;
    pub use linkme::distributed_slice;

    #[distributed_slice]
    pub static MECHANISMS: [Mechanism] = [..];
}

#[cfg(feature = "registry_dynamic")]
impl SASL {
    pub fn register(&mut self, mechanism: &'static Mechanism) {
        self.dynamic_mechs.push(mechanism)
    }
}