@ -21,16 +21,22 @@
use super ::{ ImapConnection , ImapProtocol , ImapServerConf , UIDStore } ;
use crate ::conf ::AccountSettings ;
use crate ::email ::parser ::IResult ;
use crate ::error ::{ MeliError , Result } ;
use crate ::get_conf_val ;
use crate ::imap ::RequiredResponses ;
use nom ::{
branch ::alt , bytes ::complete ::tag , combinator ::map , error ::Error as NomError , error ::ErrorKind ,
multi ::separated_list1 , sequence ::separated_pair , IResult ,
branch ::alt , bytes ::complete ::tag , combinator ::map , multi ::separated_list1 ,
sequence ::separated_pair ,
} ;
use std ::str ::FromStr ;
use std ::sync ::{ Arc , Mutex } ;
use std ::time ::SystemTime ;
pub struct ManageSieveConnection {
pub inner : ImapConnection ,
}
pub fn managesieve_capabilities ( input : & [ u8 ] ) -> Result < Vec < ( & [ u8 ] , & [ u8 ] ) > > {
let ( _ , ret ) = separated_list1 (
tag ( b" \r \n " ) ,
@ -42,26 +48,225 @@ pub fn managesieve_capabilities(input: &[u8]) -> Result<Vec<(&[u8], &[u8])>> {
Ok ( ret )
}
#[ test ]
fn test_managesieve_capabilities ( ) {
assert_eq ! ( managesieve_capabilities ( b" \" IMPLEMENTATION \" \" Dovecot Pigeonhole \" \r \n \" SIEVE \" \" fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext \" \r \n \" NOTIFY \" \" mailto \" \r \n \" SASL \" \" PLAIN \" \r \n \" STARTTLS \" \r \n \" VERSION \" \" 1.0 \" \r \n " ) . unwrap ( ) , vec ! [
( & b" IMPLEMENTATION " [ . . ] , & b" Dovecot Pigeonhole " [ . . ] ) ,
( & b" SIEVE " [ . . ] , & b" fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext " [ . . ] ) ,
( & b" NOTIFY " [ . . ] , & b" mailto " [ . . ] ) ,
( & b" SASL " [ . . ] , & b" PLAIN " [ . . ] ) ,
( & b" STARTTLS " [ . . ] , & b" " [ . . ] ) ,
( & b" VERSION " [ . . ] , & b" 1.0 " [ . . ] ) ]
) ;
#[ derive(PartialEq, Eq, Debug, Copy, Clone) ]
pub enum ManageSieveResponse < 'a > {
Ok {
code : Option < & 'a [ u8 ] > ,
message : Option < & 'a [ u8 ] > ,
} ,
NoBye {
code : Option < & 'a [ u8 ] > ,
message : Option < & 'a [ u8 ] > ,
} ,
}
mod parser {
use super ::* ;
use nom ::bytes ::complete ::tag ;
pub use nom ::bytes ::complete ::{ is_not , tag_no_case } ;
use nom ::character ::complete ::crlf ;
use nom ::combinator ::{ iterator , map , opt } ;
pub use nom ::sequence ::{ delimited , pair , preceded , terminated } ;
pub fn sieve_name ( input : & [ u8 ] ) -> IResult < & [ u8 ] , & [ u8 ] > {
crate ::backends ::imap ::protocol_parser ::string_token ( input )
}
// *(sieve-name [SP "ACTIVE"] CRLF)
// response-oknobye
pub fn listscripts ( input : & [ u8 ] ) -> IResult < & [ u8 ] , Vec < ( & [ u8 ] , bool ) > > {
let mut it = iterator (
input ,
alt ( (
terminated (
map ( terminated ( sieve_name , tag_no_case ( b" ACTIVE " ) ) , | r | {
( r , true )
} ) ,
crlf ,
) ,
terminated ( map ( sieve_name , | r | ( r , false ) ) , crlf ) ,
) ) ,
) ;
let parsed = ( & mut it ) . collect ::< Vec < ( & [ u8 ] , bool ) > > ( ) ;
let res : IResult < _ , _ > = it . finish ( ) ;
let ( rest , _ ) = res ? ;
Ok ( ( rest , parsed ) )
}
// response-getscript = (sieve-script CRLF response-ok) /
// response-nobye
pub fn getscript ( input : & [ u8 ] ) -> IResult < & [ u8 ] , & [ u8 ] > {
sieve_name ( input )
}
pub fn response_oknobye ( input : & [ u8 ] ) -> IResult < & [ u8 ] , ManageSieveResponse > {
alt ( (
map (
terminated (
pair (
preceded (
tag_no_case ( b" ok " ) ,
opt ( preceded (
tag ( b" " ) ,
delimited ( tag ( b" ( " ) , is_not ( ")" ) , tag ( b" ) " ) ) ,
) ) ,
) ,
opt ( preceded ( tag ( b" " ) , sieve_name ) ) ,
) ,
crlf ,
) ,
| ( code , message ) | ManageSieveResponse ::Ok { code , message } ,
) ,
map (
terminated (
pair (
preceded (
alt ( ( tag_no_case ( b" no " ) , tag_no_case ( b" bye " ) ) ) ,
opt ( preceded (
tag ( b" " ) ,
delimited ( tag ( b" ( " ) , is_not ( ")" ) , tag ( b" ) " ) ) ,
) ) ,
) ,
opt ( preceded ( tag ( b" " ) , sieve_name ) ) ,
) ,
crlf ,
) ,
| ( code , message ) | ManageSieveResponse ::NoBye { code , message } ,
) ,
) ) ( input )
}
#[ test ]
fn test_managesieve_listscripts ( ) {
let input_1 = b" \" summer_script \" \r \n \" vacation_script \" \r \n {13} \r \n clever \" script \r \n \" main_script \" ACTIVE \r \n OK " ;
assert_eq ! (
terminated ( listscripts , tag_no_case ( b" OK " ) ) ( input_1 ) ,
Ok ( (
& b" " [ . . ] ,
vec ! [
( & b" summer_script " [ . . ] , false ) ,
( & b" vacation_script " [ . . ] , false ) ,
( & b" clever \" script " [ . . ] , false ) ,
( & b" main_script " [ . . ] , true )
]
) )
) ;
let input_2 = b" \" summer_script \" \r \n \" main_script \" active \r \n ok " ;
assert_eq ! (
terminated ( listscripts , tag_no_case ( b" OK " ) ) ( input_2 ) ,
Ok ( (
& b" " [ . . ] ,
vec ! [ ( & b" summer_script " [ . . ] , false ) , ( & b" main_script " [ . . ] , true ) ]
) )
) ;
let input_3 = b" ok " ;
assert_eq ! (
terminated ( listscripts , tag_no_case ( b" OK " ) ) ( input_3 ) ,
Ok ( ( & b" " [ . . ] , vec ! [ ] ) )
) ;
}
#[ test ]
fn test_managesieve_general ( ) {
assert_eq ! ( managesieve_capabilities ( b" \" IMPLEMENTATION \" \" Dovecot Pigeonhole \" \r \n \" SIEVE \" \" fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext \" \r \n \" NOTIFY \" \" mailto \" \r \n \" SASL \" \" PLAIN \" \r \n \" STARTTLS \" \r \n \" VERSION \" \" 1.0 \" \r \n " ) . unwrap ( ) , vec ! [
( & b" IMPLEMENTATION " [ . . ] , & b" Dovecot Pigeonhole " [ . . ] ) ,
( & b" SIEVE " [ . . ] , & b" fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext " [ . . ] ) ,
( & b" NOTIFY " [ . . ] , & b" mailto " [ . . ] ) ,
( & b" SASL " [ . . ] , & b" PLAIN " [ . . ] ) ,
( & b" STARTTLS " [ . . ] , & b" " [ . . ] ) ,
( & b" VERSION " [ . . ] , & b" 1.0 " [ . . ] ) ]
) ;
let response_ok = b" OK (WARNINGS) \" line 8: server redirect action limit is 2, this redirect might be ignored \" \r \n " ;
assert_eq ! (
response_oknobye ( response_ok ) ,
Ok ( (
& b" " [ . . ] ,
ManageSieveResponse ::Ok {
code : Some ( & b" WARNINGS " [ . . ] ) ,
message : Some ( & b" line 8: server redirect action limit is 2, this redirect might be ignored " [ . . ] ) ,
}
) )
) ;
let response_ok = b" OK (WARNINGS) \r \n " ;
assert_eq ! (
response_oknobye ( response_ok ) ,
Ok ( (
& b" " [ . . ] ,
ManageSieveResponse ::Ok {
code : Some ( & b" WARNINGS " [ . . ] ) ,
message : None ,
}
) )
) ;
let response_ok =
b" OK \" line 8: server redirect action limit is 2, this redirect might be ignored \" \r \n " ;
assert_eq ! (
response_oknobye ( response_ok ) ,
Ok ( (
& b" " [ . . ] ,
ManageSieveResponse ::Ok {
code : None ,
message : Some ( & b" line 8: server redirect action limit is 2, this redirect might be ignored " [ . . ] ) ,
}
) )
) ;
let response_ok = b" Ok \r \n " ;
assert_eq ! (
response_oknobye ( response_ok ) ,
Ok ( (
& b" " [ . . ] ,
ManageSieveResponse ::Ok {
code : None ,
message : None ,
}
) )
) ;
let response_nobye = b" No (NONEXISTENT) \" There is no script by that name \" \r \n " ;
assert_eq ! (
response_oknobye ( response_nobye ) ,
Ok ( (
& b" " [ . . ] ,
ManageSieveResponse ::NoBye {
code : Some ( & b" NONEXISTENT " [ . . ] ) ,
message : Some ( & b" There is no script by that name " [ . . ] ) ,
}
) )
) ;
let response_nobye = b" No (NONEXISTENT) {31} \r \n There is no script by that name \r \n " ;
assert_eq ! (
response_oknobye ( response_nobye ) ,
Ok ( (
& b" " [ . . ] ,
ManageSieveResponse ::NoBye {
code : Some ( & b" NONEXISTENT " [ . . ] ) ,
message : Some ( & b" There is no script by that name " [ . . ] ) ,
}
) )
) ;
let response_nobye = b" No \r \n " ;
assert_eq ! (
response_oknobye ( response_nobye ) ,
Ok ( (
& b" " [ . . ] ,
ManageSieveResponse ::NoBye {
code : None ,
message : None ,
}
) )
) ;
}
}
// Return a byte sequence surrounded by "s and decoded if necessary
pub fn quoted_raw ( input : & [ u8 ] ) -> IResult < & [ u8 ] , & [ u8 ] > {
if input . is_empty ( ) | | input [ 0 ] ! = b'"' {
return Err ( nom ::Err ::Error ( NomError {
input ,
code : ErrorKind ::Tag ,
} ) ) ;
return Err ( nom ::Err ::Error ( ( input , "empty" ) . into ( ) ) ) ;
}
let mut i = 1 ;
@ -72,91 +277,199 @@ pub fn quoted_raw(input: &[u8]) -> IResult<&[u8], &[u8]> {
i + = 1 ;
}
Err ( nom ::Err ::Error ( NomError {
input ,
code : ErrorKind ::Tag ,
} ) )
}
pub trait ManageSieve {
fn havespace ( & mut self ) -> Result < ( ) > ;
fn putscript ( & mut self ) -> Result < ( ) > ;
fn listscripts ( & mut self ) -> Result < ( ) > ;
fn setactive ( & mut self ) -> Result < ( ) > ;
fn getscript ( & mut self ) -> Result < ( ) > ;
fn deletescript ( & mut self ) -> Result < ( ) > ;
fn renamescript ( & mut self ) -> Result < ( ) > ;
Err ( nom ::Err ::Error ( ( input , "no quotes" ) . into ( ) ) )
}
pub fn new_managesieve_connection (
account_hash : crate ::backends ::AccountHash ,
account_name : String ,
s : & AccountSettings ,
event_consumer : crate ::backends ::BackendEventConsumer ,
) -> Result < ImapConnection > {
let server_hostname = get_conf_val ! ( s [ "server_hostname" ] ) ? ;
let server_username = get_conf_val ! ( s [ "server_username" ] ) ? ;
let server_password = get_conf_val ! ( s [ "server_password" ] ) ? ;
let server_port = get_conf_val ! ( s [ "server_port" ] , 4190 ) ? ;
let danger_accept_invalid_certs : bool = get_conf_val ! ( s [ "danger_accept_invalid_certs" ] , false ) ? ;
let timeout = get_conf_val ! ( s [ "timeout" ] , 16_ u64 ) ? ;
let timeout = if timeout = = 0 {
None
} else {
Some ( std ::time ::Duration ::from_secs ( timeout ) )
} ;
let server_conf = ImapServerConf {
server_hostname : server_hostname . to_string ( ) ,
server_username : server_username . to_string ( ) ,
server_password : server_password . to_string ( ) ,
server_port ,
use_starttls : true ,
use_tls : true ,
danger_accept_invalid_certs ,
protocol : ImapProtocol ::ManageSieve ,
timeout ,
} ;
let uid_store = Arc ::new ( UIDStore {
is_online : Arc ::new ( Mutex ::new ( (
SystemTime ::now ( ) ,
Err ( MeliError ::new ( "Account is uninitialised." ) ) ,
) ) ) ,
. . UIDStore ::new (
account_hash ,
Arc ::new ( account_name ) ,
event_consumer ,
server_conf . timeout ,
)
} ) ;
Ok ( ImapConnection ::new_connection ( & server_conf , uid_store ) )
}
impl ManageSieveConnection {
pub fn new (
account_hash : crate ::backends ::AccountHash ,
account_name : String ,
s : & AccountSettings ,
event_consumer : crate ::backends ::BackendEventConsumer ,
) -> Result < Self > {
let server_hostname = get_conf_val ! ( s [ "server_hostname" ] ) ? ;
let server_username = get_conf_val ! ( s [ "server_username" ] ) ? ;
let server_password = get_conf_val ! ( s [ "server_password" ] ) ? ;
let server_port = get_conf_val ! ( s [ "server_port" ] , 4190 ) ? ;
let danger_accept_invalid_certs : bool =
get_conf_val ! ( s [ "danger_accept_invalid_certs" ] , false ) ? ;
let timeout = get_conf_val ! ( s [ "timeout" ] , 16_ u64 ) ? ;
let timeout = if timeout = = 0 {
None
} else {
Some ( std ::time ::Duration ::from_secs ( timeout ) )
} ;
let server_conf = ImapServerConf {
server_hostname : server_hostname . to_string ( ) ,
server_username : server_username . to_string ( ) ,
server_password : server_password . to_string ( ) ,
server_port ,
use_starttls : true ,
use_tls : true ,
danger_accept_invalid_certs ,
protocol : ImapProtocol ::ManageSieve ,
timeout ,
} ;
let uid_store = Arc ::new ( UIDStore {
is_online : Arc ::new ( Mutex ::new ( (
SystemTime ::now ( ) ,
Err ( MeliError ::new ( "Account is uninitialised." ) ) ,
) ) ) ,
. . UIDStore ::new (
account_hash ,
Arc ::new ( account_name ) ,
event_consumer ,
server_conf . timeout ,
)
} ) ;
Ok ( Self {
inner : ImapConnection ::new_connection ( & server_conf , uid_store ) ,
} )
}
impl ManageSieve for ImapConnection {
fn havespace ( & mut self ) -> Result < ( ) > {
pub async fn havespace ( & mut self ) -> Result < ( ) > {
Ok ( ( ) )
}
fn putscript ( & mut self ) -> Result < ( ) > {
Ok ( ( ) )
pub async fn putscript ( & mut self , script_name : & [ u8 ] , script : & [ u8 ] ) -> Result < ( ) > {
let mut ret = Vec ::new ( ) ;
self . inner
. send_literal ( format ! ( "Putscript {{{len}+}}\r\n" , len = script_name . len ( ) ) . as_bytes ( ) )
. await ? ;
self . inner . send_literal ( script_name ) . await ? ;
self . inner
. send_literal ( format ! ( " {{{len}+}}\r\n" , len = script . len ( ) ) . as_bytes ( ) )
. await ? ;
self . inner . send_literal ( script ) . await ? ;
self . inner
. read_response ( & mut ret , RequiredResponses ::empty ( ) )
. await ? ;
let ( _rest , response ) = parser ::response_oknobye ( & ret ) ? ;
match response {
ManageSieveResponse ::Ok { . . } = > Ok ( ( ) ) ,
ManageSieveResponse ::NoBye { code , message } = > Err ( format ! (
"Could not upload script: {} {}" ,
code . map ( | b | String ::from_utf8_lossy ( b ) ) . unwrap_or_default ( ) ,
message
. map ( | b | String ::from_utf8_lossy ( b ) )
. unwrap_or_default ( )
)
. into ( ) ) ,
}
}
fn listscripts ( & mut self ) -> Result < ( ) > {
Ok ( ( ) )
pub async fn listscripts ( & mut self ) -> Result < Vec < ( Vec < u8 > , bool ) > > {
let mut ret = Vec ::new ( ) ;
self . inner . send_command ( b" Listscripts " ) . await ? ;
self . inner
. read_response ( & mut ret , RequiredResponses ::empty ( ) )
. await ? ;
let ( _rest , scripts ) =
parser ::terminated ( parser ::listscripts , parser ::tag_no_case ( b" OK " ) ) ( & ret ) ? ;
Ok ( scripts
. into_iter ( )
. map ( | ( n , a ) | ( n . to_vec ( ) , a ) )
. collect ::< Vec < ( Vec < u8 > , bool ) > > ( ) )
}
fn setactive ( & mut self ) -> Result < ( ) > {
Ok ( ( ) )
pub async fn checkscript ( & mut self , script : & [ u8 ] ) -> Result < ( ) > {
let mut ret = Vec ::new ( ) ;
self . inner
. send_literal ( format ! ( "Checkscript {{{len}+}}\r\n" , len = script . len ( ) ) . as_bytes ( ) )
. await ? ;
self . inner . send_literal ( script ) . await ? ;
self . inner
. read_response ( & mut ret , RequiredResponses ::empty ( ) )
. await ? ;
let ( _rest , response ) = parser ::response_oknobye ( & ret ) ? ;
match response {
ManageSieveResponse ::Ok { . . } = > Ok ( ( ) ) ,
ManageSieveResponse ::NoBye { code , message } = > Err ( format ! (
"Checkscript reply: {} {}" ,
code . map ( | b | String ::from_utf8_lossy ( b ) ) . unwrap_or_default ( ) ,
message
. map ( | b | String ::from_utf8_lossy ( b ) )
. unwrap_or_default ( )
)
. into ( ) ) ,
}
}
fn getscript ( & mut self ) -> Result < ( ) > {
Ok ( ( ) )
pub async fn setactive ( & mut self , script_name : & [ u8 ] ) -> Result < ( ) > {
let mut ret = Vec ::new ( ) ;
self . inner
. send_literal ( format ! ( "Setactive {{{len}+}}\r\n" , len = script_name . len ( ) ) . as_bytes ( ) )
. await ? ;
self . inner . send_literal ( script_name ) . await ? ;
self . inner
. read_response ( & mut ret , RequiredResponses ::empty ( ) )
. await ? ;
let ( _rest , response ) = parser ::response_oknobye ( & ret ) ? ;
match response {
ManageSieveResponse ::Ok { . . } = > Ok ( ( ) ) ,
ManageSieveResponse ::NoBye { code , message } = > Err ( format ! (
"Could not set active script: {} {}" ,
code . map ( | b | String ::from_utf8_lossy ( b ) ) . unwrap_or_default ( ) ,
message
. map ( | b | String ::from_utf8_lossy ( b ) )
. unwrap_or_default ( )
)
. into ( ) ) ,
}
}
fn deletescript ( & mut self ) -> Result < ( ) > {
Ok ( ( ) )
pub async fn getscript ( & mut self , script_name : & [ u8 ] ) -> Result < Vec < u8 > > {
let mut ret = Vec ::new ( ) ;
self . inner
. send_literal ( format ! ( "Getscript {{{len}+}}\r\n" , len = script_name . len ( ) ) . as_bytes ( ) )
. await ? ;
self . inner . send_literal ( script_name ) . await ? ;
self . inner
. read_response ( & mut ret , RequiredResponses ::empty ( ) )
. await ? ;
if let Ok ( ( _ , ManageSieveResponse ::NoBye { code , message } ) ) =
parser ::response_oknobye ( & ret )
{
return Err ( format ! (
"Could not set active script: {} {}" ,
code . map ( | b | String ::from_utf8_lossy ( b ) ) . unwrap_or_default ( ) ,
message
. map ( | b | String ::from_utf8_lossy ( b ) )
. unwrap_or_default ( )
)
. into ( ) ) ;
}
let ( _rest , script ) =
parser ::terminated ( parser ::getscript , parser ::tag_no_case ( b" OK " ) ) ( & ret ) ? ;
Ok ( script . to_vec ( ) )
}
fn renamescript ( & mut self ) -> Result < ( ) > {
pub async fn deletescript ( & mut self , script_name : & [ u8 ] ) -> Result < ( ) > {
let mut ret = Vec ::new ( ) ;
self . inner
. send_literal (
format ! ( "Deletescript {{{len}+}}\r\n" , len = script_name . len ( ) ) . as_bytes ( ) ,
)
. await ? ;
self . inner . send_literal ( script_name ) . await ? ;
self . inner
. read_response ( & mut ret , RequiredResponses ::empty ( ) )
. await ? ;
let ( _rest , response ) = parser ::response_oknobye ( & ret ) ? ;
match response {
ManageSieveResponse ::Ok { . . } = > Ok ( ( ) ) ,
ManageSieveResponse ::NoBye { code , message } = > Err ( format ! (
"Could not delete script: {} {}" ,
code . map ( | b | String ::from_utf8_lossy ( b ) ) . unwrap_or_default ( ) ,
message
. map ( | b | String ::from_utf8_lossy ( b ) )
. unwrap_or_default ( )
)
. into ( ) ) ,
}
}
pub async fn renamescript ( & mut self ) -> Result < ( ) > {
Ok ( ( ) )
}
}