Files
Stalwart/crates/directory/src/ldap/lookup.rs
2023-08-28 19:44:23 +02:00

433 lines
15 KiB
Rust

/*
* Copyright (c) 2023 Stalwart Labs Ltd.
*
* This file is part of Stalwart Mail Server.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
* in the LICENSE file at the top-level directory of this distribution.
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* You can be released from the requirements of the AGPLv3 license by
* purchasing a commercial license. Please contact licensing@stalw.art
* for more details.
*/
use ldap3::{ResultEntry, Scope, SearchEntry};
use mail_send::Credentials;
use crate::{Directory, Principal, QueryColumn, Type};
use super::{LdapDirectory, LdapMappings};
#[async_trait::async_trait]
impl Directory for LdapDirectory {
async fn authenticate(
&self,
credentials: &Credentials<String>,
) -> crate::Result<Option<Principal>> {
let (username, secret) = match credentials {
Credentials::Plain { username, secret } => (username, secret),
Credentials::OAuthBearer { token } => (token, token),
Credentials::XOauth2 { username, secret } => (username, secret),
};
match self
.find_principal(&self.mappings.filter_name.build(username))
.await
{
Ok(Some(principal)) => {
if principal.verify_secret(secret).await {
Ok(Some(principal))
} else {
Ok(None)
}
}
result => result,
}
}
async fn principal(&self, name: &str) -> crate::Result<Option<Principal>> {
self.find_principal(&self.mappings.filter_name.build(name))
.await
}
async fn emails_by_name(&self, name: &str) -> crate::Result<Vec<String>> {
let (rs, _res) = self
.pool
.get()
.await?
.search(
&self.mappings.base_dn,
Scope::Subtree,
&self.mappings.filter_name.build(name),
&self.mappings.attrs_email,
)
.await?
.success()?;
let mut emails = Vec::new();
for entry in rs {
let entry = SearchEntry::construct(entry);
for attr in &self.mappings.attrs_email {
if let Some(values) = entry.attrs.get(attr) {
for email in values {
if !email.is_empty() {
emails.push(email.to_string());
}
}
}
}
}
Ok(emails)
}
async fn names_by_email(&self, address: &str) -> crate::Result<Vec<String>> {
let names = self
.pool
.get()
.await?
.search(
&self.mappings.base_dn,
Scope::Subtree,
&self
.mappings
.filter_email
.build(self.opt.subaddressing.to_subaddress(address).as_ref()),
&self.mappings.attr_name,
)
.await?
.success()
.map(|(rs, _res)| self.extract_names(rs))?;
if !names.is_empty() {
Ok(names)
} else if let Some(address) = self.opt.catch_all.to_catch_all(address) {
self.pool
.get()
.await?
.search(
&self.mappings.base_dn,
Scope::Subtree,
&self.mappings.filter_email.build(address.as_ref()),
&self.mappings.attr_name,
)
.await?
.success()
.map(|(rs, _res)| self.extract_names(rs))
.map_err(|e| e.into())
} else {
Ok(names)
}
}
async fn rcpt(&self, address: &str) -> crate::Result<bool> {
match self
.pool
.get()
.await?
.streaming_search(
&self.mappings.base_dn,
Scope::Subtree,
&self
.mappings
.filter_email
.build(self.opt.subaddressing.to_subaddress(address).as_ref()),
&self.mappings.attr_email_address,
)
.await?
.next()
.await
{
Ok(Some(_)) => Ok(true),
Ok(None) => {
if let Some(address) = self.opt.catch_all.to_catch_all(address) {
self.pool
.get()
.await?
.streaming_search(
&self.mappings.base_dn,
Scope::Subtree,
&self.mappings.filter_email.build(address.as_ref()),
&self.mappings.attr_email_address,
)
.await?
.next()
.await
.map(|entry| entry.is_some())
.map_err(|e| e.into())
} else {
Ok(false)
}
}
Err(e) => Err(e.into()),
}
}
async fn vrfy(&self, address: &str) -> crate::Result<Vec<String>> {
let mut stream = self
.pool
.get()
.await?
.streaming_search(
&self.mappings.base_dn,
Scope::Subtree,
&self
.mappings
.filter_verify
.build(self.opt.subaddressing.to_subaddress(address).as_ref()),
&self.mappings.attr_email_address,
)
.await?;
let mut emails = Vec::new();
while let Some(entry) = stream.next().await? {
let entry = SearchEntry::construct(entry);
for attr in &self.mappings.attr_email_address {
if let Some(values) = entry.attrs.get(attr) {
for email in values {
if !email.is_empty() {
emails.push(email.to_string());
}
}
}
}
}
Ok(emails)
}
async fn expn(&self, address: &str) -> crate::Result<Vec<String>> {
let mut stream = self
.pool
.get()
.await?
.streaming_search(
&self.mappings.base_dn,
Scope::Subtree,
&self
.mappings
.filter_expand
.build(self.opt.subaddressing.to_subaddress(address).as_ref()),
&self.mappings.attr_email_address,
)
.await?;
let mut emails = Vec::new();
while let Some(entry) = stream.next().await? {
let entry = SearchEntry::construct(entry);
for attr in &self.mappings.attr_email_address {
if let Some(values) = entry.attrs.get(attr) {
for email in values {
if !email.is_empty() {
emails.push(email.to_string());
}
}
}
}
}
Ok(emails)
}
async fn lookup(&self, query: &str, params: &[&str]) -> crate::Result<bool> {
self.query_(query, params)
.await
.map(|entry| entry.is_some())
}
async fn query(&self, query: &str, params: &[&str]) -> crate::Result<Vec<QueryColumn>> {
self.query_(query, params).await.map(|entry| {
if let Some(entry) = entry {
let mut object = String::new();
for (attr, values) in SearchEntry::construct(entry).attrs {
for value in values {
object.push_str(&attr);
object.push(':');
object.push_str(&value);
object.push('\n');
}
}
vec![QueryColumn::Text(object)]
} else {
vec![]
}
})
}
async fn is_local_domain(&self, domain: &str) -> crate::Result<bool> {
self.pool
.get()
.await?
.streaming_search(
&self.mappings.base_dn,
Scope::Subtree,
&self.mappings.filter_domains.build(domain),
Vec::<String>::new(),
)
.await?
.next()
.await
.map(|entry| entry.is_some())
.map_err(|e| e.into())
}
}
impl LdapDirectory {
async fn query_(&self, query: &str, params: &[&str]) -> crate::Result<Option<ResultEntry>> {
let mut conn = self.pool.get().await?;
tracing::trace!(context = "directory", event = "query", query = query, params = ?params);
if !params.is_empty() {
let mut expanded_query = String::with_capacity(query.len() + params.len() * 2);
for (pos, item) in query.split('?').enumerate() {
if pos > 0 {
if let Some(param) = params.get(pos - 1) {
expanded_query.push_str(param);
}
}
expanded_query.push_str(item);
}
conn.streaming_search(
&self.mappings.base_dn,
Scope::Subtree,
&expanded_query,
Vec::<String>::new(),
)
.await
} else {
conn.streaming_search(
&self.mappings.base_dn,
Scope::Subtree,
query,
Vec::<String>::new(),
)
.await
}?
.next()
.await
.map_err(|e| e.into())
}
async fn find_principal(&self, filter: &str) -> crate::Result<Option<Principal>> {
let (rs, _res) = self
.pool
.get()
.await?
.search(
&self.mappings.base_dn,
Scope::Subtree,
filter,
&self.mappings.attrs_principal,
)
.await?
.success()?;
if let Some(mut principal) = rs.into_iter().next().map(|entry| {
self.mappings
.entry_to_principal(SearchEntry::construct(entry))
}) {
// Map groups
if !principal.member_of.is_empty() {
let mut conn = self.pool.get().await?;
let mut names = Vec::with_capacity(principal.member_of.len());
for group in principal.member_of {
if group.contains('=') {
let (rs, _res) = conn
.search(
&group,
Scope::Base,
"objectClass=*",
&self.mappings.attr_name,
)
.await?
.success()?;
for entry in rs {
'outer: for (attr, value) in SearchEntry::construct(entry).attrs {
if self.mappings.attr_name.contains(&attr) {
if let Some(group) = value.first() {
if !group.is_empty() {
if !group
.eq_ignore_ascii_case(&self.opt.superuser_group)
{
names.push(group.to_string());
} else {
principal.typ = Type::Superuser;
}
break 'outer;
}
}
}
}
}
} else if !group.eq_ignore_ascii_case(&self.opt.superuser_group) {
names.push(group);
} else {
principal.typ = Type::Superuser;
}
}
principal.member_of = names;
}
Ok(Some(principal))
} else {
Ok(None)
}
}
fn extract_names(&self, rs: Vec<ResultEntry>) -> Vec<String> {
let mut names = Vec::with_capacity(rs.len());
for entry in rs {
let entry = SearchEntry::construct(entry);
'outer: for attr in &self.mappings.attr_name {
if let Some(value) = entry.attrs.get(attr).and_then(|v| v.first()) {
if !value.is_empty() {
names.push(value.to_string());
break 'outer;
}
}
}
}
names
}
}
impl LdapMappings {
pub fn entry_to_principal(&self, entry: SearchEntry) -> Principal {
let mut principal = Principal::default();
for (attr, value) in entry.attrs {
if self.attr_name.contains(&attr) {
principal.name = value.into_iter().next().unwrap_or_default();
} else if self.attr_secret.contains(&attr) {
principal.secrets.extend(value);
} else if let Some(idx) = self.attr_description.iter().position(|a| a == &attr) {
if principal.description.is_none() || idx == 0 {
principal.description = value.into_iter().next();
}
} else if self.attr_groups.contains(&attr) {
principal.member_of.extend(value);
} else if self.attr_quota.contains(&attr) {
if let Ok(quota) = value.into_iter().next().unwrap_or_default().parse() {
principal.quota = quota;
}
} else if attr.eq_ignore_ascii_case("objectClass") {
if value.contains(&self.obj_user) {
principal.typ = Type::Individual;
} else if value.contains(&self.obj_group) {
principal.typ = Type::Group;
}
}
}
principal
}
}