Compare commits

..

15 Commits

Author SHA1 Message Date
holger krekel
e82d76a112 fix #616 -- allow invalid certs for smtp and imap connections -- this is the behaviour of C-core. 2019-09-27 18:37:08 +02:00
holger krekel
f45ee2ab4d fix #615 -- like with c-core Chat-Version is left in unprotected headers because
it's eg used in server-filters for detecting DC messages
2019-09-27 18:28:47 +02:00
holger krekel
2b73fab913 cargo fmt 2019-09-27 18:28:29 +02:00
holger krekel
e0d750ac64 little cleanup dc_imex 2019-09-27 18:28:29 +02:00
Alexander Krotov
bb57c6e7b7 Merge pull request #627 from deltachat/dc_receive_imf-slice
Pass slice to dc_receive_imf
2019-09-27 16:27:14 +00:00
Alexander Krotov
f346a052c1 Return Result from dc_initiate_key_transfer 2019-09-27 17:57:45 +02:00
Alexander Krotov
3933353b5f Pass slice to dc_receive_imf
instead of pointer and length
2019-09-27 17:53:41 +03:00
Dmitry Bogatov
6c9c21c135 quote_word: avoid dependency on phf crate 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
d02a721eed Reimplement dc_encode_header_words in safe Rust
This change fixes proptest, introduced in [THIS~2] commit.
2019-09-27 04:11:50 +02:00
Dmitry Bogatov
8ffb4ae127 Add proptest seed that reveals strencoding bug 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
96fbeb583b Add proptest to check dc_header_{encode,decode} 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
33b98a15d3 Remove unused "print_hex" function 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
e523ebe3c1 Implement safe version of quote_word 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
e17c671b7c Rename local variables to not misleadingly refer to MMAPString 2019-09-27 04:11:50 +02:00
Dmitry Bogatov
e7565e1a2a Use rust strings instead of MMapString in src/dc_strencode.rs
Since Rust strings operations are assumed to never fail, this commit
removes a lot of checking, whether appending to mmapstring fails. Now it
is Rust runtime burden.
2019-09-27 04:11:50 +02:00
8 changed files with 127 additions and 310 deletions

View File

@@ -1563,7 +1563,13 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) ->
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| dc_imex::dc_initiate_key_transfer(ctx))
.with_inner(|ctx| match dc_imex::dc_initiate_key_transfer(ctx) {
Ok(res) => res.strdup(),
Err(err) => {
error!(ctx, "dc_initiate_key_transfer(): {}", err);
ptr::null_mut()
}
})
.unwrap_or_else(|_| ptr::null_mut())
}

View File

