This commit is contained in:
Mauro D 2023-01-27 16:01:50 +00:00
parent 1dbb2ab8fc
commit cf9640069d
11 changed files with 323 additions and 37 deletions

13
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: stalwartlabs
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

22
.github/workflows/rust.yml vendored Normal file
View file

@ -0,0 +1,22 @@
name: "CI"
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose

View file

@ -1,5 +1,13 @@
[package]
name = "smtp-proto"
description = "SMTP protocol parser"
authors = [ "Stalwart Labs <hello@stalw.art>"]
repository = "https://github.com/stalwartlabs/smtp-proto"
homepage = "https://github.com/stalwartlabs/smtp-proto"
license = "AGPL-3.0-only"
keywords = ["smtp", "lmtp", "protocol", "parser"]
categories = ["email", "parser-implementations"]
readme = "README.md"
version = "0.1.0"
edition = "2021"

View file

@ -1,2 +1,37 @@
# smtp-proto
SMTP Protocol
[![crates.io](https://img.shields.io/crates/v/smtp-proto)](https://crates.io/crates/smtp-proto)
[![build](https://github.com/stalwartlabs/sieve/actions/workflows/rust.yml/badge.svg)](https://github.com/stalwartlabs/sieve/actions/workflows/rust.yml)
[![docs.rs](https://img.shields.io/docsrs/smtp-proto)](https://docs.rs/smtp-proto)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
_smtp-proto_ is a fast SMTP/LMTP parser for Rust that supports all [registered SMTP service extensions](https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml).
The library is part of Stalwart SMTP and LMTP servers. It is not yet documented so if you need help using the library please start a discussion.
## Testing & Fuzzing
To run the testsuite:
```bash
$ cargo test
```
To fuzz the library with `cargo-fuzz`:
```bash
$ cargo +nightly fuzz run smtp_proto
```
## License
Licensed under the terms of the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.en.html) as published by
the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
See [LICENSE](LICENSE) for more details.
You can be released from the requirements of the AGPLv3 license by purchasing
a commercial license. Please contact licensing@stalw.art for more details.
## Copyright
Copyright (C) 2020-2023, Stalwart Labs Ltd.

View file

@ -1,3 +1,64 @@
/*
* Copyright (c) 2020-2023, Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP protocol parser.
*
* 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.
*/
//! # smtp-proto
//!
//! [![crates.io](https://img.shields.io/crates/v/smtp-proto)](https://crates.io/crates/smtp-proto)
//! [![build](https://github.com/stalwartlabs/sieve/actions/workflows/rust.yml/badge.svg)](https://github.com/stalwartlabs/sieve/actions/workflows/rust.yml)
//! [![docs.rs](https://img.shields.io/docsrs/smtp-proto)](https://docs.rs/smtp-proto)
//! [![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0)
//!
//! _smtp-proto_ is a fast SMTP/LMTP parser for Rust that supports all [registered SMTP service extensions](https://www.iana.org/assignments/mail-parameters/mail-parameters.xhtml).
//! The library is part of Stalwart SMTP and LMTP servers. It is not yet documented so if you need help using the library please start a discussion.
//!
//!
//! ## Testing & Fuzzing
//!
//! To run the testsuite:
//!
//! ```bash
//! $ cargo test
//! ```
//!
//! To fuzz the library with `cargo-fuzz`:
//!
//! ```bash
//! $ cargo +nightly fuzz run smtp_proto
//! ```
//!
//! ## License
//!
//! Licensed under the terms of the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.en.html) as published by
//! the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
//! See [LICENSE](LICENSE) for more details.
//!
//! You can be released from the requirements of the AGPLv3 license by purchasing
//! a commercial license. Please contact licensing@stalw.art for more details.
//!
//! ## Copyright
//!
//! Copyright (C) 2020-2023, Stalwart Labs Ltd.
use std::fmt::Display;
pub mod request;

View file

@ -1,3 +1,26 @@
/*
* Copyright (c) 2020-2023, Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP protocol parser.
*
* 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.
*/
pub mod parser;
pub mod receiver;

View file

@ -1,3 +1,26 @@
/*
* Copyright (c) 2020-2023, Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP protocol parser.
*
* 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 std::slice::Iter;
use crate::*;
@ -7,6 +30,9 @@ use super::*;
#[derive(Default)]
pub struct RequestParser {}
const MAX_ADDRESS_LEN: usize = 256;
const MAX_DOMAIN_LEN: usize = 255;
impl Request<String> {
pub fn parse(bytes: &mut Iter<'_, u8>) -> Result<Request<String>, Error> {
let mut parser = Rfc5321Parser::new(bytes);
@ -70,7 +96,7 @@ impl Request<String> {
if parser.stop_char != LF {
let host = parser.text()?;
parser.seek_lf()?;
if !host.is_empty() {
if (1..=MAX_DOMAIN_LEN).contains(&host.len()) {
return Ok(Request::Ehlo { host });
}
}
@ -136,7 +162,7 @@ impl Request<String> {
if parser.stop_char != LF {
let value = parser.string()?;
parser.seek_lf()?;
if !value.is_empty() {
if (1..=MAX_ADDRESS_LEN).contains(&value.len()) {
return Ok(Request::Expn { value });
}
}
@ -163,7 +189,7 @@ impl Request<String> {
if parser.stop_char != LF {
let host = parser.text()?;
parser.seek_lf()?;
if !host.is_empty() {
if (1..=MAX_DOMAIN_LEN).contains(&host.len()) {
return Ok(Request::Lhlo { host });
}
}
@ -179,7 +205,7 @@ impl Request<String> {
if parser.stop_char != LF {
let value = parser.string()?;
parser.seek_lf()?;
if !value.is_empty() {
if (1..=MAX_ADDRESS_LEN).contains(&value.len()) {
return Ok(Request::Vrfy { value });
}
}
@ -272,7 +298,7 @@ impl Request<String> {
if parser.stop_char != LF {
let host = parser.text()?;
parser.seek_lf()?;
if !host.is_empty() {
if (1..=MAX_DOMAIN_LEN).contains(&host.len()) {
return Ok(Request::Helo { host });
}
}
@ -423,11 +449,13 @@ impl<'x, 'y> Rfc5321Parser<'x, 'y> {
self.stop_char = ch;
let value = value.into_string();
let len = value.chars().count();
return Ok(if len == 0 || len < 255 && at_count == 1 && lp_len > 0 {
return Ok(
if len == 0 || len <= MAX_ADDRESS_LEN && at_count == 1 && lp_len > 0 {
value.into()
} else {
None
});
},
);
}
b'\r' => (),
b':' if !in_quote && matches!(value.first(), Some(b'@')) => {
@ -442,22 +470,26 @@ impl<'x, 'y> Rfc5321Parser<'x, 'y> {
self.stop_char = b' ';
let value = value.into_string();
let len = value.chars().count();
return Ok(if len == 0 || len < 255 && at_count == 1 && lp_len > 0 {
return Ok(
if len == 0 || len <= MAX_ADDRESS_LEN && at_count == 1 && lp_len > 0 {
value.into()
} else {
None
});
},
);
}
}
b'\n' => {
self.stop_char = b'\n';
let value = value.into_string();
let len = value.chars().count();
return Ok(if len == 0 || len < 255 && at_count == 1 && lp_len > 0 {
return Ok(
if len == 0 || len <= MAX_ADDRESS_LEN && at_count == 1 && lp_len > 0 {
value.into()
} else {
None
});
},
);
}
b'\"' if !in_quote || last_ch != b'\\' => {
in_quote = !in_quote;
@ -937,7 +969,9 @@ impl<'x, 'y> Rfc5321Parser<'x, 'y> {
}
AUTH_ if self.stop_char == b'=' => {
let mailbox = self.xtext()?;
if !mailbox.is_empty() && self.stop_char.is_ascii_whitespace() {
if (1..=MAX_ADDRESS_LEN).contains(&mailbox.len())
&& self.stop_char.is_ascii_whitespace()
{
params.auth = mailbox.into();
} else {
self.seek_lf()?;
@ -1039,7 +1073,9 @@ impl<'x, 'y> Rfc5321Parser<'x, 'y> {
return Err(Error::InvalidParameter { param: "ORCPT" });
}
let addr = self.xtext()?;
if self.stop_char.is_ascii_whitespace() && (1..=500).contains(&addr.len()) {
if self.stop_char.is_ascii_whitespace()
&& (1..=MAX_ADDRESS_LEN).contains(&addr.len())
{
params.orcpt = addr.into();
} else {
self.seek_lf()?;
@ -2081,12 +2117,11 @@ mod tests {
let (request, parsed_request): (&str, Result<Request<String>, Error>) = item;
for extra in ["\n", "\r\n", " \n", " \r\n"] {
let request = format!("{}{}", request, extra);
let request = format!("{request}{extra}");
assert_eq!(
parsed_request,
Request::parse(&mut request.as_bytes().iter()),
"failed for {:?}",
request
"failed for {request:?}"
);
}
}

View file

@ -1,3 +1,26 @@
/*
* Copyright (c) 2020-2023, Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP protocol parser.
*
* 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 std::slice::Iter;
use crate::{Error, Request};
@ -247,7 +270,7 @@ mod tests {
continue 'outer;
}
}
panic!("Failed for {:?}", data);
panic!("Failed for {data:?}");
}
}
@ -295,7 +318,7 @@ mod tests {
Err(Error::NeedsMoreData { .. }) => {
break;
}
err => panic!("Unexpected error {:?}", err),
err => panic!("Unexpected error {err:?}"),
}
}
}

View file

@ -1,3 +1,26 @@
/*
* Copyright (c) 2020-2023, Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP protocol parser.
*
* 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 std::{
fmt::Display,
io::{self, Write},
@ -39,7 +62,7 @@ impl<T: Display> EhloResponse<T> {
while mechanisms != 0 {
let item = 1 << (63 - mechanisms.leading_zeros());
mechanisms ^= item;
write!(writer, " {}", (item as u64).to_mechanism())?;
write!(writer, " {}", item.to_mechanism())?;
}
writer.write_all(b"\r\n")
}
@ -79,7 +102,7 @@ impl<T: Display> EhloResponse<T> {
EXT_MTRK => write!(writer, "MTRK\r\n"),
EXT_NO_SOLICITING => {
if let Some(keywords) = &self.no_soliciting {
write!(writer, "NO-SOLICITING {}\r\n", keywords)
write!(writer, "NO-SOLICITING {keywords}\r\n")
} else {
write!(writer, "NO-SOLICITING\r\n")
}

View file

@ -1,3 +1,26 @@
/*
* Copyright (c) 2020-2023, Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP protocol parser.
*
* 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 std::fmt::Display;
use crate::{EhloResponse, Response};

View file

@ -1,3 +1,26 @@
/*
* Copyright (c) 2020-2023, Stalwart Labs Ltd.
*
* This file is part of the Stalwart SMTP protocol parser.
*
* 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 std::slice::Iter;
use crate::{request::parser::Rfc5321Parser, *};
@ -480,8 +503,7 @@ mod tests {
assert_eq!(
parsed_response,
EhloResponse::parse(&mut response.as_bytes().iter()),
"failed for {:?}",
response
"failed for {response:?}",
);
}
}
@ -624,8 +646,7 @@ mod tests {
ResponseReceiver::default()
.parse(&mut response.as_bytes().iter())
.unwrap(),
"failed for {:?}",
response
"failed for {response:?}",
);
all_responses.extend_from_slice(response.as_bytes());
all_parsed_responses.push(parsed_response);
@ -643,15 +664,14 @@ mod tests {
assert_eq!(
parsed_response.next(),
Some(response),
"chunk size {}",
chunk_size
"chunk size {chunk_size}",
);
receiver.reset();
}
Err(Error::NeedsMoreData { .. }) => {
break;
}
err => panic!("Unexpected error {:?} for chunk size {}", err, chunk_size),
err => panic!("Unexpected error {err:?} for chunk size {chunk_size}"),
}
}
}