Files
bichon/src/modules/imap/manager.rs
T

176 lines
6.5 KiB
Rust
Raw Normal View History

2025-11-19 02:14:37 +08:00
//
// Copyright (c) 2025 rustmailer.com (https://rustmailer.com)
//
// This file is part of the Bichon Email Archiving Project
//
// 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.
//
// 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/>.
use crate::modules::account::dispatcher::STATUS_DISPATCHER;
use crate::modules::account::entity::AuthType;
use crate::modules::account::migration::{AccountModel, AccountType};
use crate::modules::error::code::ErrorCode;
use crate::modules::error::BichonResult;
use crate::modules::imap::capabilities::{
capability_to_string, check_capabilities, fetch_capabilities,
};
use crate::modules::imap::client::Client;
use crate::modules::imap::oauth2::OAuth2;
use crate::modules::imap::session::SessionStream;
use crate::modules::oauth2::token::OAuth2AccessToken;
use crate::{bichon_version, decrypt, raise_error};
2025-11-19 02:14:37 +08:00
use async_imap::Session;
use tracing::error;
#[derive(Debug)]
pub struct ImapConnectionManager {
pub account_id: u64,
}
impl ImapConnectionManager {
pub fn new(account_id: u64) -> Self {
Self { account_id }
}
pub async fn fetch_account(&self) -> BichonResult<AccountModel> {
// Fetch the account entity in non-test environment
AccountModel::get(self.account_id).await
}
async fn create_client(&self, account: &AccountModel) -> BichonResult<Client> {
assert_eq!(account.account_type, AccountType::IMAP);
let imap = account.imap.as_ref().unwrap();
Client::connection(
&imap.host,
&imap.encryption,
imap.port,
imap.use_proxy,
account.use_dangerous,
)
.await
2025-11-19 02:14:37 +08:00
}
async fn authenticate(
&self,
client: Client,
account: &AccountModel,
) -> BichonResult<Session<Box<dyn SessionStream>>> {
assert_eq!(account.account_type, AccountType::IMAP);
let imap = account.imap.as_ref().unwrap();
let username = account.name.clone().unwrap_or(account.email.clone());
2025-11-19 02:14:37 +08:00
match &imap.auth.auth_type {
AuthType::Password => {
let password = &imap.auth.password.clone().ok_or_else(|| {
raise_error!(
"Imap auth type is Passwd, but password not set".into(),
ErrorCode::MissingConfiguration
)
})?;
let password = decrypt!(&password)?;
client.login(&username, &password).await
2025-11-19 02:14:37 +08:00
}
AuthType::OAuth2 => {
let record = OAuth2AccessToken::get(self.account_id).await?;
let access_token = record.and_then(|r| r.access_token).ok_or_else(|| {
raise_error!(
"Imap auth type is OAuth2, but OAuth2 authorization is not yet complete."
.into(),
ErrorCode::MissingConfiguration
)
})?;
client
.authenticate(OAuth2::new(username, access_token))
2025-11-19 02:14:37 +08:00
.await
}
}
}
pub async fn build(&self) -> BichonResult<Session<Box<dyn SessionStream>>> {
let account = self.fetch_account().await?;
let client = match self.create_client(&account).await {
Ok(client) => client,
Err(error) => {
error!(
"Failed to create IMAP {}'s client: {:#?}",
&account.email, error
);
STATUS_DISPATCHER
.append_error(
self.account_id,
format!("imap client connect error: {:#?}", error),
)
.await;
return Err(error);
}
};
let mut session = match self.authenticate(client, &account).await {
Ok(session) => session,
Err(error) => {
error!("Failed to authenticate IMAP session: {:#?}", error);
STATUS_DISPATCHER
.append_error(
self.account_id,
format!("imap client authenticate error: {:#?}", error),
)
.await;
return Err(error);
}
};
match fetch_capabilities(&mut session).await {
Ok(capabilities) => {
let to_save: Vec<String> = capabilities.iter().map(capability_to_string).collect();
AccountModel::update_capabilities(self.account_id, to_save).await?;
if let Err(error) = check_capabilities(&capabilities) {
error!("Failed to check IMAP capabilities: {:#?}", error);
STATUS_DISPATCHER
.append_error(
self.account_id,
format!("imap client check capabilities error: {:#?}", error),
)
.await;
return Err(error);
}
if capabilities.has_str("ID") || capabilities.has_str("id") {
session
.id([
("name", Some("bichon")),
("version", Some(bichon_version!())),
("vendor", Some("rustmailer")),
])
.await
.map_err(|e| {
raise_error!(format!("{:#?}", e), ErrorCode::ImapCommandFailed)
})?;
}
2025-11-19 02:14:37 +08:00
}
Err(error) => {
error!("Failed to fetch IMAP capabilities: {:#?}", error);
STATUS_DISPATCHER
.append_error(
self.account_id,
format!("imap client fetch capabilities error: {:#?}", error),
)
.await;
return Err(error);
}
}
Ok(session)
}
}