@@ -96,16 +96,7 @@ pub unsafe fn dc_reset_tables(context: &Context, bits: i32) -> i32 {
fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(), Error> {
let data = dc_read_file(context, filename)?;
unsafe {
dc_receive_imf(
context,
data.as_ptr() as *const _,
data.len(),
"import",
0,
0,
)
};
unsafe { dc_receive_imf(context, &data, "import", 0, 0) };
Ok(())
}
@@ -414,18 +405,13 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
============================================="
),
},
"initiate-key-transfer" => {
let setup_code = dc_initiate_key_transfer(context);
if !setup_code.is_null() {
println!(
"Setup code for the transferred setup message: {}",
as_str(setup_code),
);
free(setup_code as *mut libc::c_void);
} else {
bail!("Failed to generate setup code");
};
}
"initiate-key-transfer" => match dc_initiate_key_transfer(context) {
Ok(setup_code) => println!(
"Setup code for the transferred setup message: {}",
setup_code,
),
Err(err) => bail!("Failed to generate setup code: {}", err),
},
"get-setupcodebegin" => {
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
let msg_id: u32 = arg1.parse()?;

View File

@@ -5,3 +5,4 @@
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 679506fe9ac59df773f8cfa800fdab5f0a32fe49d2ab370394000a1aa5bc2a72 # shrinks to buf = "%0A"
cc e34960438edb2426904b44fb4215154e7e2880f2fd1c3183b98bfcc76fec4882 # shrinks to input = " 0"

View File

@@ -75,11 +75,9 @@ pub fn dc_imex_has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Resu
}
}
pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
pub fn dc_initiate_key_transfer(context: &Context) -> Result<String> {
let mut msg: Message;
if !dc_alloc_ongoing(context) {
return std::ptr::null_mut();
}
ensure!(dc_alloc_ongoing(context), "could not allocate ongoing");
let setup_code = dc_create_setup_code(context);
/* this may require a keypair to be created. this may take a second ... */
if !context
@@ -149,7 +147,7 @@ pub unsafe fn dc_initiate_key_transfer(context: &Context) -> *mut libc::c_char {
}
dc_free_ongoing(context);
setup_code.strdup()
Ok(setup_code)
}
/// Renders HTML body of a setup file message.
@@ -554,7 +552,7 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
The macro avoids weird values of 0% or 100% while still working. */
#[allow(non_snake_case)]
unsafe fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
let mut ok_to_continue: bool;
let mut ok_to_continue = true;
let mut success = false;
let mut delete_dest_file: libc::c_int = 0;
@@ -571,54 +569,55 @@ unsafe fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()>
sql::try_execute(context, &context.sql, "VACUUM;").ok();
context.sql.close(context);
let mut closed = true;
info!(
context,
"Backup \"{}\" to \"{}\".",
context.get_dbfile().display(),
dest_path_filename.display(),
);
if dc_copy_file(context, context.get_dbfile(), &dest_path_filename) {
context.sql.open(&context, &context.get_dbfile(), 0);
closed = false;
/* add all files as blobs to the database copy (this does not require the source to be locked, neigher the destination as it is used only here) */
/*for logging only*/
let sql = Sql::new();
if sql.open(context, &dest_path_filename, 0) {
if !sql.table_exists("backup_blobs") {
if sql::execute(
context,
&sql,
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
params![],
)
.is_err()
{
/* error already logged */
ok_to_continue = false;
} else {
ok_to_continue = true;
}
} else {
ok_to_continue = true;
let copied = dc_copy_file(context, context.get_dbfile(), &dest_path_filename);
context.sql.open(&context, &context.get_dbfile(), 0);
if !copied {
let s = dest_path_filename.to_string_lossy().to_string();
bail!(
"could not copy file from {:?} to {:?}",
context.get_dbfile(),
s
);
}
/* add all files as blobs to the database copy (this does not require the source to be locked, neigher the destination as it is used only here) */
/*for logging only*/
let sql = Sql::new();
if sql.open(context, &dest_path_filename, 0) {
if !sql.table_exists("backup_blobs") {
if sql::execute(
context,
&sql,
"CREATE TABLE backup_blobs (id INTEGER PRIMARY KEY, file_name, file_content);",
params![],
)
.is_err()
{
/* error already logged */
ok_to_continue = false;
}
if ok_to_continue {
let mut total_files_cnt = 0;
let dir = context.get_blobdir();
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
}
if ok_to_continue {
let mut total_files_cnt = 0;
let dir = context.get_blobdir();
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
total_files_cnt += dir_handle.filter(|r| r.is_ok()).count();
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
if total_files_cnt > 0 {
// scan directory, pass 2: copy files
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
sql.prepare(
info!(context, "EXPORT: total_files_cnt={}", total_files_cnt);
if total_files_cnt > 0 {
// scan directory, pass 2: copy files
if let Ok(dir_handle) = std::fs::read_dir(&dir) {
sql.prepare(
"INSERT INTO backup_blobs (file_name, file_content) VALUES (?, ?);",
move |mut stmt, _| {
let mut processed_files_cnt = 0;
for entry in dir_handle {
if entry.is_err() {
ok_to_continue = true;
break;
}
let entry = entry.unwrap();
@@ -677,39 +676,34 @@ unsafe fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()>
Ok(())
}
).unwrap();
} else {
error!(
context,
"Backup: Cannot copy from blob-directory \"{}\".",
context.get_blobdir().display(),
);
}
} else {
info!(context, "Backup: No files to copy.",);
ok_to_continue = true;
}
if ok_to_continue {
if sql
.set_config_int(context, "backup_time", now as i32)
.is_ok()
{
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
success = true;
}
error!(
context,
"Backup: Cannot copy from blob-directory \"{}\".",
context.get_blobdir().display(),
);
}
} else {
error!(
context,
"Backup: Cannot get info for blob-directory \"{}\".",
context.get_blobdir().display(),
);
};
}
info!(context, "Backup: No files to copy.",);
}
if ok_to_continue {
if sql
.set_config_int(context, "backup_time", now as i32)
.is_ok()
{
context.call_cb(Event::ImexFileWritten(dest_path_filename.clone()));
success = true;
}
}
} else {
error!(
context,
"Backup: Cannot get info for blob-directory \"{}\".",
context.get_blobdir().display(),
);
};
}
}
if closed {
context.sql.open(&context, &context.get_dbfile(), 0);
}
if 0 != delete_dest_file {
dc_delete_file(context, &dest_path_filename);
}

View File

@@ -39,8 +39,7 @@ enum CreateEvent {
/// Receive a message and add it to the database.
pub unsafe fn dc_receive_imf(
context: &Context,
imf_raw_not_terminated: *const libc::c_char,
imf_raw_bytes: libc::size_t,
imf_raw: &[u8],
server_folder: impl AsRef<str>,
server_uid: u32,
flags: u32,
@@ -61,9 +60,8 @@ pub unsafe fn dc_receive_imf(
// we use mailmime_parse() through dc_mimeparser (both call mailimf_struct_multiple_parse()
// somewhen, I did not found out anything that speaks against this approach yet)
let body = std::slice::from_raw_parts(imf_raw_not_terminated as *const u8, imf_raw_bytes);
let mut mime_parser = MimeParser::new(context);
mime_parser.parse(body);
mime_parser.parse(imf_raw);
if mime_parser.header.is_empty() {
// Error - even adding an empty record won't help as we do not know the message ID
@@ -204,8 +202,7 @@ pub unsafe fn dc_receive_imf(
if let Err(err) = add_parts(
context,
&mut mime_parser,
imf_raw_not_terminated,
imf_raw_bytes,
imf_raw,
incoming,
&mut incoming_origin,
server_folder.as_ref(),
@@ -292,8 +289,7 @@ pub unsafe fn dc_receive_imf(
unsafe fn add_parts(
context: &Context,
mut mime_parser: &mut MimeParser,
imf_raw_not_terminated: *const libc::c_char,
imf_raw_bytes: libc::size_t,
imf_raw: &[u8],
incoming: i32,
incoming_origin: &mut Origin,
server_folder: impl AsRef<str>,
@@ -683,10 +679,7 @@ unsafe fn add_parts(
part.bytes,
*hidden,
if save_mime_headers {
Some(String::from_utf8_lossy(std::slice::from_raw_parts(
imf_raw_not_terminated as *const u8,
imf_raw_bytes,
)))
Some(String::from_utf8_lossy(imf_raw))
} else {
None
},

View File

@@ -1,3 +1,4 @@
use itertools::Itertools;
use std::borrow::Cow;
use std::ffi::CString;
use std::ptr;
@@ -5,7 +6,6 @@ use std::ptr;
use charset::Charset;
use libc::{free, strlen};
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
use mmime::mmapstring::*;
use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
@@ -25,195 +25,49 @@ use crate::dc_tools::*;
* @return Returns the encoded string which must be free()'d when no longed needed.
* On errors, NULL is returned.
*/
pub unsafe fn dc_encode_header_words(to_encode_r: impl AsRef<str>) -> String {
let to_encode =
CString::new(to_encode_r.as_ref().as_bytes()).expect("invalid cstring to_encode");
let mut ok_to_continue = true;
let mut ret_str: *mut libc::c_char = ptr::null_mut();
let mut cur: *const libc::c_char = to_encode.as_ptr();
let mmapstr: *mut MMAPString = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
if mmapstr.is_null() {
ok_to_continue = false;
}
loop {
if !ok_to_continue {
if !mmapstr.is_null() {
mmap_string_free(mmapstr);
}
break;
} else {
if *cur as libc::c_int != '\u{0}' as i32 {
let begin: *const libc::c_char;
let mut end: *const libc::c_char;
let mut do_quote: bool;
let mut quote_words: libc::c_int;
begin = cur;
end = begin;
quote_words = 0i32;
do_quote = true;
while *cur as libc::c_int != '\u{0}' as i32 {
get_word(cur, &mut cur, &mut do_quote);
if !do_quote {
break;
}
quote_words = 1i32;
end = cur;
if *cur as libc::c_int != '\u{0}' as i32 {
cur = cur.offset(1isize)
}
}
if 0 != quote_words {
if !quote_word(
mmapstr,
begin,
end.wrapping_offset_from(begin) as libc::size_t,
) {
ok_to_continue = false;
continue;
}
if *end as libc::c_int == ' ' as i32 || *end as libc::c_int == '\t' as i32 {
if mmap_string_append_c(mmapstr, *end).is_null() {
ok_to_continue = false;
continue;
}
end = end.offset(1isize)
}
if *end as libc::c_int != '\u{0}' as i32 {
if mmap_string_append_len(
mmapstr,
end,
cur.wrapping_offset_from(end) as libc::size_t,
)
.is_null()
{
ok_to_continue = false;
continue;
}
}
} else if mmap_string_append_len(
mmapstr,
begin,
cur.wrapping_offset_from(begin) as libc::size_t,
)
.is_null()
{
ok_to_continue = false;
continue;
}
if !(*cur as libc::c_int == ' ' as i32 || *cur as libc::c_int == '\t' as i32) {
continue;
}
if mmap_string_append_c(mmapstr, *cur).is_null() {
ok_to_continue = false;
continue;
}
cur = cur.offset(1isize);
} else {
ret_str = strdup((*mmapstr).str_0);
ok_to_continue = false;
}
}
pub fn dc_encode_header_words(input: impl AsRef<str>) -> String {
let mut result = String::default();
for (_, group) in &input.as_ref().chars().group_by(|c| c.is_whitespace()) {
let word: String = group.collect();
result.push_str(&quote_word(&word.as_bytes()));
}
let s = to_string(ret_str);
free(ret_str.cast());
s
result
}
unsafe fn quote_word(
mmapstr: *mut MMAPString,
word: *const libc::c_char,
size: libc::size_t,
) -> bool {
let mut cur: *const libc::c_char;
let mut i = 0;
let mut hex: [libc::c_char; 4] = [0; 4];
// let mut col: libc::c_int = 0i32;
if mmap_string_append(mmapstr, b"=?utf-8?Q?\x00".as_ptr().cast()).is_null() {
return false;
}
fn must_encode(byte: u8) -> bool {
static SPECIALS: &[u8] = b",:!\"#$@[\\]^`{|}~=?_";
// col = (*mmapstr).len as libc::c_int;
cur = word;
while i < size {
let mut do_quote_char = false;
match *cur as u8 as char {
',' | ':' | '!' | '"' | '#' | '$' | '@' | '[' | '\\' | ']' | '^' | '`' | '{' | '|'
| '}' | '~' | '=' | '?' | '_' => do_quote_char = true,
_ => {
if *cur as u8 >= 128 {
do_quote_char = true;
}
}
}
if do_quote_char {
print_hex(hex.as_mut_ptr(), cur);
if mmap_string_append(mmapstr, hex.as_mut_ptr()).is_null() {
return false;
}
// col += 3i32
} else {
if *cur as libc::c_int == ' ' as i32 {
if mmap_string_append_c(mmapstr, '_' as i32 as libc::c_char).is_null() {
return false;
}
} else if mmap_string_append_c(mmapstr, *cur).is_null() {
return false;
}
// col += 3i32
}
cur = cur.offset(1isize);
i = i.wrapping_add(1)
}
if mmap_string_append(mmapstr, b"?=\x00" as *const u8 as *const libc::c_char).is_null() {
return false;
}
true
SPECIALS.into_iter().any(|b| *b == byte)
}
unsafe fn get_word(
begin: *const libc::c_char,
pend: *mut *const libc::c_char,
pto_be_quoted: *mut bool,
) {
let mut cur: *const libc::c_char = begin;
while *cur as libc::c_int != ' ' as i32
&& *cur as libc::c_int != '\t' as i32
&& *cur as libc::c_int != '\u{0}' as i32
{
cur = cur.offset(1isize)
fn quote_word(word: &[u8]) -> String {
let mut result = String::default();
let mut encoded = false;
for byte in word {
let byte = *byte;
if byte >= 128 || must_encode(byte) {
result.push_str(&format!("={:2X}", byte));
encoded = true;
} else if byte == b' ' {
result.push('_');
encoded = true;
} else {
result.push(byte as _);
}
}
*pto_be_quoted = to_be_quoted(begin, cur.wrapping_offset_from(begin) as libc::size_t);
*pend = cur;
if encoded {
result = format!("=?utf-8?Q?{}?=", &result);
}
result
}
/* ******************************************************************************
* Encode/decode header words, RFC 2047
******************************************************************************/
/* see comment below */
unsafe fn to_be_quoted(word: *const libc::c_char, size: libc::size_t) -> bool {
let mut cur: *const libc::c_char = word;
let mut i = 0;
while i < size {
match *cur as libc::c_int {
44 | 58 | 33 | 34 | 35 | 36 | 64 | 91 | 92 | 93 | 94 | 96 | 123 | 124 | 125 | 126
| 61 | 63 | 95 => return true,
_ => {
if *cur as libc::c_uchar as libc::c_int >= 128i32 {
return true;
}
}
}
cur = cur.offset(1isize);
i = i.wrapping_add(1)
}
false
}
pub unsafe fn dc_decode_header_words(in_0: *const libc::c_char) -> *mut libc::c_char {
if in_0.is_null() {
return ptr::null_mut();
@@ -319,15 +173,6 @@ pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
String::from_utf8_lossy(to_decode)
}
unsafe fn print_hex(target: *mut libc::c_char, cur: *const libc::c_char) {
assert!(!target.is_null());
assert!(!cur.is_null());
let bytes = std::slice::from_raw_parts(cur as *const _, strlen(cur));
let raw = CString::yolo(format!("={}", &hex::encode_upper(bytes)[..2]));
libc::memcpy(target as *mut _, raw.as_ptr() as *const _, 4);
}
#[cfg(test)]
mod tests {
use super::*;
@@ -429,18 +274,6 @@ mod tests {
assert_eq!(dc_needs_ext_header("a b"), true);
}
#[test]
fn test_print_hex() {
let mut hex: [libc::c_char; 4] = [0; 4];
let cur = b"helloworld" as *const u8 as *const libc::c_char;
unsafe { print_hex(hex.as_mut_ptr(), cur) };
assert_eq!(to_string(hex.as_ptr() as *const _), "=68");
let cur = b":" as *const u8 as *const libc::c_char;
unsafe { print_hex(hex.as_mut_ptr(), cur) };
assert_eq!(to_string(hex.as_ptr() as *const _), "=3A");
}
use proptest::prelude::*;
proptest! {
@@ -456,5 +289,13 @@ mod tests {
// make sure this never panics
let _decoded = dc_decode_ext_header(&buf);
}
#[test]
fn test_dc_header_roundtrip(input: String) {
let encoded = dc_encode_header_words(&input);
let decoded = dc_decode_header_words_safe(&encoded);
assert_eq!(input, decoded);
}
}
}

View File

@@ -110,6 +110,8 @@ impl Client {
) -> imap::error::Result<Self> {
let stream = net::TcpStream::connect(addr)?;
let tls = native_tls::TlsConnector::builder()
// see also: https://github.com/deltachat/deltachat-core-rust/issues/203
.danger_accept_invalid_certs(true)
.danger_accept_invalid_hostnames(true)
.build()
.unwrap();
@@ -953,14 +955,7 @@ impl Imap {
if !is_deleted && msg.body().is_some() {
let body = msg.body().unwrap();
unsafe {
dc_receive_imf(
context,
body.as_ptr() as *const libc::c_char,
body.len(),
folder.as_ref(),
server_uid,
flags as u32,
);
dc_receive_imf(context, &body, folder.as_ref(), server_uid, flags as u32);
}
}
}

View File

@@ -70,8 +70,9 @@ impl Smtp {
let port = lp.send_port as u16;
let tls = native_tls::TlsConnector::builder()
// FIXME: unfortunately this is needed to make things work on macos + testrun.org
// see also: https://github.com/deltachat/deltachat-core-rust/issues/203
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true)
.min_protocol_version(Some(DEFAULT_TLS_PROTOCOLS[0]))
.build()
.unwrap();