Compare commits

..

15 Commits

Author SHA1 Message Date
holger krekel
e0932ba0cc rework set_mdnsent and set_seen 2019-09-30 23:26:21 +02:00
holger krekel
ab2ef1e1e4 shift most mmime functions to wrapmime 2019-09-30 00:52:14 +02:00
holger krekel
18030fa61e remove duplicate code and possibly a leak 2019-09-30 00:52:14 +02:00
holger krekel
064337b5d3 refactor one occassion of determinig transfer encoding 2019-09-30 00:52:14 +02:00
Dmitry Bogatov
a6a6fc48c1 Remove unused outlk_autodiscover_t.redirect field 2019-09-29 23:35:01 +02:00
holger krekel
d72e9bb05b remove dc_get_fine_* method and validate_filename 2019-09-29 22:49:01 +02:00
holger krekel
7a9fdb4acd add a new tested context.new_blob_dir method to simplify writing out blob files 2019-09-29 22:49:01 +02:00
Alexander Krotov
a6d0464735 Merge pull request #643 from deltachat/top_evil_rs-skip-safe
top_evil_rs.py: do not list safe files
2019-09-29 19:07:56 +00:00
Alexander Krotov
52f69cc7dc top_evil_rs.py: do not list safe files 2019-09-29 18:51:48 +03:00
Dmitry Bogatov
0beadde758 Remove _safe suffix from dc_decode_header function
There is no longer unsafe version of this function, so suffix is
useless now.
2019-09-28 23:18:41 +02:00
Dmitry Bogatov
618abd63cf Drop unsafe version of dc_decode_header_words
Replace all call sites with safe version and adjust tests as apporiate.
2019-09-28 23:18:41 +02:00
Friedel Ziegelmayer
34b3ddf63b refactor(e2ee): reduce unsafe spread 2019-09-28 13:20:33 -06:00
holger krekel
ca76cac314 address @flub's review comment from https://github.com/deltachat/deltachat-core-rust/pull/622 2019-09-28 03:01:52 +02:00
B. Petersen
3a16ad89bd make ffi-string-parameter const
typically, nearly all string-parameters in the ffi should be const.
one of the few exceptions is dc_str_unref() that really modifies the given data.
2019-09-28 02:56:27 +02:00
dignifiedquire
fb9369f333 refactor(imex): almost all unsafe gone here 2019-09-28 02:55:42 +02:00
28 changed files with 1406 additions and 1209 deletions

View File

@@ -3485,7 +3485,7 @@ int dc_contact_is_verified (dc_contact_t* contact);
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_domain (char* domain);
dc_provider_t* dc_provider_new_from_domain (const char* domain);
/**
@@ -3499,7 +3499,7 @@ dc_provider_t* dc_provider_new_from_domain (char* domain);
* accessor functions. If no provider info is found, NULL will be
* returned.
*/
dc_provider_t* dc_provider_new_from_email (char* email);
dc_provider_t* dc_provider_new_from_email (const char* email);
/**

View File

@@ -225,8 +225,8 @@ pub unsafe extern "C" fn dc_get_userdata(context: *mut dc_context_t) -> *mut lib
#[no_mangle]
pub unsafe extern "C" fn dc_open(
context: *mut dc_context_t,
dbfile: *mut libc::c_char,
blobdir: *mut libc::c_char,
dbfile: *const libc::c_char,
blobdir: *const libc::c_char,
) -> libc::c_int {
if context.is_null() || dbfile.is_null() {
eprintln!("ignoring careless call to dc_open()");
@@ -298,8 +298,8 @@ pub unsafe extern "C" fn dc_get_blobdir(context: *mut dc_context_t) -> *mut libc
#[no_mangle]
pub unsafe extern "C" fn dc_set_config(
context: *mut dc_context_t,
key: *mut libc::c_char,
value: *mut libc::c_char,
key: *const libc::c_char,
value: *const libc::c_char,
) -> libc::c_int {
if context.is_null() || key.is_null() {
eprintln!("ignoring careless call to dc_set_config()");
@@ -322,7 +322,7 @@ pub unsafe extern "C" fn dc_set_config(
#[no_mangle]
pub unsafe extern "C" fn dc_get_config(
context: *mut dc_context_t,
key: *mut libc::c_char,
key: *const libc::c_char,
) -> *mut libc::c_char {
if context.is_null() || key.is_null() {
eprintln!("ignoring careless call to dc_get_config()");
@@ -369,8 +369,8 @@ fn render_info(
#[no_mangle]
pub unsafe extern "C" fn dc_get_oauth2_url(
context: *mut dc_context_t,
addr: *mut libc::c_char,
redirect: *mut libc::c_char,
addr: *const libc::c_char,
redirect: *const libc::c_char,
) -> *mut libc::c_char {
if context.is_null() {
eprintln!("ignoring careless call to dc_get_oauth2_url()");
@@ -612,7 +612,7 @@ pub unsafe extern "C" fn dc_maybe_network(context: *mut dc_context_t) {
pub unsafe extern "C" fn dc_get_chatlist(
context: *mut dc_context_t,
flags: libc::c_int,
query_str: *mut libc::c_char,
query_str: *const libc::c_char,
query_id: u32,
) -> *mut dc_chatlist_t {
if context.is_null() {
@@ -733,7 +733,7 @@ pub unsafe extern "C" fn dc_send_msg(
pub unsafe extern "C" fn dc_send_text_msg(
context: *mut dc_context_t,
chat_id: u32,
text_to_send: *mut libc::c_char,
text_to_send: *const libc::c_char,
) -> u32 {
if context.is_null() || text_to_send.is_null() {
eprintln!("ignoring careless call to dc_send_text_msg()");
@@ -1017,7 +1017,7 @@ pub unsafe extern "C" fn dc_get_chat_contacts(
pub unsafe extern "C" fn dc_search_msgs(
context: *mut dc_context_t,
chat_id: u32,
query: *mut libc::c_char,
query: *const libc::c_char,
) -> *mut dc_array::dc_array_t {
if context.is_null() || query.is_null() {
eprintln!("ignoring careless call to dc_search_msgs()");
@@ -1054,7 +1054,7 @@ pub unsafe extern "C" fn dc_get_chat(context: *mut dc_context_t, chat_id: u32) -
pub unsafe extern "C" fn dc_create_group_chat(
context: *mut dc_context_t,
verified: libc::c_int,
name: *mut libc::c_char,
name: *const libc::c_char,
) -> u32 {
if context.is_null() || name.is_null() {
eprintln!("ignoring careless call to dc_create_group_chat()");
@@ -1131,7 +1131,7 @@ pub unsafe extern "C" fn dc_remove_contact_from_chat(
pub unsafe extern "C" fn dc_set_chat_name(
context: *mut dc_context_t,
chat_id: u32,
name: *mut libc::c_char,
name: *const libc::c_char,
) -> libc::c_int {
if context.is_null() || chat_id <= constants::DC_CHAT_ID_LAST_SPECIAL as u32 || name.is_null() {
eprintln!("ignoring careless call to dc_set_chat_name()");
@@ -1151,7 +1151,7 @@ pub unsafe extern "C" fn dc_set_chat_name(
pub unsafe extern "C" fn dc_set_chat_profile_image(
context: *mut dc_context_t,
chat_id: u32,
image: *mut libc::c_char,
image: *const libc::c_char,
) -> libc::c_int {
if context.is_null() || chat_id <= constants::DC_CHAT_ID_LAST_SPECIAL as u32 {
eprintln!("ignoring careless call to dc_set_chat_profile_image()");
@@ -1322,7 +1322,7 @@ pub unsafe extern "C" fn dc_get_msg(context: *mut dc_context_t, msg_id: u32) ->
}
#[no_mangle]
pub unsafe extern "C" fn dc_may_be_valid_addr(addr: *mut libc::c_char) -> libc::c_int {
pub unsafe extern "C" fn dc_may_be_valid_addr(addr: *const libc::c_char) -> libc::c_int {
if addr.is_null() {
eprintln!("ignoring careless call to dc_may_be_valid_addr()");
return 0;
@@ -1334,7 +1334,7 @@ pub unsafe extern "C" fn dc_may_be_valid_addr(addr: *mut libc::c_char) -> libc::
#[no_mangle]
pub unsafe extern "C" fn dc_lookup_contact_id_by_addr(
context: *mut dc_context_t,
addr: *mut libc::c_char,
addr: *const libc::c_char,
) -> u32 {
if context.is_null() || addr.is_null() {
eprintln!("ignoring careless call to dc_lookup_contact_id_by_addr()");
@@ -1349,8 +1349,8 @@ pub unsafe extern "C" fn dc_lookup_contact_id_by_addr(
#[no_mangle]
pub unsafe extern "C" fn dc_create_contact(
context: *mut dc_context_t,
name: *mut libc::c_char,
addr: *mut libc::c_char,
name: *const libc::c_char,
addr: *const libc::c_char,
) -> u32 {
if context.is_null() || addr.is_null() {
eprintln!("ignoring careless call to dc_create_contact()");
@@ -1369,7 +1369,7 @@ pub unsafe extern "C" fn dc_create_contact(
#[no_mangle]
pub unsafe extern "C" fn dc_add_address_book(
context: *mut dc_context_t,
addr_book: *mut libc::c_char,
addr_book: *const libc::c_char,
) -> libc::c_int {
if context.is_null() || addr_book.is_null() {
eprintln!("ignoring careless call to dc_add_address_book()");
@@ -1390,7 +1390,7 @@ pub unsafe extern "C" fn dc_add_address_book(
pub unsafe extern "C" fn dc_get_contacts(
context: *mut dc_context_t,
flags: u32,
query: *mut libc::c_char,
query: *const libc::c_char,
) -> *mut dc_array::dc_array_t {
if context.is_null() {
eprintln!("ignoring careless call to dc_get_contacts()");
@@ -1521,23 +1521,31 @@ pub unsafe extern "C" fn dc_get_contact(
pub unsafe extern "C" fn dc_imex(
context: *mut dc_context_t,
what: libc::c_int,
param1: *mut libc::c_char,
_param2: *mut libc::c_char,
param1: *const libc::c_char,
_param2: *const libc::c_char,
) {
if context.is_null() {
eprintln!("ignoring careless call to dc_imex()");
return;
}
let what = match imex::ImexMode::from_i32(what as i32) {
Some(what) => what,
None => {
eprintln!("ignoring invalid argument {} to dc_imex", what);
return;
}
};
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| dc_imex::dc_imex(ctx, what, as_opt_str(param1)))
.with_inner(|ctx| imex::imex(ctx, what, as_opt_str(param1)))
.ok();
}
#[no_mangle]
pub unsafe extern "C" fn dc_imex_has_backup(
context: *mut dc_context_t,
dir: *mut libc::c_char,
dir: *const libc::c_char,
) -> *mut libc::c_char {
if context.is_null() || dir.is_null() {
eprintln!("ignoring careless call to dc_imex_has_backup()");
@@ -1545,7 +1553,7 @@ pub unsafe extern "C" fn dc_imex_has_backup(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match dc_imex::dc_imex_has_backup(ctx, as_str(dir)) {
.with_inner(|ctx| match imex::has_backup(ctx, as_str(dir)) {
Ok(res) => res.strdup(),
Err(err) => {
error!(ctx, "dc_imex_has_backup: {}", err);
@@ -1563,7 +1571,7 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) ->
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| match dc_imex::dc_initiate_key_transfer(ctx) {
.with_inner(|ctx| match imex::initiate_key_transfer(ctx) {
Ok(res) => res.strdup(),
Err(err) => {
error!(ctx, "dc_initiate_key_transfer(): {}", err);
@@ -1577,7 +1585,7 @@ pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) ->
pub unsafe extern "C" fn dc_continue_key_transfer(
context: *mut dc_context_t,
msg_id: u32,
setup_code: *mut libc::c_char,
setup_code: *const libc::c_char,
) -> libc::c_int {
if context.is_null()
|| msg_id <= constants::DC_MSG_ID_LAST_SPECIAL as u32
@@ -1588,15 +1596,15 @@ pub unsafe extern "C" fn dc_continue_key_transfer(
}
let ffi_context = &*context;
ffi_context
.with_inner(|ctx| {
match dc_imex::dc_continue_key_transfer(ctx, msg_id, as_str(setup_code)) {
.with_inner(
|ctx| match imex::continue_key_transfer(ctx, msg_id, as_str(setup_code)) {
Ok(()) => 1,
Err(err) => {
error!(ctx, "dc_continue_key_transfer: {}", err);
0
}
}
})
},
)
.unwrap_or(0)
}
@@ -1615,7 +1623,7 @@ pub unsafe extern "C" fn dc_stop_ongoing_process(context: *mut dc_context_t) {
#[no_mangle]
pub unsafe extern "C" fn dc_check_qr(
context: *mut dc_context_t,
qr: *mut libc::c_char,
qr: *const libc::c_char,
) -> *mut dc_lot_t {
if context.is_null() || qr.is_null() {
eprintln!("ignoring careless call to dc_check_qr()");
@@ -1652,7 +1660,7 @@ pub unsafe extern "C" fn dc_get_securejoin_qr(
#[no_mangle]
pub unsafe extern "C" fn dc_join_securejoin(
context: *mut dc_context_t,
qr: *mut libc::c_char,
qr: *const libc::c_char,
) -> u32 {
if context.is_null() || qr.is_null() {
eprintln!("ignoring careless call to dc_join_securejoin()");
@@ -2569,7 +2577,7 @@ pub unsafe extern "C" fn dc_msg_get_setupcodebegin(msg: *mut dc_msg_t) -> *mut l
}
#[no_mangle]
pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *mut libc::c_char) {
pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *const libc::c_char) {
if msg.is_null() {
eprintln!("ignoring careless call to dc_msg_set_text()");
return;
@@ -2582,8 +2590,8 @@ pub unsafe extern "C" fn dc_msg_set_text(msg: *mut dc_msg_t, text: *mut libc::c_
#[no_mangle]
pub unsafe extern "C" fn dc_msg_set_file(
msg: *mut dc_msg_t,
file: *mut libc::c_char,
filemime: *mut libc::c_char,
file: *const libc::c_char,
filemime: *const libc::c_char,
) {
if msg.is_null() || file.is_null() {
eprintln!("ignoring careless call to dc_msg_set_file()");

View File

@@ -8,10 +8,10 @@ use deltachat::configure::*;
use deltachat::constants::*;
use deltachat::contact::*;
use deltachat::context::*;
use deltachat::dc_imex::*;
use deltachat::dc_receive_imf::*;
use deltachat::dc_tools::*;
use deltachat::error::Error;
use deltachat::imex::*;
use deltachat::job::*;
use deltachat::location;
use deltachat::lot::LotState;
@@ -102,7 +102,7 @@ fn dc_poke_eml_file(context: &Context, filename: impl AsRef<Path>) -> Result<(),
/// Import a file to the database.
/// For testing, import a folder with eml-files, a single eml-file, e-mail plus public key and so on.
/// For normal importing, use dc_imex().
/// For normal importing, use imex().
///
/// @private @memberof Context
/// @param context The context as created by dc_context_new().
@@ -405,7 +405,7 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
============================================="
),
},
"initiate-key-transfer" => match dc_initiate_key_transfer(context) {
"initiate-key-transfer" => match initiate_key_transfer(context) {
Ok(setup_code) => println!(
"Setup code for the transferred setup message: {}",
setup_code,
@@ -432,28 +432,28 @@ pub unsafe fn dc_cmdline(context: &Context, line: &str) -> Result<(), failure::E
!arg1.is_empty() && !arg2.is_empty(),
"Arguments <msg-id> <setup-code> expected"
);
dc_continue_key_transfer(context, arg1.parse()?, &arg2)?;
continue_key_transfer(context, arg1.parse()?, &arg2)?;
}
"has-backup" => {
dc_imex_has_backup(context, blobdir)?;
has_backup(context, blobdir)?;
}
"export-backup" => {
dc_imex(context, 11, Some(blobdir));
imex(context, ImexMode::ExportBackup, Some(blobdir));
}
"import-backup" => {
ensure!(!arg1.is_empty(), "Argument <backup-file> missing.");
dc_imex(context, 12, Some(arg1));
imex(context, ImexMode::ImportBackup, Some(arg1));
}
"export-keys" => {
dc_imex(context, 1, Some(blobdir));
imex(context, ImexMode::ExportSelfKeys, Some(blobdir));
}
"import-keys" => {
dc_imex(context, 2, Some(blobdir));
imex(context, ImexMode::ImportSelfKeys, Some(blobdir));
}
"export-setup" => {
let setup_code = dc_create_setup_code(context);
let setup_code = create_setup_code(context);
let file_name = blobdir.join("autocrypt-setup-message.html");
let file_content = dc_render_setup_file(context, &setup_code)?;
let file_content = render_setup_file(context, &setup_code)?;
std::fs::write(&file_name, file_content)?;
println!(
"Setup message written to: {}\nSetup code: {}",

View File

@@ -123,7 +123,7 @@ unsafe fn display_mime_discrete_type(mut discrete_type: *mut mailmime_discrete_t
_ => {}
};
}
unsafe fn display_mime_data(mut data: *mut mailmime_data) {
pub unsafe fn display_mime_data(mut data: *mut mailmime_data) {
match (*data).dt_type {
0 => {
println!(

View File

@@ -888,6 +888,7 @@ pub unsafe fn mailimf_fields_new(mut fld_list: *mut clist) -> *mut mailimf_field
(*fields).fld_list = fld_list;
return fields;
}
#[no_mangle]
pub unsafe fn mailimf_field_new(
mut fld_type: libc::c_int,
@@ -947,6 +948,20 @@ pub unsafe fn mailimf_field_new(
}
return field;
}
#[no_mangle]
pub unsafe fn mailimf_field_new_subject(fld_subject: *mut mailimf_subject) -> *mut mailimf_field {
let mut field: *mut mailimf_field = 0 as *mut mailimf_field;
field = malloc(::std::mem::size_of::<mailimf_field>() as libc::size_t) as *mut mailimf_field;
if field.is_null() {
return 0 as *mut mailimf_field;
}
(*field).fld_type = MAILIMF_FIELD_SUBJECT as libc::c_int;
(*field).fld_data.fld_subject = fld_subject;
field
}
#[no_mangle]
pub unsafe fn mailimf_orig_date_new(
mut dt_date_time: *mut mailimf_date_time,

View File

@@ -708,6 +708,28 @@ pub unsafe fn mailmime_new(
return mime;
}
pub unsafe fn mailmime_new_simple(
mut mm_type: libc::c_int,
mut mm_mime_fields: *mut mailmime_fields,
mut mm_content_type: *mut mailmime_content,
mut mm_fields: *mut mailimf_fields,
mut mm_msg_mime: *mut Mailmime,
) -> *mut Mailmime {
mailmime_new(
mm_type,
std::ptr::null(),
0,
mm_mime_fields,
mm_content_type,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
mm_fields,
mm_msg_mime,
)
}
pub unsafe fn mailmime_free(mut mime: *mut Mailmime) {
match (*mime).mm_type {
1 => {

View File

@@ -21,7 +21,6 @@ struct outlk_autodiscover_t<'a> {
pub out_smtp_set: libc::c_int,
pub tag_config: libc::c_int,
pub config: [*mut libc::c_char; 6],
pub redirect: *mut libc::c_char,
}
pub unsafe fn outlk_autodiscover(
@@ -38,7 +37,6 @@ pub unsafe fn outlk_autodiscover(
out_smtp_set: 0,
tag_config: 0,
config: [ptr::null_mut(); 6],
redirect: ptr::null_mut(),
};
let ok_to_continue;
let mut i = 0;

View File

@@ -64,15 +64,6 @@ pub const DC_GCL_ADD_SELF: usize = 0x02;
pub(crate) const DC_FP_NO_AUTOCRYPT_HEADER: i32 = 2;
pub(crate) const DC_FP_ADD_AUTOCRYPT_HEADER: i32 = 1;
/// param1 is a directory where the keys are written to
pub const DC_IMEX_EXPORT_SELF_KEYS: i32 = 1;
/// param1 is a directory where the keys are searched in and read from
pub const DC_IMEX_IMPORT_SELF_KEYS: i32 = 2;
/// param1 is a directory where the backup is written to
pub const DC_IMEX_EXPORT_BACKUP: i32 = 11;
/// param1 is the file with the backup to import
pub const DC_IMEX_IMPORT_BACKUP: i32 = 12;
/// virtual chat showing all messages belonging to chats flagged with chats.blocked=2
pub(crate) const DC_CHAT_ID_DEADDROP: u32 = 1;
/// messages that should be deleted get this chat_id; the messages are deleted from the working thread later then. This is also needed as rfc724_mid should be preset as long as the message is not deleted on the server (otherwise it is downloaded again)

View File

@@ -1,5 +1,7 @@
use std::collections::HashMap;
use std::ffi::OsString;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Condvar, Mutex, RwLock};
@@ -8,6 +10,7 @@ use libc::uintptr_t;
use crate::chat::*;
use crate::constants::*;
use crate::contact::*;
use crate::dc_tools::{dc_copy_file, dc_derive_safe_stem_ext};
use crate::error::*;
use crate::events::Event;
use crate::imap::*;
@@ -20,6 +23,7 @@ use crate::message::{self, Message};
use crate::param::Params;
use crate::smtp::*;
use crate::sql::Sql;
use rand::{thread_rng, Rng};
/// Callback function type for [Context]
///
@@ -158,6 +162,57 @@ impl Context {
self.blobdir.as_path()
}
pub fn copy_to_blobdir(&self, orig_filename: impl AsRef<str>) -> Result<String> {
// return a $BLOBDIR/<filename> with the content of orig_filename
// copied into it. The <filename> will be safely derived from
// orig_filename, and will not clash with existing filenames.
let dest = self.new_blob_file(&orig_filename, b"")?;
if dc_copy_file(
&self,
PathBuf::from(orig_filename.as_ref()),
PathBuf::from(&dest),
) {
Ok(dest)
} else {
bail!("could not copy {} to {}", orig_filename.as_ref(), dest);
}
}
pub fn new_blob_file(&self, orig_filename: impl AsRef<str>, data: &[u8]) -> Result<String> {
// return a $BLOBDIR/<FILENAME> string which corresponds to the
// respective file in the blobdir, and which contains the data.
// FILENAME is computed by looking and possibly mangling the
// basename of orig_filename. The resulting filenames are meant
// to be human-readable.
let (stem, ext) = dc_derive_safe_stem_ext(orig_filename.as_ref());
// ext starts with "." or is empty string, so we can always resconstruct
for i in 0..3 {
let candidate_basename = match i {
// first a try to just use the (possibly mangled) original basename
0 => format!("{}{}", stem, ext),
// otherwise extend stem with random numbers
_ => {
let mut rng = thread_rng();
let random_id: u32 = rng.gen();
format!("{}-{}{}", stem, random_id, ext)
}
};
let path = self.get_blobdir().join(&candidate_basename);
if let Ok(mut file) = fs::OpenOptions::new()
.create_new(true)
.write(true)
.open(&path)
{
file.write_all(data)?;
return Ok(format!("$BLOBDIR/{}", candidate_basename));
}
}
bail!("out of luck to create new blob file");
}
pub fn call_cb(&self, event: Event) -> uintptr_t {
(*self.cb)(self, event)
}
@@ -438,6 +493,7 @@ pub fn get_version_str() -> &'static str {
mod tests {
use super::*;
use crate::dc_tools::*;
use crate::test_utils::*;
#[test]
@@ -475,6 +531,51 @@ mod tests {
assert!(res.is_err());
}
#[test]
fn test_new_blob_file() {
let t = dummy_context();
let context = t.ctx;
let x = &context.new_blob_file("hello", b"data").unwrap();
assert!(dc_file_exist(&context, x));
assert!(x.starts_with("$BLOBDIR"));
assert!(dc_read_file(&context, x).unwrap() == b"data");
let y = &context.new_blob_file("hello", b"data").unwrap();
assert!(dc_file_exist(&context, y));
assert!(y.starts_with("$BLOBDIR/hello-"));
let x = &context.new_blob_file("xyz/hello.png", b"data").unwrap();
assert!(dc_file_exist(&context, x));
assert_eq!(x, "$BLOBDIR/hello.png");
let y = &context.new_blob_file("hello\\world.png", b"data").unwrap();
assert!(dc_file_exist(&context, y));
assert_eq!(y, "$BLOBDIR/world.png");
}
#[test]
fn test_new_blob_file_long_names() {
let t = dummy_context();
let context = t.ctx;
let s = "12312312039182039182039812039810293810293810293810293801293801293123123";
let x = &context.new_blob_file(s, b"data").unwrap();
println!("blobfilename '{}'", x);
println!("xxxxfilename '{}'", s);
assert!(x.len() < s.len());
assert!(dc_file_exist(&context, x));
assert!(x.starts_with("$BLOBDIR"));
}
#[test]
fn test_new_blob_file_unicode() {
let t = dummy_context();
let context = t.ctx;
let s = "helloäworld.qwe";
let x = &context.new_blob_file(s, b"data").unwrap();
assert_eq!(x, "$BLOBDIR/hello-world.qwe");
assert_eq!(dc_read_file(&context, x).unwrap(), b"data");
}
#[test]
fn test_sqlite_parent_not_exists() {
let tmp = tempfile::tempdir().unwrap();

View File

@@ -5,14 +5,12 @@ use std::ptr;
use charset::Charset;
use deltachat_derive::{FromSql, ToSql};
use libc::{strcmp, strlen, strncmp};
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailimf::*;
use mmime::mailmime::content::*;
use mmime::mailmime::disposition::*;
use mmime::mailmime::types::*;
use mmime::mailmime::*;
use mmime::mmapstring::*;
use mmime::other::*;
use crate::constants::Viewtype;
@@ -21,11 +19,12 @@ use crate::context::Context;
use crate::dc_simplify::*;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
use crate::e2ee;
use crate::error::Error;
use crate::location;
use crate::param::*;
use crate::stock::StockMessage;
use crate::wrapmime;
#[derive(Debug)]
pub struct MimeParser<'a> {
@@ -38,7 +37,9 @@ pub struct MimeParser<'a> {
pub subject: Option<String>,
pub is_send_by_messenger: bool,
pub decrypting_failed: bool,
pub e2ee_helper: E2eeHelper,
pub encrypted: bool,
pub signatures: HashSet<String>,
pub gossipped_addr: HashSet<String>,
pub is_forwarded: bool,
pub reports: Vec<*mut Mailmime>,
pub is_system_message: SystemMessage,
@@ -92,7 +93,9 @@ impl<'a> MimeParser<'a> {
subject: None,
is_send_by_messenger: false,
decrypting_failed: false,
e2ee_helper: Default::default(),
encrypted: false,
signatures: Default::default(),
gossipped_addr: Default::default(),
is_forwarded: false,
context,
reports: Vec::new(),
@@ -113,14 +116,18 @@ impl<'a> MimeParser<'a> {
);
if r == MAILIMF_NO_ERROR as libc::c_int && !self.mimeroot.is_null() {
self.e2ee_helper.try_decrypt(self.context, self.mimeroot)?;
let (encrypted, signatures, gossipped_addr) =
e2ee::try_decrypt(self.context, self.mimeroot)?;
self.encrypted = encrypted;
self.signatures = signatures;
self.gossipped_addr = gossipped_addr;
self.parse_mime_recursive(self.mimeroot);
if let Some(field) = self.lookup_field("Subject") {
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
let subj = (*(*field).fld_data.fld_subject).sbj_value;
self.subject = as_opt_str(subj).map(dc_decode_header_words_safe);
self.subject = as_opt_str(subj).map(dc_decode_header_words);
}
}
@@ -280,12 +287,12 @@ impl<'a> MimeParser<'a> {
) == MAILIMF_NO_ERROR as libc::c_int
&& !mb_list.is_null()
{
if let Some(dn_to_addr) = mailimf_find_first_addr(mb_list) {
if let Some(dn_to_addr) = wrapmime::mailimf_find_first_addr(mb_list) {
if let Some(from_field) = self.lookup_field("From") {
if (*from_field).fld_type == MAILIMF_FIELD_FROM as libc::c_int
&& !(*from_field).fld_data.fld_from.is_null()
{
let from_addr = mailimf_find_first_addr(
let from_addr = wrapmime::mailimf_find_first_addr(
(*(*from_field).fld_data.fld_from).frm_mb_list,
);
if let Some(from_addr) = from_addr {
@@ -583,7 +590,7 @@ impl<'a> MimeParser<'a> {
return false;
}
let mut decoded_data = match mailmime_transfer_decode(mime) {
let mut decoded_data = match wrapmime::mailmime_transfer_decode(mime) {
Ok(decoded_data) => decoded_data,
Err(_) => {
// Note that it's now always an error - might be no data
@@ -697,7 +704,7 @@ impl<'a> MimeParser<'a> {
// might be a wrongly encoded filename
let s = to_string_lossy((*dsp_param).pa_data.pa_filename);
// this is used only if the parts buffer stays empty
desired_filename = dc_decode_header_words_safe(&s)
desired_filename = dc_decode_header_words(&s)
}
}
}
@@ -769,34 +776,40 @@ impl<'a> MimeParser<'a> {
decoded_data: &[u8],
desired_filename: &str,
) {
/* create a free file name to use */
let path_filename = dc_get_fine_path_filename(self.context, "$BLOBDIR", desired_filename);
/* copy data to file */
if dc_write_file(self.context, &path_filename, decoded_data) {
let mut part = Part::default();
part.typ = msg_type;
part.mimetype = mime_type;
part.bytes = decoded_data.len() as libc::c_int;
part.param.set(Param::File, path_filename.to_string_lossy());
if let Some(raw_mime) = raw_mime {
part.param.set(Param::MimeType, raw_mime);
/* write decoded data to new blob file */
let bpath = match self.context.new_blob_file(desired_filename, decoded_data) {
Ok(path) => path,
Err(err) => {
error!(
self.context,
"Could not add blob for mime part {}, error {}", desired_filename, err
);
return;
}
};
if mime_type == DC_MIMETYPE_IMAGE {
if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
part.param.set_int(Param::Width, width as i32);
part.param.set_int(Param::Height, height as i32);
}
}
self.do_add_single_part(part);
let mut part = Part::default();
part.typ = msg_type;
part.mimetype = mime_type;
part.bytes = decoded_data.len() as libc::c_int;
part.param.set(Param::File, bpath);
if let Some(raw_mime) = raw_mime {
part.param.set(Param::MimeType, raw_mime);
}
if mime_type == DC_MIMETYPE_IMAGE {
if let Ok((width, height)) = dc_get_filemeta(decoded_data) {
part.param.set_int(Param::Width, width as i32);
part.param.set_int(Param::Height, height as i32);
}
}
self.do_add_single_part(part);
}
fn do_add_single_part(&mut self, mut part: Part) {
if self.e2ee_helper.encrypted && self.e2ee_helper.signatures.len() > 0 {
if self.encrypted && self.signatures.len() > 0 {
part.param.set_int(Param::GuranteeE2ee, 1);
} else if self.e2ee_helper.encrypted {
} else if self.encrypted {
part.param.set_int(Param::ErroneousE2ee, 0x2);
}
self.parts.push(part);
@@ -825,7 +838,7 @@ impl<'a> MimeParser<'a> {
let mut fld_from: *const mailimf_from = ptr::null();
/* get From: and check there is exactly one sender */
let fld = mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
let fld = wrapmime::mailimf_find_field(self.header_root, MAILIMF_FIELD_FROM as libc::c_int);
if !(fld.is_null()
|| {
fld_from = (*fld).fld_data.fld_from;
@@ -843,7 +856,7 @@ impl<'a> MimeParser<'a> {
if !mb.is_null() {
let from_addr_norm = addr_normalize(as_str((*mb).mb_addr_spec));
let recipients = mailimf_get_recipients(self.header_root);
let recipients = wrapmime::mailimf_get_recipients(self.header_root);
if recipients.len() == 1 {
if recipients.contains(from_addr_norm) {
sender_equals_recipient = true;
@@ -904,22 +917,6 @@ pub struct Part {
pub param: Params,
}
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
if mb_list.is_null() {
return None;
}
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
let mb = cur as *mut mailimf_mailbox;
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
let addr = unsafe { as_str((*mb).mb_addr_spec) };
return Some(addr_normalize(addr).to_string());
}
}
None
}
unsafe fn hash_header(out: &mut HashMap<String, *mut mailimf_field>, in_0: *const mailimf_fields) {
if in_0.is_null() {
return;
@@ -1141,207 +1138,6 @@ pub unsafe fn mailmime_find_ct_parameter(
ptr::null_mut()
}
pub unsafe fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
ensure!(!mime.is_null(), "invalid inputs");
let mut mime_transfer_encoding = MAILMIME_MECHANISM_BINARY as libc::c_int;
let mime_data = (*mime).mm_data.mm_single;
if !(*mime).mm_mime_fields.is_null() {
for cur in (*(*(*mime).mm_mime_fields).fld_list).into_iter() {
let field = cur as *mut mailmime_field;
if !field.is_null()
&& (*field).fld_type == MAILMIME_FIELD_TRANSFER_ENCODING as libc::c_int
&& !(*field).fld_data.fld_encoding.is_null()
{
mime_transfer_encoding = (*(*field).fld_data.fld_encoding).enc_type;
break;
}
}
}
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
{
let decoded_data = (*mime_data).dt_data.dt_text.dt_data;
let decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
if decoded_data.is_null() || decoded_data_bytes <= 0 {
bail!("No data to decode found");
} else {
let result = std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes);
return Ok(result.to_vec());
}
}
let mut current_index = 0;
let mut transfer_decoding_buffer = ptr::null_mut();
let mut decoded_data_bytes = 0;
let r = mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
);
if r == MAILIMF_NO_ERROR as libc::c_int
&& !transfer_decoding_buffer.is_null()
&& decoded_data_bytes > 0
{
let result =
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
.to_vec();
mmap_string_unref(transfer_decoding_buffer);
return Ok(result);
}
Err(format_err!("Failed to to decode"))
}
pub unsafe fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
/* returned addresses are normalized. */
let mut recipients: HashSet<String> = Default::default();
for cur in (*(*imffields).fld_list).into_iter() {
let fld = cur as *mut mailimf_field;
let fld_to: *mut mailimf_to;
let fld_cc: *mut mailimf_cc;
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
// TODO match on enums /rtn
match (*fld).fld_type {
13 => {
fld_to = (*fld).fld_data.fld_to;
if !fld_to.is_null() {
addr_list = (*fld_to).to_addr_list
}
}
14 => {
fld_cc = (*fld).fld_data.fld_cc;
if !fld_cc.is_null() {
addr_list = (*fld_cc).cc_addr_list
}
}
_ => {}
}
if !addr_list.is_null() {
for cur2 in (*(*addr_list).ad_list).into_iter() {
let adr = cur2 as *mut mailimf_address;
if !adr.is_null() {
if (*adr).ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
mailimf_get_recipients_add_addr(&mut recipients, (*adr).ad_data.ad_mailbox);
} else if (*adr).ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
let group: *mut mailimf_group = (*adr).ad_data.ad_group;
if !group.is_null() && !(*group).grp_mb_list.is_null() {
for cur3 in (*(*(*group).grp_mb_list).mb_list).into_iter() {
mailimf_get_recipients_add_addr(
&mut recipients,
cur3 as *mut mailimf_mailbox,
);
}
}
}
}
}
}
}
recipients
}
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
if !mb.is_null() {
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
recipients.insert(addr_norm.into());
}
}
/*the result is a pointer to mime, must not be freed*/
pub unsafe fn mailimf_find_field(
header: *mut mailimf_fields,
wanted_fld_type: libc::c_int,
) -> *mut mailimf_field {
if header.is_null() || (*header).fld_list.is_null() {
return ptr::null_mut();
}
let mut cur1: *mut clistiter = (*(*header).fld_list).first;
while !cur1.is_null() {
let field: *mut mailimf_field = (if !cur1.is_null() {
(*cur1).data
} else {
ptr::null_mut()
}) as *mut mailimf_field;
if !field.is_null() {
if (*field).fld_type == wanted_fld_type {
return field;
}
}
cur1 = if !cur1.is_null() {
(*cur1).next
} else {
ptr::null_mut()
}
}
ptr::null_mut()
}
/*the result is a pointer to mime, must not be freed*/
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
if mime.is_null() {
return ptr::null_mut();
}
match (*mime).mm_type as _ {
MAILMIME_MULTIPLE => {
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
if !header.is_null() {
return header;
}
}
}
MAILMIME_MESSAGE => return (*mime).mm_data.mm_message.mm_fields,
_ => {}
}
ptr::null_mut()
}
pub unsafe fn mailimf_find_optional_field(
header: *mut mailimf_fields,
wanted_fld_name: *const libc::c_char,
) -> *mut mailimf_optional_field {
if header.is_null() || (*header).fld_list.is_null() {
return ptr::null_mut();
}
for cur_data in (*(*header).fld_list).into_iter() {
let field: *mut mailimf_field = cur_data as *mut _;
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
if !optional_field.is_null()
&& !(*optional_field).fld_name.is_null()
&& !(*optional_field).fld_value.is_null()
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
{
return optional_field;
}
}
}
ptr::null_mut()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1357,14 +1153,13 @@ mod tests {
let mut mime: *mut Mailmime = ptr::null_mut();
let mut dummy = 0;
let res = mailmime_parse(txt, strlen(txt), &mut dummy, &mut mime);
assert_eq!(res, MAIL_NO_ERROR as libc::c_int);
assert!(!mime.is_null());
let fields: *mut mailimf_fields = mailmime_find_mailimf_fields(mime);
let fields: *mut mailimf_fields = wrapmime::mailmime_find_mailimf_fields(mime);
assert!(!fields.is_null());
let mut of_a: *mut mailimf_optional_field = mailimf_find_optional_field(
let mut of_a: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
fields,
b"fielda\x00" as *const u8 as *const libc::c_char,
);
@@ -1384,7 +1179,7 @@ mod tests {
"ValueA",
);
of_a = mailimf_find_optional_field(
of_a = wrapmime::mailimf_find_optional_field(
fields,
b"FIELDA\x00" as *const u8 as *const libc::c_char,
);
@@ -1404,7 +1199,7 @@ mod tests {
"ValueA",
);
let of_b: *mut mailimf_optional_field = mailimf_find_optional_field(
let of_b: *mut mailimf_optional_field = wrapmime::mailimf_find_optional_field(
fields,
b"FieldB\x00" as *const u8 as *const libc::c_char,
);
@@ -1427,7 +1222,9 @@ mod tests {
let context = dummy_context();
let raw = include_bytes!("../test-data/message/issue_523.txt");
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe { mimeparser.parse(&raw[..]).unwrap() };
unsafe {
mimeparser.parse(&raw[..]).unwrap();
};
assert_eq!(mimeparser.subject, None);
assert_eq!(mimeparser.parts.len(), 1);
}
@@ -1435,9 +1232,13 @@ mod tests {
proptest! {
#[test]
fn test_dc_mailmime_parse_crash_fuzzy(data in "[!-~\t ]{2000,}") {
// this test doesn't exercise much of dc_mimeparser anymore
// because a missing From-field early aborts parsing
let context = dummy_context();
let mut mimeparser = MimeParser::new(&context.ctx);
unsafe { mimeparser.parse(data.as_bytes()).unwrap() };
unsafe {
assert!(mimeparser.parse(data.as_bytes()).is_err());
}
}
}
@@ -1466,7 +1267,7 @@ mod tests {
fn test_mimeparser_with_context() {
unsafe {
let context = dummy_context();
let raw = b"Content-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
let raw = b"From: hello\nContent-Type: multipart/mixed; boundary=\"==break==\";\nSubject: outer-subject\nX-Special-A: special-a\nFoo: Bar\nChat-Version: 0.0\n\n--==break==\nContent-Type: text/plain; protected-headers=\"v1\";\nSubject: inner-subject\nX-Special-B: special-b\nFoo: Xy\nChat-Version: 1.0\n\ntest1\n\n--==break==--\n\n\x00";
let mut mimeparser = MimeParser::new(&context.ctx);
mimeparser.parse(&raw[..]).unwrap();

View File

@@ -29,6 +29,7 @@ use crate::peerstate::*;
use crate::securejoin::handle_securejoin_handshake;
use crate::sql;
use crate::stock::StockMessage;
use crate::wrapmime;
#[derive(Debug, PartialEq, Eq)]
enum CreateEvent {
@@ -610,7 +611,7 @@ unsafe fn add_parts(
let icnt = mime_parser.parts.len();
let mut txt_raw = None;
let is_ok = context
context
.sql
.prepare(
"INSERT INTO msgs \
@@ -697,13 +698,10 @@ unsafe fn add_parts(
Ok(())
},
)
.is_ok();
if !is_ok {
// i/o error - there is nothing more we can do - in other cases, we try to write at least an empty record
cleanup(mime_in_reply_to, mime_references);
bail!("Cannot write DB.");
}
.map_err(|err| {
cleanup(mime_in_reply_to, mime_references);
err
})?;
info!(
context,
@@ -798,7 +796,7 @@ unsafe fn handle_reports(
b"disposition-notification\x00" as *const u8 as *const libc::c_char,
) == 0
{
if let Ok(report_body) = mailmime_transfer_decode(report_data) {
if let Ok(report_body) = wrapmime::mailmime_transfer_decode(report_data) {
let mut report_parsed = std::ptr::null_mut();
let mut dummy = 0;
@@ -810,13 +808,14 @@ unsafe fn handle_reports(
) == MAIL_NO_ERROR as libc::c_int
&& !report_parsed.is_null()
{
let report_fields = mailmime_find_mailimf_fields(report_parsed);
let report_fields =
wrapmime::mailmime_find_mailimf_fields(report_parsed);
if !report_fields.is_null() {
let of_disposition = mailimf_find_optional_field(
let of_disposition = wrapmime::mailimf_find_optional_field(
report_fields,
b"Disposition\x00" as *const u8 as *const libc::c_char,
);
let of_org_msgid = mailimf_find_optional_field(
let of_org_msgid = wrapmime::mailimf_find_optional_field(
report_fields,
b"Original-Message-ID\x00" as *const u8 as *const libc::c_char,
);
@@ -1079,7 +1078,7 @@ unsafe fn create_or_lookup_group(
}
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Name") {
grpname = Some(dc_decode_header_words_safe(&optional_field));
grpname = Some(dc_decode_header_words(&optional_field));
}
if let Some(optional_field) = mime_parser.lookup_optional_field("Chat-Group-Member-Removed") {
X_MrRemoveFromGrp = Some(optional_field);
@@ -1609,10 +1608,7 @@ fn check_verified_properties(
) -> Result<()> {
let contact = Contact::load_from_db(context, from_id)?;
ensure!(
mimeparser.e2ee_helper.encrypted,
"This message is not encrypted."
);
ensure!(mimeparser.encrypted, "This message is not encrypted.");
// ensure, the contact is verified
// and the message is signed with a verified key of the sender.
@@ -1633,7 +1629,7 @@ fn check_verified_properties(
if let Some(peerstate) = peerstate {
ensure!(
peerstate.has_verified_key(&mimeparser.e2ee_helper.signatures),
peerstate.has_verified_key(&mimeparser.signatures),
"The message was sent with non-verified encryption."
);
}
@@ -1660,7 +1656,7 @@ fn check_verified_properties(
let mut peerstate = Peerstate::from_addr(context, &context.sql, &to_addr);
// mark gossiped keys (if any) as verified
if mimeparser.e2ee_helper.gossipped_addr.contains(&to_addr) && peerstate.is_some() {
if mimeparser.gossipped_addr.contains(&to_addr) && peerstate.is_some() {
let peerstate = peerstate.as_mut().unwrap();
// if we're here, we know the gossip key is verified:
@@ -1971,7 +1967,7 @@ unsafe fn add_or_lookup_contact_by_addr(
/* add addr_spec if missing, update otherwise */
let mut display_name_dec = "".to_string();
if !display_name_enc.is_null() {
let tmp = as_str(dc_decode_header_words(display_name_enc));
let tmp = dc_decode_header_words(as_str(display_name_enc));
display_name_dec = normalize_name(&tmp);
}
/*can be NULL*/

View File

@@ -4,7 +4,7 @@ use std::ffi::CString;
use std::ptr;
use charset::Charset;
use libc::{free, strlen};
use libc::free;
use mmime::mailmime::decode::mailmime_encoded_phrase_parse;
use mmime::other::*;
use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, CONTROLS};
@@ -68,28 +68,7 @@ fn quote_word(word: &[u8]) -> String {
* Encode/decode header words, RFC 2047
******************************************************************************/
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();
}
let mut out: *mut libc::c_char = ptr::null_mut();
let mut cur_token = 0;
let r: libc::c_int = mailmime_encoded_phrase_parse(
b"iso-8859-1\x00" as *const u8 as *const libc::c_char,
in_0,
strlen(in_0),
&mut cur_token,
b"utf-8\x00" as *const u8 as *const libc::c_char,
&mut out,
);
if r != MAILIMF_NO_ERROR as libc::c_int || out.is_null() {
out = dc_strdup(in_0)
}
out
}
pub fn dc_decode_header_words_safe(input: &str) -> String {
pub(crate) fn dc_decode_header_words(input: &str) -> String {
static FROM_ENCODING: &[u8] = b"iso-8859-1\x00";
static TO_ENCODING: &[u8] = b"utf-8\x00";
let mut out = ptr::null_mut();
@@ -177,60 +156,31 @@ pub fn dc_decode_ext_header(to_decode: &[u8]) -> Cow<str> {
mod tests {
use super::*;
use libc::strcmp;
use std::ffi::CStr;
#[test]
fn test_dc_decode_header_words() {
unsafe {
let mut buf1: *mut libc::c_char = dc_decode_header_words(
b"=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?=\x00" as *const u8 as *const libc::c_char,
);
assert_eq!(
strcmp(
buf1,
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf1 as *mut libc::c_void);
assert_eq!(
dc_decode_header_words("=?utf-8?B?dGVzdMOkw7bDvC50eHQ=?="),
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
buf1 =
dc_decode_header_words(b"just ascii test\x00" as *const u8 as *const libc::c_char);
assert_eq!(CStr::from_ptr(buf1).to_str().unwrap(), "just ascii test");
free(buf1 as *mut libc::c_void);
assert_eq!(dc_decode_header_words("just ascii test"), "just ascii test");
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
assert_eq!(dc_encode_header_words("abcdef"), "abcdef");
let r = dc_encode_header_words(
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec())
.unwrap(),
let r = dc_encode_header_words(
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert!(r.starts_with("=?utf-8"));
assert_eq!(
dc_decode_header_words(&r),
std::string::String::from_utf8(b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt".to_vec()).unwrap(),
);
assert_eq!(
dc_decode_header_words("=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?="),
std::string::String::from_utf8(b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39".to_vec()).unwrap(),
);
assert!(r.starts_with("=?utf-8"));
buf1 = r.strdup();
let buf2: *mut libc::c_char = dc_decode_header_words(buf1);
assert_eq!(
strcmp(
buf2,
b"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\x00" as *const u8 as *const libc::c_char
),
0
);
free(buf2 as *mut libc::c_void);
buf1 = dc_decode_header_words(
b"=?ISO-8859-1?Q?attachment=3B=0D=0A_filename=3D?= =?ISO-8859-1?Q?=22test=E4=F6=FC=2Etxt=22=3B=0D=0A_size=3D39?=\x00" as *const u8 as *const libc::c_char
);
assert_eq!(
strcmp(
buf1,
b"attachment;\r\n filename=\"test\xc3\xa4\xc3\xb6\xc3\xbc.txt\";\r\n size=39\x00" as *const u8 as *const libc::c_char,
),
0
);
}
}
#[test]
@@ -293,7 +243,7 @@ mod tests {
#[test]
fn test_dc_header_roundtrip(input: String) {
let encoded = dc_encode_header_words(&input);
let decoded = dc_decode_header_words_safe(&encoded);
let decoded = dc_decode_header_words(&encoded);
assert_eq!(input, decoded);
}

View File

@@ -1,8 +1,9 @@
//! Some tools and enhancements to the used libraries, there should be
//! no references to Context and other "larger" entities here.
use core::cmp::max;
use std::borrow::Cow;
use std::ffi::{CStr, CString, OsString};
use std::ffi::{CStr, CString};
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::time::SystemTime;
@@ -217,26 +218,26 @@ pub(crate) fn dc_str_to_color(s: impl AsRef<str>) -> u32 {
/* date/time tools */
/* the result is UTC or DC_INVALID_TIMESTAMP */
pub(crate) unsafe fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
let sec = (*date_time).dt_sec;
let min = (*date_time).dt_min;
let hour = (*date_time).dt_hour;
let day = (*date_time).dt_day;
let month = (*date_time).dt_month;
let year = (*date_time).dt_year;
pub(crate) fn dc_timestamp_from_date(date_time: *mut mailimf_date_time) -> i64 {
assert!(!date_time.is_null());
let dt = unsafe { *date_time };
let sec = dt.dt_sec;
let min = dt.dt_min;
let hour = dt.dt_hour;
let day = dt.dt_day;
let month = dt.dt_month;
let year = dt.dt_year;
let ts = chrono::NaiveDateTime::new(
chrono::NaiveDate::from_ymd(year, month as u32, day as u32),
chrono::NaiveTime::from_hms(hour as u32, min as u32, sec as u32),
);
let (zone_hour, zone_min) = if (*date_time).dt_zone >= 0 {
((*date_time).dt_zone / 100, (*date_time).dt_zone % 100)
let (zone_hour, zone_min) = if dt.dt_zone >= 0 {
(dt.dt_zone / 100, dt.dt_zone % 100)
} else {
(
-(-(*date_time).dt_zone / 100),
-(-(*date_time).dt_zone % 100),
)
(-(-dt.dt_zone / 100), -(-dt.dt_zone % 100))
};
ts.timestamp() - (zone_hour * 3600 + zone_min * 60) as i64
@@ -415,12 +416,41 @@ pub(crate) fn dc_ensure_no_slash_safe(path: &str) -> &str {
path
}
/// Function modifies the given buffer and replaces all characters not valid in filenames by a "-".
fn validate_filename(filename: &str) -> String {
filename
.replace('/', "-")
.replace('\\', "-")
.replace(':', "-")
// Function returns a sanitized basename that does not contain
// win/linux path separators and also not any non-ascii chars
fn get_safe_basename(filename: &str) -> String {
// return the (potentially mangled) basename of the input filename
// this might be a path that comes in from another operating system
let mut index: usize = 0;
if let Some(unix_index) = filename.rfind("/") {
index = unix_index + 1;
}
if let Some(win_index) = filename.rfind("\\") {
index = max(index, win_index + 1);
}
if index >= filename.len() {
"nobasename".to_string()
} else {
// we don't allow any non-ascii to be super-safe
filename[index..].replace(|c: char| !c.is_ascii() || c == ':', "-")
}
}
pub fn dc_derive_safe_stem_ext(filename: &str) -> (String, String) {
let basename = get_safe_basename(&filename);
let (mut stem, mut ext) = if let Some(index) = basename.rfind(".") {
(
basename[0..index].to_string(),
basename[index..].to_string(),
)
} else {
(basename, "".to_string())
};
// limit length of stem and ext
stem.truncate(32);
ext.truncate(32);
(stem, ext)
}
// the returned suffix is lower-case
@@ -566,49 +596,24 @@ pub fn dc_read_file<P: AsRef<std::path::Path>>(
}
}
pub(crate) fn dc_get_fine_path_filename(
context: &Context,
pub(crate) fn dc_get_next_backup_path(
folder: impl AsRef<Path>,
desired_filename_suffix: impl AsRef<str>,
) -> PathBuf {
let now = time();
backup_time: i64,
) -> Result<PathBuf, Error> {
let folder = PathBuf::from(folder.as_ref());
// XXX sanitize desired_filename eg using
// https://github.com/kardeiz/sanitize-filename/blob/master/src/lib.rs
let suffix = validate_filename(desired_filename_suffix.as_ref());
let file_name = PathBuf::from(suffix);
let extension = file_name.extension().map(|c| c.clone());
let stem = chrono::NaiveDateTime::from_timestamp(backup_time, 0)
.format("delta-chat-%Y-%m-%d")
.to_string();
for i in 0..100_000 {
let ret = if i == 0 {
let mut folder = folder.clone();
folder.push(&file_name);
folder
} else {
let idx = if i < 100 { i } else { now + i };
let file_name = if let Some(stem) = file_name.file_stem() {
let mut stem = stem.to_os_string();
stem.push(format!("-{}", idx));
stem
} else {
OsString::from(idx.to_string())
};
let mut folder = folder.clone();
folder.push(file_name);
if let Some(ext) = extension {
folder.set_extension(&ext);
}
folder
};
if !dc_file_exist(context, &ret) {
// fine filename found
return ret;
// 64 backup files per day should be enough for everyone
for i in 0..64 {
let mut path = folder.clone();
path.push(format!("{}-{}.bak", stem, i));
if !path.exists() {
return Ok(path);
}
}
panic!("Something is really wrong, you need to clean up your disk");
bail!("could not create backup file, disk full?");
}
pub(crate) fn dc_is_blobdir_path(context: &Context, path: impl AsRef<str>) -> bool {
@@ -636,13 +641,10 @@ pub(crate) fn dc_make_rel_and_copy(context: &Context, path: &mut String) -> bool
dc_make_rel_path(context, path);
return true;
}
let blobdir_path = dc_get_fine_path_filename(context, "$BLOBDIR", &path);
if dc_copy_file(context, &path, &blobdir_path) {
*path = blobdir_path.to_string_lossy().to_string();
dc_make_rel_path(context, path);
if let Ok(blobdir_path) = context.copy_to_blobdir(&path) {
*path = blobdir_path;
return true;
}
false
}
@@ -1437,6 +1439,19 @@ mod tests {
}
}
#[test]
fn test_file_get_safe_basename() {
assert_eq!(get_safe_basename("12312/hello"), "hello");
assert_eq!(get_safe_basename("12312\\hello"), "hello");
assert_eq!(get_safe_basename("//12312\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12/\\\\hello"), "hello");
assert_eq!(get_safe_basename("//123:12//hello"), "hello");
assert_eq!(get_safe_basename("//123:12//"), "nobasename");
assert_eq!(get_safe_basename("//123:12/"), "nobasename");
assert!(get_safe_basename("123\x012.hello").ends_with(".hello"));
}
#[test]
fn test_file_handling() {
let t = dummy_context();
@@ -1471,6 +1486,7 @@ mod tests {
assert!(dc_file_exist(context, &abs_path));
assert!(dc_copy_file(context, "$BLOBDIR/foobar", "$BLOBDIR/dada",));
assert_eq!(dc_get_filebytes(context, "$BLOBDIR/dada",), 7);
let buf = dc_read_file(context, "$BLOBDIR/dada").unwrap();
@@ -1483,14 +1499,12 @@ mod tests {
assert!(dc_create_folder(context, "$BLOBDIR/foobar-folder"));
assert!(dc_file_exist(context, "$BLOBDIR/foobar-folder",));
assert!(!dc_delete_file(context, "$BLOBDIR/foobar-folder"));
let fn0 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn0, PathBuf::from("$BLOBDIR/foobar.dadada"));
let fn0 = "$BLOBDIR/data.data";
assert!(dc_write_file(context, &fn0, b"content"));
let fn1 = dc_get_fine_path_filename(context, "$BLOBDIR", "foobar.dadada");
assert_eq!(fn1, PathBuf::from("$BLOBDIR/foobar-1.dadada"));
assert!(dc_delete_file(context, &fn0));
assert!(!dc_file_exist(context, &fn0));
}
#[test]

View File

@@ -1,11 +1,10 @@
//! End-to-end encryption support.
use std::collections::HashSet;
use std::ffi::CStr;
use std::ptr;
use std::str::FromStr;
use libc::{strcmp, strlen, strncmp};
use libc::strlen;
use mmime::clist::*;
use mmime::mailimf::types::*;
use mmime::mailimf::types_helper::*;
@@ -18,11 +17,11 @@ use mmime::mailmime::*;
use mmime::mailprivacy_prepare_mime;
use mmime::mmapstring::*;
use mmime::{mailmime_substitute, MAILIMF_NO_ERROR, MAIL_NO_ERROR};
use num_traits::FromPrimitive;
use crate::aheader::*;
use crate::config::Config;
use crate::context::Context;
use crate::dc_mimeparser::*;
use crate::dc_tools::*;
use crate::error::*;
use crate::key::*;
@@ -47,12 +46,12 @@ pub struct EncryptHelper {
impl EncryptHelper {
pub fn new(context: &Context) -> Result<EncryptHelper> {
let e2ee = context.sql.get_config_int(&context, "e2ee_enabled");
let prefer_encrypt = if 0 != e2ee.unwrap_or_default() {
EncryptPreference::Mutual
} else {
EncryptPreference::NoPreference
};
let prefer_encrypt = context
.sql
.get_config_int(&context, "e2ee_enabled")
.and_then(EncryptPreference::from_i32)
.unwrap_or_default();
let addr = match context.get_config(Config::ConfiguredAddr) {
None => {
bail!("addr not configured!");
@@ -61,6 +60,7 @@ impl EncryptHelper {
};
let public_key = load_or_generate_self_public_key(context, &addr)?;
Ok(EncryptHelper {
prefer_encrypt,
addr,
@@ -83,11 +83,13 @@ impl EncryptHelper {
mut in_out_message: *mut Mailmime,
imffields_unprotected: *mut mailimf_fields,
) -> Result<bool> {
/* libEtPan's pgp_encrypt_mime() takes the parent as the new root.
We just expect the root as being given to this function. */
if in_out_message.is_null() || unsafe { !(*in_out_message).mm_parent.is_null() } {
bail!("corrupted inputs");
}
// libEtPan's pgp_encrypt_mime() takes the parent as the new root.
// We just expect the root as being given to this function.
ensure!(
!in_out_message.is_null() && unsafe { (*in_out_message).mm_parent.is_null() },
"corrupted inputs"
);
if !(self.prefer_encrypt == EncryptPreference::Mutual || e2ee_guaranteed) {
return Ok(false);
}
@@ -106,13 +108,14 @@ impl EncryptHelper {
None => {
let msg = format!("peerstate for {} missing, cannot encrypt", recipient_addr);
if e2ee_guaranteed {
bail!("{}", msg);
return Err(format_err!("{}", msg));
} else {
info!(context, "{}", msg);
return Ok(false);
}
}
};
if peerstate.prefer_encrypt != EncryptPreference::Mutual && !e2ee_guaranteed {
info!(context, "peerstate for {} is no-encrypt", recipient_addr);
return Ok(false);
@@ -136,30 +139,24 @@ impl EncryptHelper {
let sign_key = {
keyring.add_ref(&self.public_key);
let key = Key::from_self_private(context, self.addr.clone(), &context.sql);
if key.is_none() {
bail!("no own private key found")
}
ensure!(key.is_some(), "no own private key found");
key
};
/* encrypt message */
// encrypt message
unsafe {
mailprivacy_prepare_mime(in_out_message);
let mut part_to_encrypt: *mut Mailmime =
(*in_out_message).mm_data.mm_message.mm_msg_mime;
let mut part_to_encrypt = (*in_out_message).mm_data.mm_message.mm_msg_mime;
(*part_to_encrypt).mm_parent = ptr::null_mut();
let imffields_encrypted: *mut mailimf_fields = mailimf_fields_new_empty();
/* mailmime_new_message_data() calls mailmime_fields_new_with_version() which would add the unwanted MIME-Version:-header */
let message_to_encrypt: *mut Mailmime = mailmime_new(
let imffields_encrypted = mailimf_fields_new_empty();
// mailmime_new_message_data() calls mailmime_fields_new_with_version()
// which would add the unwanted MIME-Version:-header
let message_to_encrypt = mailmime_new_simple(
MAILMIME_MESSAGE as libc::c_int,
ptr::null(),
0 as libc::size_t,
mailmime_fields_new_empty(),
mailmime_get_content_message(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
imffields_encrypted,
part_to_encrypt,
);
@@ -168,12 +165,13 @@ impl EncryptHelper {
wrapmime::new_custom_field(imffields_encrypted, "Autocrypt-Gossip", &header)
}
/* memoryhole headers: move some headers into encrypted part */
// memoryhole headers: move some headers into encrypted part
// XXX note we can't use clist's into_iter() because the loop body also removes items
let mut cur: *mut clistiter = (*(*imffields_unprotected).fld_list).first;
let mut cur = (*(*imffields_unprotected).fld_list).first;
while !cur.is_null() {
let field: *mut mailimf_field = (*cur).data as *mut mailimf_field;
let field = (*cur).data as *mut mailimf_field;
let mut move_to_encrypted = false;
if !field.is_null() {
if (*field).fld_type == MAILIMF_FIELD_SUBJECT as libc::c_int {
move_to_encrypted = true;
@@ -189,6 +187,7 @@ impl EncryptHelper {
}
}
}
if move_to_encrypted {
mailimf_fields_add(imffields_encrypted, field);
cur = clist_delete((*imffields_unprotected).fld_list, cur);
@@ -196,48 +195,24 @@ impl EncryptHelper {
cur = (*cur).next;
}
}
let subject: *mut mailimf_subject = mailimf_subject_new("...".strdup());
mailimf_fields_add(
imffields_unprotected,
mailimf_field_new(
MAILIMF_FIELD_SUBJECT as libc::c_int,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
subject,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
),
);
let subject = mailimf_subject_new("...".strdup());
mailimf_fields_add(imffields_unprotected, mailimf_field_new_subject(subject));
wrapmime::append_ct_param(
(*part_to_encrypt).mm_content_type,
"protected-headers",
"v1",
)?;
let plain: *mut MMAPString =
mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
let mut col: libc::c_int = 0i32;
let plain = mmap_string_new(b"\x00" as *const u8 as *const libc::c_char);
let mut col = 0;
mailmime_write_mem(plain, &mut col, message_to_encrypt);
mailmime_free(message_to_encrypt);
if (*plain).str_0.is_null() || (*plain).len <= 0 {
bail!("could not write/allocate");
}
ensure!(
!(*plain).str_0.is_null() && (*plain).len > 0,
"could not write/allocate"
);
let ctext = dc_pgp_pk_encrypt(
std::slice::from_raw_parts((*plain).str_0 as *const u8, (*plain).len),
@@ -246,165 +221,125 @@ impl EncryptHelper {
);
mmap_string_free(plain);
if let Ok(ctext_v) = ctext {
/* create MIME-structure that will contain the encrypted text */
let mut encrypted_part: *mut Mailmime = new_data_part(
ptr::null_mut(),
0 as libc::size_t,
"multipart/encrypted",
MAILMIME_MECHANISM_BASE64,
)?;
let content: *mut mailmime_content = (*encrypted_part).mm_content_type;
wrapmime::append_ct_param(content, "protocol", "application/pgp-encrypted")?;
let version_mime: *mut Mailmime = new_data_part(
VERSION_CONTENT.as_mut_ptr() as *mut libc::c_void,
strlen(VERSION_CONTENT.as_mut_ptr()),
"application/pgp-encrypted",
MAILMIME_MECHANISM_7BIT,
)?;
mailmime_smart_add_part(encrypted_part, version_mime);
let ctext_v = ctext?;
// we assume that ctext_v is not dropped until the end
// of this if-scope
let ctext_part: *mut Mailmime = new_data_part(
ctext_v.as_ptr() as *mut libc::c_void,
ctext_v.len(),
"application/octet-stream",
MAILMIME_MECHANISM_7BIT,
)?;
mailmime_smart_add_part(encrypted_part, ctext_part);
(*in_out_message).mm_data.mm_message.mm_msg_mime = encrypted_part;
(*encrypted_part).mm_parent = in_out_message;
let gossiped = !&gossip_headers.is_empty();
factory.finalize_mime_message(in_out_message, true, gossiped)?;
Ok(true)
} else {
bail!("encryption failed")
}
// create MIME-structure that will contain the encrypted text
let mut encrypted_part = new_data_part(
ptr::null_mut(),
0 as libc::size_t,
"multipart/encrypted",
MAILMIME_MECHANISM_BASE64,
)?;
let content = (*encrypted_part).mm_content_type;
wrapmime::append_ct_param(content, "protocol", "application/pgp-encrypted")?;
let version_mime = new_data_part(
VERSION_CONTENT.as_mut_ptr() as *mut libc::c_void,
strlen(VERSION_CONTENT.as_mut_ptr()),
"application/pgp-encrypted",
MAILMIME_MECHANISM_7BIT,
)?;
mailmime_smart_add_part(encrypted_part, version_mime);
// we assume that ctext_v is not dropped until the end
// of this if-scope
let ctext_part = new_data_part(
ctext_v.as_ptr() as *mut libc::c_void,
ctext_v.len(),
"application/octet-stream",
MAILMIME_MECHANISM_7BIT,
)?;
mailmime_smart_add_part(encrypted_part, ctext_part);
(*in_out_message).mm_data.mm_message.mm_msg_mime = encrypted_part;
(*encrypted_part).mm_parent = in_out_message;
let gossiped = !&gossip_headers.is_empty();
factory.finalize_mime_message(in_out_message, true, gossiped)?;
Ok(true)
}
}
}
#[derive(Debug, Default)]
pub struct E2eeHelper {
// for decrypting only
pub encrypted: bool,
pub signatures: HashSet<String>,
pub gossipped_addr: HashSet<String>,
}
pub fn try_decrypt(
context: &Context,
in_out_message: *mut Mailmime,
) -> Result<(bool, HashSet<String>, HashSet<String>)> {
// just a pointer into mailmime structure, must not be freed
let imffields = unsafe { mailmime_find_mailimf_fields(in_out_message) };
ensure!(
!in_out_message.is_null() && !imffields.is_null(),
"corrupt invalid mime inputs"
);
impl E2eeHelper {
pub unsafe fn try_decrypt(
&mut self,
context: &Context,
in_out_message: *mut Mailmime,
) -> Result<()> {
/*just a pointer into mailmime structure, must not be freed*/
let imffields: *mut mailimf_fields = mailmime_find_mailimf_fields(in_out_message);
let mut message_time = 0;
let mut from = None;
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut gossip_headers: *mut mailimf_fields = ptr::null_mut();
let from = wrapmime::get_field_from(imffields)?;
let message_time = wrapmime::get_field_date(imffields)?;
// XXX do wrapmime:: helper for the next block
if !(in_out_message.is_null() || imffields.is_null()) {
let mut field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
let mut peerstate = None;
let autocryptheader = Aheader::from_imffields(&from, imffields);
if !field.is_null() && !(*field).fld_data.fld_from.is_null() {
from = mailimf_find_first_addr((*(*field).fld_data.fld_from).frm_mb_list)
}
field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
if !field.is_null() && !(*field).fld_data.fld_orig_date.is_null() {
let orig_date: *mut mailimf_orig_date = (*field).fld_data.fld_orig_date;
if !orig_date.is_null() {
message_time = dc_timestamp_from_date((*orig_date).dt_date_time);
if message_time != 0 && message_time > time() {
message_time = time()
}
}
}
let mut peerstate = None;
let autocryptheader = from
.as_ref()
.and_then(|from| Aheader::from_imffields(from, imffields));
if message_time > 0 {
if let Some(ref from) = from {
peerstate = Peerstate::from_addr(context, &context.sql, from);
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false)?;
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false)?;
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
p.save_to_db(&context.sql, true)?;
peerstate = Some(p);
}
}
}
/* load private key for decryption */
let self_addr = context.get_config(Config::ConfiguredAddr);
if let Some(self_addr) = self_addr {
if private_keyring.load_self_private_for_decrypting(
context,
self_addr,
&context.sql,
) {
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate =
Peerstate::from_addr(&context, &context.sql, &from.unwrap_or_default());
}
if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate)?;
}
if let Some(ref key) = peerstate.gossip_key {
public_keyring_for_validate.add_ref(key);
}
if let Some(ref key) = peerstate.public_key {
public_keyring_for_validate.add_ref(key);
}
}
match decrypt_if_autocrypt_message(
context,
in_out_message,
&private_keyring,
&public_keyring_for_validate,
&mut self.signatures,
&mut gossip_headers,
) {
Ok(res) => {
self.encrypted = res;
}
Err(err) => {
bail!("failed to decrypt: {}", err);
}
}
if !gossip_headers.is_null() {
self.gossipped_addr = update_gossip_peerstates(
context,
message_time,
imffields,
gossip_headers,
)?;
}
}
if message_time > 0 {
peerstate = Peerstate::from_addr(context, &context.sql, &from);
if let Some(ref mut peerstate) = peerstate {
if let Some(ref header) = autocryptheader {
peerstate.apply_header(&header, message_time);
peerstate.save_to_db(&context.sql, false).unwrap();
} else if message_time > peerstate.last_seen_autocrypt
&& !contains_report(in_out_message)
{
peerstate.degrade_encryption(message_time);
peerstate.save_to_db(&context.sql, false).unwrap();
}
} else if let Some(ref header) = autocryptheader {
let p = Peerstate::from_header(context, header, message_time);
p.save_to_db(&context.sql, true).unwrap();
peerstate = Some(p);
}
if !gossip_headers.is_null() {
mailimf_fields_free(gossip_headers);
}
Ok(())
}
/* possibly perform decryption */
let mut private_keyring = Keyring::default();
let mut public_keyring_for_validate = Keyring::default();
let mut encrypted = false;
let mut signatures = HashSet::default();
let mut gossipped_addr = HashSet::default();
let self_addr = context.get_config(Config::ConfiguredAddr);
if let Some(self_addr) = self_addr {
if private_keyring.load_self_private_for_decrypting(context, self_addr, &context.sql) {
if peerstate.as_ref().map(|p| p.last_seen).unwrap_or_else(|| 0) == 0 {
peerstate = Peerstate::from_addr(&context, &context.sql, &from);
}
if let Some(ref peerstate) = peerstate {
if peerstate.degrade_event.is_some() {
handle_degrade_event(context, &peerstate)?;
}
if let Some(ref key) = peerstate.gossip_key {
public_keyring_for_validate.add_ref(key);
}
if let Some(ref key) = peerstate.public_key {
public_keyring_for_validate.add_ref(key);
}
}
let mut gossip_headers = ptr::null_mut();
encrypted = decrypt_if_autocrypt_message(
context,
in_out_message,
&private_keyring,
&public_keyring_for_validate,
&mut signatures,
&mut gossip_headers,
)?;
if !gossip_headers.is_null() {
gossipped_addr =
update_gossip_peerstates(context, message_time, imffields, gossip_headers)?;
unsafe { mailimf_fields_free(gossip_headers) };
}
}
}
Ok((encrypted, signatures, gossipped_addr))
}
fn new_data_part(
@@ -414,31 +349,34 @@ fn new_data_part(
default_encoding: u32,
) -> Result<*mut Mailmime> {
let content = new_content_type(&content_type)?;
unsafe {
let mut encoding: *mut mailmime_mechanism = ptr::null_mut();
if wrapmime::content_type_needs_encoding(content) {
encoding = mailmime_mechanism_new(default_encoding as i32, ptr::null_mut());
ensure!(!encoding.is_null(), "failed to create encoding");
}
let mime_fields = mailmime_fields_new_with_data(
encoding,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
);
ensure!(!mime_fields.is_null(), "internal mime error");
let mime = mailmime_new_empty(content, mime_fields);
ensure!(!mime.is_null(), "internal mime error");
if (*mime).mm_type == MAILMIME_SINGLE as libc::c_int {
if !data.is_null() && data_bytes > 0 {
mailmime_set_body_text(mime, data as *mut libc::c_char, data_bytes);
}
}
return Ok(mime);
let mut encoding = ptr::null_mut();
if wrapmime::content_type_needs_encoding(content) {
encoding = unsafe { mailmime_mechanism_new(default_encoding as i32, ptr::null_mut()) };
ensure!(!encoding.is_null(), "failed to create encoding");
}
let mime_fields = {
unsafe {
mailmime_fields_new_with_data(
encoding,
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
ptr::null_mut(),
)
}
};
ensure!(!mime_fields.is_null(), "internal mime error");
let mime = unsafe { mailmime_new_empty(content, mime_fields) };
ensure!(!mime.is_null(), "internal mime error");
if unsafe { (*mime).mm_type } == MAILMIME_SINGLE as libc::c_int {
if !data.is_null() && data_bytes > 0 {
unsafe { mailmime_set_body_text(mime, data as *mut libc::c_char, data_bytes) };
}
}
Ok(mime)
}
/// Load public key from database or generate a new one.
@@ -470,7 +408,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
&public_key,
&private_key,
&self_addr,
1,
true,
&context.sql,
) {
true => {
@@ -488,7 +426,7 @@ fn load_or_generate_self_public_key(context: &Context, self_addr: impl AsRef<str
}
}
unsafe fn update_gossip_peerstates(
fn update_gossip_peerstates(
context: &Context,
message_time: i64,
imffields: *mut mailimf_fields,
@@ -498,21 +436,27 @@ unsafe fn update_gossip_peerstates(
let mut recipients: Option<HashSet<String>> = None;
let mut gossipped_addr: HashSet<String> = Default::default();
for cur_data in (*(*gossip_headers).fld_list).into_iter() {
let field: *mut mailimf_field = cur_data as *mut _;
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field = (*field).fld_data.fld_optional_field;
if !optional_field.is_null()
&& !(*optional_field).fld_name.is_null()
&& strcasecmp(
(*optional_field).fld_name,
b"Autocrypt-Gossip\x00" as *const u8 as *const libc::c_char,
) == 0i32
for cur_data in unsafe { (*(*gossip_headers).fld_list).into_iter() } {
let field = cur_data as *mut mailimf_field;
if field.is_null() {
continue;
}
let field = unsafe { *field };
if field.fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field = unsafe { field.fld_data.fld_optional_field };
if optional_field.is_null() {
continue;
}
let optional_field = unsafe { *optional_field };
if !optional_field.fld_name.is_null()
&& as_str(optional_field.fld_name) == "Autocrypt-Gossip"
{
let value = CStr::from_ptr((*optional_field).fld_value)
.to_str()
.unwrap();
let gossip_header = Aheader::from_str(value);
let value = to_string_lossy(optional_field.fld_value);
let gossip_header = Aheader::from_str(&value);
if let Ok(ref header) = gossip_header {
if recipients.is_none() {
recipients = Some(mailimf_get_recipients(imffields));
@@ -582,16 +526,15 @@ fn decrypt_if_autocrypt_message(
public_keyring_for_validate,
ret_valid_signatures,
)?;
/* decrypted_mime is a dangling pointer which we now put into
mailmime's Ownership */
// decrypted_mime is a dangling pointer which we now put into mailmime's Ownership
unsafe {
mailmime_substitute(mime, decrypted_mime);
mailmime_free(mime);
}
/* finally, let's also return gossip headers
XXX better return parsed headers so that upstream
does not need to dive into mmime-stuff again. */
// finally, let's also return gossip headers
// XXX better return parsed headers so that upstream
// does not need to dive into mmime-stuff again.
unsafe {
if (*ret_gossip_headers).is_null() && ret_valid_signatures.len() > 0 {
let mut dummy: libc::size_t = 0;
@@ -604,11 +547,12 @@ fn decrypt_if_autocrypt_message(
) == MAILIMF_NO_ERROR as libc::c_int
&& !test.is_null()
{
*ret_gossip_headers = test
*ret_gossip_headers = test;
}
}
}
return Ok(true);
Ok(true)
}
fn decrypt_part(
@@ -632,76 +576,67 @@ fn decrypt_part(
mime_transfer_encoding = enc;
}
let (decoded_data, decoded_data_bytes) =
wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
/* encrypted, non-NULL decoded data in decoded_data now ...
Note that we need to take care of freeing decoded_data ourself,
after encryption has been attempted.
*/
let data: Vec<u8> = wrapmime::decode_dt_data(mime_data, mime_transfer_encoding)?;
let mut ret_decrypted_mime = ptr::null_mut();
unsafe {
if has_decrypted_pgp_armor(decoded_data, decoded_data_bytes as libc::c_int) {
/* we should only have one decryption happening */
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
if has_decrypted_pgp_armor(&data) {
// we should only have one decryption happening
ensure!(ret_valid_signatures.is_empty(), "corrupt signatures");
let plain = match dc_pgp_pk_decrypt(
std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes),
&private_keyring,
&public_keyring_for_validate,
Some(ret_valid_signatures),
) {
Ok(plain) => {
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
plain
}
Err(err) => {
mmap_string_unref(decoded_data);
bail!("could not decrypt: {}", err)
}
};
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
let plain = match dc_pgp_pk_decrypt(
&data,
&private_keyring,
&public_keyring_for_validate,
Some(ret_valid_signatures),
) {
Ok(plain) => {
ensure!(!ret_valid_signatures.is_empty(), "no valid signatures");
plain
}
Err(err) => bail!("could not decrypt: {}", err),
};
let plain_bytes = plain.len();
let plain_buf = plain.as_ptr() as *const libc::c_char;
let mut index: libc::size_t = 0;
let mut decrypted_mime: *mut Mailmime = ptr::null_mut();
if mailmime_parse(
let mut index = 0;
let mut decrypted_mime = ptr::null_mut();
if unsafe {
mailmime_parse(
plain_buf as *const _,
plain_bytes,
&mut index,
&mut decrypted_mime,
) != MAIL_NO_ERROR as libc::c_int
|| decrypted_mime.is_null()
{
if !decrypted_mime.is_null() {
mailmime_free(decrypted_mime);
}
} else {
ret_decrypted_mime = decrypted_mime;
)
} != MAIL_NO_ERROR as libc::c_int
|| decrypted_mime.is_null()
{
if !decrypted_mime.is_null() {
unsafe { mailmime_free(decrypted_mime) };
}
} else {
// decrypted_mime points into `plain`.
// FIXME(@dignifiedquire): this still leaks memory I believe, as mailmime_free
// does not free the underlying buffer. But for now we have to live with it
std::mem::forget(plain);
ret_decrypted_mime = decrypted_mime;
}
mmap_string_unref(decoded_data);
}
Ok(ret_decrypted_mime)
}
unsafe fn has_decrypted_pgp_armor(str__: *const libc::c_char, mut str_bytes: libc::c_int) -> bool {
let str_end: *const libc::c_uchar = (str__ as *const libc::c_uchar).offset(str_bytes as isize);
let mut p: *const libc::c_uchar = str__ as *const libc::c_uchar;
while p < str_end {
if *p as libc::c_int > ' ' as i32 {
break;
fn has_decrypted_pgp_armor(input: &[u8]) -> bool {
if let Some(index) = input.iter().position(|b| *b > b' ') {
if input.len() - index > 26 {
let start = index;
let end = start + 27;
return &input[start..end] == b"-----BEGIN PGP MESSAGE-----";
}
p = p.offset(1isize);
str_bytes -= 1
}
str_bytes > 27i32
&& strncmp(
p as *const libc::c_char,
b"-----BEGIN PGP MESSAGE-----\x00" as *const u8 as *const libc::c_char,
27,
) == 0
false
}
/// Check if a MIME structure contains a multipart/report part.
@@ -712,29 +647,31 @@ unsafe fn has_decrypted_pgp_armor(str__: *const libc::c_char, mut str_bytes: lib
/// However, Delta Chat itself has no problem with encrypted multipart/report
/// parts and MUAs should be encouraged to encrpyt multipart/reports as well so
/// that we could use the normal Autocrypt processing.
unsafe fn contains_report(mime: *mut Mailmime) -> bool {
if (*mime).mm_type == MAILMIME_MULTIPLE as libc::c_int {
if (*(*(*mime).mm_content_type).ct_type).tp_type
== MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int
&& (*(*(*(*mime).mm_content_type).ct_type)
.tp_data
.tp_composite_type)
.ct_type
== MAILMIME_COMPOSITE_TYPE_MULTIPART as libc::c_int
&& strcmp(
(*(*mime).mm_content_type).ct_subtype,
b"report\x00" as *const u8 as *const libc::c_char,
) == 0i32
fn contains_report(mime: *mut Mailmime) -> bool {
assert!(!mime.is_null());
let mime = unsafe { *mime };
if mime.mm_type == MAILMIME_MULTIPLE as libc::c_int {
let tp_type = unsafe { (*(*mime.mm_content_type).ct_type).tp_type };
let ct_type =
unsafe { (*(*(*mime.mm_content_type).ct_type).tp_data.tp_composite_type).ct_type };
if tp_type == MAILMIME_TYPE_COMPOSITE_TYPE as libc::c_int
&& ct_type == MAILMIME_COMPOSITE_TYPE_MULTIPART as libc::c_int
&& as_str(unsafe { (*mime.mm_content_type).ct_subtype }) == "report"
{
return true;
}
for cur_data in (*(*(*mime).mm_mime_fields).fld_list).into_iter() {
for cur_data in unsafe { (*(*mime.mm_mime_fields).fld_list).into_iter() } {
if contains_report(cur_data as *mut Mailmime) {
return true;
}
}
} else if (*mime).mm_type == MAILMIME_MESSAGE as libc::c_int {
if contains_report((*mime).mm_data.mm_message.mm_msg_mime) {
} else if mime.mm_type == MAILMIME_MESSAGE as libc::c_int {
let m = unsafe { mime.mm_data.mm_message.mm_msg_mime };
if contains_report(m) {
return true;
}
}
@@ -869,4 +806,22 @@ Sent with my Delta Chat Messenger: https://delta.chat";
assert_eq!(res0.unwrap(), res1.unwrap());
}
}
#[test]
fn test_has_decrypted_pgp_armor() {
let data = b" -----BEGIN PGP MESSAGE-----";
assert_eq!(has_decrypted_pgp_armor(data), true);
let data = b" \n-----BEGIN PGP MESSAGE-----";
assert_eq!(has_decrypted_pgp_armor(data), true);
let data = b" -----BEGIN PGP MESSAGE---";
assert_eq!(has_decrypted_pgp_armor(data), false);
let data = b" -----BEGIN PGP MESSAGE-----";
assert_eq!(has_decrypted_pgp_armor(data), true);
let data = b"blas";
assert_eq!(has_decrypted_pgp_armor(data), false);
}
}

View File

@@ -26,6 +26,10 @@ pub enum Error {
CStringError(crate::dc_tools::CStringError),
#[fail(display = "PGP: {:?}", _0)]
Pgp(pgp::errors::Error),
#[fail(display = "Base64Decode: {:?}", _0)]
Base64Decode(base64::DecodeError),
#[fail(display = "{:?}", _0)]
FromUtf8(std::string::FromUtf8Error),
}
pub type Result<T> = std::result::Result<T, Error>;
@@ -36,6 +40,12 @@ impl From<rusqlite::Error> for Error {
}
}
impl From<base64::DecodeError> for Error {
fn from(err: base64::DecodeError) -> Error {
Error::Base64Decode(err)
}
}
impl From<failure::Error> for Error {
fn from(err: failure::Error) -> Error {
Error::Failure(err)
@@ -60,6 +70,12 @@ impl From<std::str::Utf8Error> for Error {
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(err: std::string::FromUtf8Error) -> Error {
Error::FromUtf8(err)
}
}
impl From<image_meta::ImageError> for Error {
fn from(err: image_meta::ImageError) -> Error {
Error::Image(err)

View File

@@ -167,7 +167,7 @@ pub enum Event {
#[strum(props(id = "2041"))]
ConfigureProgress(usize),
/// Inform about the import/export progress started by dc_imex().
/// Inform about the import/export progress started by imex().
///
/// @param data1 (usize) 0=error, 1-999=progress in permille, 1000=success and done
/// @param data2 0
@@ -175,8 +175,8 @@ pub enum Event {
#[strum(props(id = "2051"))]
ImexProgress(usize),
/// A file has been exported. A file has been written by dc_imex().
/// This event may be sent multiple times by a single call to dc_imex().
/// A file has been exported. A file has been written by imex().
/// This event may be sent multiple times by a single call to imex().
///
/// A typical purpose for a handler of this event may be to make the file public to some system
/// services.

View File

@@ -1197,8 +1197,8 @@ impl Imap {
};
if copied {
if self.add_flag(context, uid, "\\Deleted") == 0 {
warn!(context, "Cannot mark message as \"Deleted\".",);
if !self.add_flag_finalized(context, uid, "\\Deleted") {
warn!(context, "Giving up: cannot mark {} as \"Deleted\".", uid);
}
self.config.write().unwrap().selected_folder_needs_expunge = true;
res = ImapResult::Success;
@@ -1223,13 +1223,21 @@ impl Imap {
}
}
fn add_flag<S: AsRef<str>>(&self, context: &Context, server_uid: u32, flag: S) -> usize {
fn add_flag_finalized(&self, context: &Context, server_uid: u32, flag: &str) -> bool {
// return true if we successfully set the flag or we otherwise
// think add_flag should not be retried: Disconnection during setting
// the flag, or other imap-errors, returns true as well.
//
// returning false means that the operation can be retried.
if server_uid == 0 {
return 0;
return true; // might be moved but we don't want to have a stuck job
}
if self.should_reconnect() {
return false;
}
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
let set = format!("{}", server_uid);
let query = format!("+FLAGS ({})", flag.as_ref());
let query = format!("+FLAGS ({})", flag);
match session.uid_store(&set, &query) {
Ok(_) => {}
Err(err) => {
@@ -1239,161 +1247,126 @@ impl Imap {
);
}
}
}
// All non-connection states are treated as success - the mail may
// already be deleted or moved away on the server.
if self.should_reconnect() {
0
return true; // we tried once, that's probably enough for setting flag
} else {
1
unreachable!();
}
}
pub fn set_seen<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
let mut res = ImapResult::RetryLater;
pub fn prepare_imap_operation_on_msg(
&self,
context: &Context,
folder: &str,
uid: u32,
) -> Option<ImapResult> {
if uid == 0 {
res = ImapResult::Failed
} else if self.is_connected() {
info!(
Some(ImapResult::Failed)
} else if !self.is_connected() {
Some(ImapResult::RetryLater)
} else if self.select_folder(context, Some(&folder)) == 0 {
warn!(
context,
"Marking message {}/{} as seen...",
folder.as_ref(),
uid,
"Cannot select folder {} for preparing IMAP operation", folder
);
if self.select_folder(context, Some(folder.as_ref())) == 0 {
warn!(
context,
"Cannot select folder {} for setting SEEN flag.",
folder.as_ref(),
);
} else if self.add_flag(context, uid, "\\Seen") == 0 {
warn!(context, "Cannot mark message as seen.",);
} else {
res = ImapResult::Success
}
}
if res == ImapResult::RetryLater {
if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
}
Some(ImapResult::RetryLater)
} else {
res
None
}
}
pub fn set_mdnsent<S: AsRef<str>>(&self, context: &Context, folder: S, uid: u32) -> ImapResult {
// returns 0=job should be retried later, 1=job done, 2=job done and flag just set
let mut res = ImapResult::RetryLater;
pub fn set_seen(&self, context: &Context, folder: &str, uid: u32) -> ImapResult {
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
return imapresult;
}
// we are connected, and the folder is selected
info!(context, "Marking message {}/{} as seen...", folder, uid,);
if self.add_flag_finalized(context, uid, "\\Seen") {
ImapResult::Success
} else {
warn!(
context,
"Cannot mark message {} in folder {} as seen, ignoring.", uid, folder
);
ImapResult::Failed
}
}
pub fn set_mdnsent(&self, context: &Context, folder: &str, uid: u32) -> ImapResult {
if let Some(imapresult) = self.prepare_imap_operation_on_msg(context, folder, uid) {
return imapresult;
}
// we are connected, and the folder is selected
let set = format!("{}", uid);
info!(context, "Marking message {}/{} as $MDNSent...", folder, uid,);
if uid == 0 {
res = ImapResult::Failed;
} else if self.is_connected() {
info!(
context,
"Marking message {}/{} as $MDNSent...",
folder.as_ref(),
uid,
);
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
// set: set the flags and return this information.
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
if self.select_folder(context, Some(folder.as_ref())) == 0 {
let can_create_flag = self
.config
.read()
.unwrap()
.selected_mailbox
.as_ref()
.map(|mbox| {
// empty means, everything can be stored
mbox.permanent_flags.is_empty()
|| mbox
.permanent_flags
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
});
match can_create_flag {
None | Some(false) => {
warn!(
context,
"Cannot select folder {} for setting $MDNSent flag.",
folder.as_ref()
"can't store $MDNSent flags in folder {}, ignoring.", folder
);
} else {
// Check if the folder can handle the `$MDNSent` flag (see RFC 3503). If so, and not
// set: set the flags and return this information.
// If the folder cannot handle the `$MDNSent` flag, we risk duplicated MDNs; it's up
// to the receiving MUA to handle this then (eg. Delta Chat has no problem with this).
// return ImapResult::Failed;
}
Some(true) => {}
}
let can_create_flag = self
.config
.read()
.unwrap()
.selected_mailbox
.as_ref()
.map(|mbox| {
// empty means, everything can be stored
mbox.permanent_flags.is_empty()
|| mbox
.permanent_flags
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.expect("just selected folder");
if can_create_flag {
let fetched_msgs =
if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(set, FETCH_FLAGS) {
Ok(res) => Some(res),
Err(err) => {
eprintln!("fetch error: {:?}", err);
None
}
}
} else {
unreachable!();
};
if let Some(msgs) = fetched_msgs {
let flag_set = msgs
.first()
.map(|msg| {
msg.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.unwrap_or_else(|| false);
res = if flag_set {
ImapResult::AlreadyDone
} else if self.add_flag(context, uid, "$MDNSent") != 0 {
ImapResult::Success
} else {
res
};
if res == ImapResult::Success {
info!(context, "$MDNSent just set and MDN will be sent.");
} else {
info!(context, "$MDNSent already set and MDN already sent.");
}
}
} else {
res = ImapResult::Success;
info!(
context,
"Cannot store $MDNSent flags, risk sending duplicate MDN.",
);
let msgs = if let Some(ref mut session) = &mut *self.session.lock().unwrap() {
match session.uid_fetch(&set, FETCH_FLAGS) {
Ok(res) => res,
Err(err) => {
warn!(context, "IMAP uid_fetch {:?} error: {}", set, err);
return ImapResult::Failed;
}
}
}
if res == ImapResult::RetryLater {
if self.should_reconnect() {
ImapResult::RetryLater
} else {
ImapResult::Failed
}
} else {
res
unreachable!();
};
let flag_set = msgs
.first()
.map(|msg| {
msg.flags()
.iter()
.find(|flag| match flag {
imap::types::Flag::Custom(s) => s == "$MDNSent",
_ => false,
})
.is_some()
})
.unwrap_or_else(|| false);
if flag_set {
info!(context, "$MDNSent already set and MDN already sent.");
ImapResult::AlreadyDone
} else if self.add_flag_finalized(context, uid, "$MDNSent") {
info!(context, "$MDNSent just set and MDN will be sent.");
ImapResult::Success
} else {
info!(context, "$MDNSent could not be set, ignoring");
ImapResult::Failed
}
}
@@ -1463,7 +1436,7 @@ impl Imap {
}
// mark the message for deletion
if self.add_flag(context, *server_uid, "\\Deleted") == 0 {
if !self.add_flag_finalized(context, *server_uid, "\\Deleted") {
warn!(context, "Cannot mark message as \"Deleted\".");
} else {
self.config.write().unwrap().selected_folder_needs_expunge = true;

View File

@@ -2,10 +2,7 @@ use std::ffi::CString;
use std::path::Path;
use std::ptr;
use libc::{free, strlen};
use mmime::mailmime::content::*;
use mmime::mmapstring::*;
use mmime::other::*;
use num_traits::FromPrimitive;
use rand::{thread_rng, Rng};
use crate::chat;
@@ -25,12 +22,48 @@ use crate::pgp::*;
use crate::sql::{self, Sql};
use crate::stock::StockMessage;
// import/export and tools
// param1 is a directory where the keys are written to
// param1 is a directory where the keys are searched in and read from
// param1 is a directory where the backup is written to
// param1 is the file with the backup to import
pub fn dc_imex(context: &Context, what: libc::c_int, param1: Option<impl AsRef<Path>>) {
#[derive(Debug, Display, Copy, Clone, PartialEq, Eq, FromPrimitive, ToPrimitive)]
#[repr(i32)]
pub enum ImexMode {
/// Export all private keys and all public keys of the user to the
/// directory given as `param1`. The default key is written to the files `public-key-default.asc`
/// and `private-key-default.asc`, if there are more keys, they are written to files as
/// `public-key-<id>.asc` and `private-key-<id>.asc`
ExportSelfKeys = 1,
/// Import private keys found in the directory given as `param1`.
/// The last imported key is made the default keys unless its name contains the string `legacy`.
/// Public keys are not imported.
ImportSelfKeys = 2,
/// Export a backup to the directory given as `param1`.
/// The backup contains all contacts, chats, images and other data and device independent settings.
/// The backup does not contain device dependent settings as ringtones or LED notification settings.
/// The name of the backup is typically `delta-chat.<day>.bak`, if more than one backup is create on a day,
/// the format is `delta-chat.<day>-<number>.bak`
ExportBackup = 11,
/// `param1` is the file (not: directory) to import. The file is normally
/// created by DC_IMEX_EXPORT_BACKUP and detected by dc_imex_has_backup(). Importing a backup
/// is only possible as long as the context is not configured or used in another way.
ImportBackup = 12,
}
/// Import/export things.
/// For this purpose, the function creates a job that is executed in the IMAP-thread then;
/// this requires to call dc_perform_imap_jobs() regularly.
///
/// What to do is defined by the _what_ parameter.
///
/// While dc_imex() returns immediately, the started job may take a while,
/// you can stop it using dc_stop_ongoing_process(). During execution of the job,
/// some events are sent out:
///
/// - A number of #DC_EVENT_IMEX_PROGRESS events are sent and may be used to create
/// a progress bar or stuff like that. Moreover, you'll be informed when the imex-job is done.
///
/// - For each file written on export, the function sends #DC_EVENT_IMEX_FILE_WRITTEN
///
/// Only one import-/export-progress can run at the same time.
/// To cancel an import-/export-progress, use dc_stop_ongoing_process().
pub fn imex(context: &Context, what: ImexMode, param1: Option<impl AsRef<Path>>) {
let mut param = Params::new();
param.set_int(Param::Cmd, what as i32);
if let Some(param1) = param1 {
@@ -42,7 +75,7 @@ pub fn dc_imex(context: &Context, what: libc::c_int, param1: Option<impl AsRef<P
}
/// Returns the filename of the backup if found, nullptr otherwise.
pub fn dc_imex_has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<String> {
pub fn has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Result<String> {
let dir_name = dir_name.as_ref();
let dir_iter = std::fs::read_dir(dir_name)?;
let mut newest_backup_time = 0;
@@ -75,10 +108,10 @@ pub fn dc_imex_has_backup(context: &Context, dir_name: impl AsRef<Path>) -> Resu
}
}
pub fn dc_initiate_key_transfer(context: &Context) -> Result<String> {
pub fn initiate_key_transfer(context: &Context) -> Result<String> {
let mut msg: Message;
ensure!(dc_alloc_ongoing(context), "could not allocate ongoing");
let setup_code = dc_create_setup_code(context);
let setup_code = create_setup_code(context);
/* this may require a keypair to be created. this may take a second ... */
if !context
.running_state
@@ -87,7 +120,7 @@ pub fn dc_initiate_key_transfer(context: &Context) -> Result<String> {
.unwrap()
.shall_stop_ongoing
{
if let Ok(ref setup_file_content) = dc_render_setup_file(context, &setup_code) {
if let Ok(ref setup_file_content) = render_setup_file(context, &setup_code) {
/* encrypting may also take a while ... */
if !context
.running_state
@@ -96,14 +129,15 @@ pub fn dc_initiate_key_transfer(context: &Context) -> Result<String> {
.unwrap()
.shall_stop_ongoing
{
let setup_file_name =
dc_get_fine_path_filename(context, "$BLOBDIR", "autocrypt-setup-message.html");
if dc_write_file(context, &setup_file_name, setup_file_content.as_bytes()) {
let setup_file_name = context.new_blob_file(
"autocrypt-setup-message.html",
setup_file_content.as_bytes(),
)?;
{
if let Ok(chat_id) = chat::create_by_contact_id(context, 1) {
msg = Message::default();
msg.type_0 = Viewtype::File;
msg.param
.set(Param::File, setup_file_name.to_string_lossy());
msg.param.set(Param::File, setup_file_name);
msg.param
.set(Param::MimeType, "application/autocrypt-setup");
@@ -153,7 +187,7 @@ pub fn dc_initiate_key_transfer(context: &Context) -> Result<String> {
/// Renders HTML body of a setup file message.
///
/// The `passphrase` must be at least 2 characters long.
pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<String> {
pub fn render_setup_file(context: &Context, passphrase: &str) -> Result<String> {
ensure!(
passphrase.len() >= 2,
"Passphrase must be at least 2 chars long."
@@ -203,7 +237,7 @@ pub fn dc_render_setup_file(context: &Context, passphrase: &str) -> Result<Strin
))
}
pub fn dc_create_setup_code(_context: &Context) -> String {
pub fn create_setup_code(_context: &Context) -> String {
let mut random_val: u16;
let mut rng = thread_rng();
let mut ret = String::new();
@@ -211,22 +245,22 @@ pub fn dc_create_setup_code(_context: &Context) -> String {
for i in 0..9 {
loop {
random_val = rng.gen();
if !(random_val as libc::c_int > 60000) {
if !(random_val as usize > 60000) {
break;
}
}
random_val = (random_val as libc::c_int % 10000) as u16;
random_val = (random_val as usize % 10000) as u16;
ret += &format!(
"{}{:04}",
if 0 != i { "-" } else { "" },
random_val as libc::c_int,
random_val as usize
);
}
ret
}
pub fn dc_continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str) -> Result<()> {
pub fn continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str) -> Result<()> {
ensure!(msg_id > DC_MSG_ID_LAST_SPECIAL, "wrong id");
let msg = Message::load_from_db(context, msg_id);
@@ -240,17 +274,14 @@ pub fn dc_continue_key_transfer(context: &Context, msg_id: u32, setup_code: &str
);
if let Some(filename) = msg.get_file(context) {
if let Ok(buf) = dc_read_file(context, filename) {
let norm_sc = CString::yolo(dc_normalize_setup_code(setup_code));
unsafe {
if let Ok(armored_key) =
dc_decrypt_setup_file(context, norm_sc.as_ptr(), buf.as_ptr().cast())
{
set_self_key(context, &armored_key, true, true)?;
} else {
bail!("Bad setup code.")
}
if let Ok(ref mut buf) = dc_read_file(context, filename) {
let sc = normalize_setup_code(setup_code);
if let Ok(armored_key) = decrypt_setup_file(context, sc, buf) {
set_self_key(context, &armored_key, true, true)?;
} else {
bail!("Bad setup code.")
}
Ok(())
} else {
bail!("Cannot read Autocrypt Setup Message file.");
@@ -320,7 +351,7 @@ fn set_self_key(
&public_key,
&private_key,
self_addr.unwrap(),
set_default as libc::c_int,
set_default,
&context.sql,
) {
bail!("Cannot save keypair, internal key-state possibly corrupted now!");
@@ -328,65 +359,56 @@ fn set_self_key(
Ok(())
}
pub unsafe fn dc_decrypt_setup_file(
context: &Context,
passphrase: *const libc::c_char,
filecontent: *const libc::c_char,
fn decrypt_setup_file(
_context: &Context,
passphrase: impl AsRef<str>,
filecontent: &mut [u8],
) -> Result<String> {
let fc_buf: *mut libc::c_char;
let mut fc_headerline = String::default();
let mut fc_base64: *const libc::c_char = ptr::null();
let mut binary: *mut libc::c_char = ptr::null_mut();
let mut binary_bytes: libc::size_t = 0;
let mut indx: libc::size_t = 0;
let mut payload: Result<String> = Err(format_err!("Failed to decrypt"));
let split_result = unsafe {
dc_split_armored_data(
filecontent.as_mut_ptr().cast(),
&mut fc_headerline,
ptr::null_mut(),
ptr::null_mut(),
&mut fc_base64,
)
};
fc_buf = dc_strdup(filecontent);
if dc_split_armored_data(
fc_buf,
&mut fc_headerline,
ptr::null_mut(),
ptr::null_mut(),
&mut fc_base64,
) && fc_headerline == "-----BEGIN PGP MESSAGE-----"
&& !fc_base64.is_null()
{
/* convert base64 to binary */
/*must be freed using mmap_string_unref()*/
if !(mailmime_base64_body_parse(
fc_base64,
strlen(fc_base64),
&mut indx,
&mut binary,
&mut binary_bytes,
) != MAILIMF_NO_ERROR as libc::c_int
|| binary.is_null()
|| binary_bytes == 0)
{
/* decrypt symmetrically */
match dc_pgp_symm_decrypt(
as_str(passphrase),
std::slice::from_raw_parts(binary as *const u8, binary_bytes),
) {
Ok(plain) => payload = Ok(String::from_utf8(plain).unwrap()),
Err(err) => {
error!(context, "Failed to decrypt message: {:?}", err);
payload = Err(err);
}
}
}
if !split_result || fc_headerline != "-----BEGIN PGP MESSAGE-----" || fc_base64.is_null() {
bail!("Invalid armored data");
}
free(fc_buf as *mut libc::c_void);
if !binary.is_null() {
mmap_string_unref(binary);
}
// convert base64 to binary
let base64_encoded =
unsafe { std::slice::from_raw_parts(fc_base64 as *const u8, libc::strlen(fc_base64)) };
payload
let data = base64_decode(&base64_encoded)?;
// decrypt symmetrically
let payload = dc_pgp_symm_decrypt(passphrase.as_ref(), &data)?;
let payload_str = String::from_utf8(payload)?;
Ok(payload_str)
}
pub fn dc_normalize_setup_code(s: &str) -> String {
/// Decode the base64 encoded slice. Handles line breaks.
fn base64_decode(input: &[u8]) -> Result<Vec<u8>> {
use std::io::Read;
let c = std::io::Cursor::new(input);
let lr = pgp::line_reader::LineReader::new(c);
let br = pgp::base64_reader::Base64Reader::new(lr);
let mut reader = pgp::base64_decoder::Base64Decoder::new(br);
let mut data = Vec::new();
reader.read_to_end(&mut data)?;
Ok(data)
}
pub fn normalize_setup_code(s: &str) -> String {
let mut out = String::new();
for c in s.chars() {
if c >= '0' && c <= '9' {
@@ -400,9 +422,9 @@ pub fn dc_normalize_setup_code(s: &str) -> String {
}
#[allow(non_snake_case)]
pub fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
pub fn job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
ensure!(dc_alloc_ongoing(context), "could not allocate ongoing");
let what = job.param.get_int(Param::Cmd).unwrap_or_default();
let what: Option<ImexMode> = job.param.get_int(Param::Cmd).and_then(ImexMode::from_i32);
let param = job.param.get(Param::Arg).unwrap_or_default();
ensure!(!param.is_empty(), "No Import/export dir/file given.");
@@ -410,8 +432,8 @@ pub fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
context.call_cb(Event::ImexProgress(10));
ensure!(context.sql.is_open(), "Database not opened.");
if what == DC_IMEX_EXPORT_BACKUP || what == DC_IMEX_EXPORT_SELF_KEYS {
/* before we export anything, make sure the private key exists */
if what == Some(ImexMode::ExportBackup) || what == Some(ImexMode::ExportSelfKeys) {
// before we export anything, make sure the private key exists
if e2ee::ensure_secret_key_exists(context).is_err() {
dc_free_ongoing(context);
bail!("Cannot create private key or private key not available.");
@@ -421,12 +443,12 @@ pub fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
}
let path = Path::new(param);
let success = match what {
DC_IMEX_EXPORT_SELF_KEYS => export_self_keys(context, path),
DC_IMEX_IMPORT_SELF_KEYS => import_self_keys(context, path),
DC_IMEX_EXPORT_BACKUP => unsafe { export_backup(context, path) },
DC_IMEX_IMPORT_BACKUP => import_backup(context, path),
_ => {
bail!("unknown IMEX type: {}", what);
Some(ImexMode::ExportSelfKeys) => export_self_keys(context, path),
Some(ImexMode::ImportSelfKeys) => import_self_keys(context, path),
Some(ImexMode::ExportBackup) => export_backup(context, path),
Some(ImexMode::ImportBackup) => import_backup(context, path),
None => {
bail!("unknown IMEX type");
}
};
dc_free_ongoing(context);
@@ -443,11 +465,7 @@ pub fn dc_job_do_DC_JOB_IMEX_IMAP(context: &Context, job: &Job) -> Result<()> {
}
}
/*******************************************************************************
* Import backup
******************************************************************************/
#[allow(non_snake_case)]
/// Import Backup
fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Result<()> {
info!(
context,
@@ -523,13 +541,13 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
continue;
}
let pathNfilename = context.get_blobdir().join(file_name);
if dc_write_file(context, &pathNfilename, &file_blob) {
let path_filename = context.get_blobdir().join(file_name);
if dc_write_file(context, &path_filename, &file_blob) {
continue;
}
bail!(
"Storage full? Cannot write file {} with {} bytes.",
pathNfilename.display(),
path_filename.display(),
file_blob.len(),
);
}
@@ -550,20 +568,16 @@ fn import_backup(context: &Context, backup_to_import: impl AsRef<Path>) -> Resul
******************************************************************************/
/* the FILE_PROGRESS macro calls the callback with the permille of files processed.
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<()> {
fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
let mut ok_to_continue = true;
let mut success = false;
let mut delete_dest_file: libc::c_int = 0;
// get a fine backup file name (the name includes the date so that multiple backup instances are possible)
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete. however, currently it is not clear it the import exists in the long run (may be replaced by a restore-from-imap)
// FIXME: we should write to a temporary file first and rename it on success. this would guarantee the backup is complete.
// let dest_path_filename = dc_get_next_backup_file(context, dir, res);
let now = time();
let res = chrono::NaiveDateTime::from_timestamp(now as i64, 0)
.format("delta-chat-%Y-%m-%d.bak")
.to_string();
let dest_path_filename = dc_get_fine_path_filename(context, dir, res);
let dest_path_filename = dc_get_next_backup_path(dir, now)?;
sql::housekeeping(context);
@@ -650,9 +664,9 @@ unsafe fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()>
continue;
} else {
info!(context, "EXPORTing filename={}", name);
let curr_pathNfilename = context.get_blobdir().join(entry.file_name());
let curr_path_filename = context.get_blobdir().join(entry.file_name());
if let Ok(buf) =
dc_read_file(context, &curr_pathNfilename)
dc_read_file(context, &curr_path_filename)
{
if buf.is_empty() {
continue;
@@ -661,7 +675,7 @@ unsafe fn export_backup(context: &Context, dir: impl AsRef<Path>) -> Result<()>
error!(
context,
"Disk full? Cannot add file \"{}\" to backup.",
curr_pathNfilename.display(),
curr_path_filename.display(),
);
/* this is not recoverable! writing to the sqlite database should work! */
ok_to_continue = false;
@@ -773,7 +787,7 @@ fn import_self_keys(context: &Context, dir: impl AsRef<Path>) -> Result<()> {
ptr::null_mut(),
ptr::null_mut(),
);
free(buf2 as *mut libc::c_void);
libc::free(buf2 as *mut libc::c_void);
}
if split_res
&& buf2_headerline.contains("-----BEGIN PGP PUBLIC KEY BLOCK-----")
@@ -884,7 +898,7 @@ mod tests {
let t = test_context(Some(Box::new(logging_cb)));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "hello").unwrap();
let msg = render_setup_file(&t.ctx, "hello").unwrap();
println!("{}", &msg);
// Check some substrings, indicating things got substituted.
// In particular note the mixing of `\r\n` and `\n` depending
@@ -910,10 +924,10 @@ mod tests {
}
#[test]
fn test_render_setup_file_newline_replace() {
fn otest_render_setup_file_newline_replace() {
let t = test_context(Some(Box::new(ac_setup_msg_cb)));
configure_alice_keypair(&t.ctx);
let msg = dc_render_setup_file(&t.ctx, "pw").unwrap();
let msg = render_setup_file(&t.ctx, "pw").unwrap();
println!("{}", &msg);
assert!(msg.contains("<p>hello<br>there</p>"));
}
@@ -921,7 +935,7 @@ mod tests {
#[test]
fn test_create_setup_code() {
let t = dummy_context();
let setupcode = dc_create_setup_code(&t.ctx);
let setupcode = create_setup_code(&t.ctx);
assert_eq!(setupcode.len(), 44);
assert_eq!(setupcode.chars().nth(4).unwrap(), '-');
assert_eq!(setupcode.chars().nth(9).unwrap(), '-');
@@ -949,12 +963,65 @@ mod tests {
#[test]
fn test_normalize_setup_code() {
let norm = dc_normalize_setup_code("123422343234423452346234723482349234");
let norm = normalize_setup_code("123422343234423452346234723482349234");
assert_eq!(norm, "1234-2234-3234-4234-5234-6234-7234-8234-9234");
let norm = dc_normalize_setup_code(
"\t1 2 3422343234- foo bar-- 423-45 2 34 6234723482349234 ",
);
let norm =
normalize_setup_code("\t1 2 3422343234- foo bar-- 423-45 2 34 6234723482349234 ");
assert_eq!(norm, "1234-2234-3234-4234-5234-6234-7234-8234-9234");
}
/* S_EM_SETUPFILE is a AES-256 symm. encrypted setup message created by Enigmail
with an "encrypted session key", see RFC 4880. The code is in S_EM_SETUPCODE */
const S_EM_SETUPCODE: &str = "1742-0185-6197-1303-7016-8412-3581-4441-0597";
const S_EM_SETUPFILE: &str = include_str!("../test-data/message/stress.txt");
#[test]
fn test_split_and_decrypt() {
let ctx = dummy_context();
let context = &ctx.ctx;
let mut headerline = String::default();
let mut setupcodebegin = ptr::null();
let mut preferencrypt = ptr::null();
let mut buf_1 = S_EM_SETUPFILE.to_string();
unsafe {
assert!(dc_split_armored_data(
buf_1.as_mut_ptr().cast(),
&mut headerline,
&mut setupcodebegin,
&mut preferencrypt,
ptr::null_mut(),
));
}
assert_eq!(headerline, "-----BEGIN PGP MESSAGE-----");
assert!(!setupcodebegin.is_null());
// TODO: verify that this is the right check
assert!(S_EM_SETUPCODE.starts_with(as_str(setupcodebegin)));
assert!(preferencrypt.is_null());
let mut setup_file = S_EM_SETUPFILE.to_string();
let mut decrypted = unsafe {
decrypt_setup_file(context, S_EM_SETUPCODE, setup_file.as_bytes_mut()).unwrap()
};
unsafe {
assert!(dc_split_armored_data(
decrypted.as_mut_ptr().cast(),
&mut headerline,
&mut setupcodebegin,
&mut preferencrypt,
ptr::null_mut(),
));
}
assert_eq!(headerline, "-----BEGIN PGP PRIVATE KEY BLOCK-----");
assert!(setupcodebegin.is_null());
assert!(!preferencrypt.is_null());
assert_eq!(as_str(preferencrypt), "mutual",);
}
}

View File

@@ -7,10 +7,10 @@ use crate::chat;
use crate::configure::*;
use crate::constants::*;
use crate::context::Context;
use crate::dc_imex::*;
use crate::dc_tools::*;
use crate::events::Event;
use crate::imap::*;
use crate::imex::*;
use crate::location;
use crate::login_param::LoginParam;
use crate::message::{self, Message, MessageState};
@@ -317,7 +317,7 @@ impl Job {
{
let folder = msg.server_folder.as_ref().unwrap();
match inbox.set_mdnsent(context, folder, msg.server_uid) {
match inbox.set_mdnsent(&context, folder, msg.server_uid) {
ImapResult::RetryLater => {
self.try_again_later(3i32, None);
}
@@ -824,7 +824,7 @@ fn job_perform(context: &Context, thread: Thread, probe_network: bool) {
Action::MoveMsg => job.do_DC_JOB_MOVE_MSG(context),
Action::SendMdn => job.do_DC_JOB_SEND(context),
Action::ConfigureImap => unsafe { dc_job_do_DC_JOB_CONFIGURE_IMAP(context) },
Action::ImexImap => match dc_job_do_DC_JOB_IMEX_IMAP(context, &job) {
Action::ImexImap => match job_do_DC_JOB_IMEX_IMAP(context, &job) {
Ok(()) => {}
Err(err) => {
error!(context, "{}", err);
@@ -965,41 +965,39 @@ fn send_mdn(context: &Context, msg_id: u32) {
#[allow(non_snake_case)]
fn add_smtp_job(context: &Context, action: Action, mimefactory: &MimeFactory) -> libc::c_int {
let mut success: libc::c_int = 0i32;
let mut param = Params::new();
let path_filename = dc_get_fine_path_filename(context, "$BLOBDIR", &mimefactory.rfc724_mid);
let bytes = unsafe {
std::slice::from_raw_parts(
(*mimefactory.out).str_0 as *const u8,
(*mimefactory.out).len,
)
};
if !dc_write_file(context, &path_filename, bytes) {
error!(
context,
"Could not write message <{}> to \"{}\".",
mimefactory.rfc724_mid,
path_filename.display(),
);
} else {
info!(context, "add_smtp_job file written: {:?}", path_filename);
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, path_filename.to_string_lossy());
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id
} else {
0
}) as libc::c_int,
param,
0,
);
success = 1;
}
success
let bpath = match context.new_blob_file(&mimefactory.rfc724_mid, bytes) {
Ok(path) => path,
Err(err) => {
error!(
context,
"Could not write {} smtp-message, error {}", mimefactory.rfc724_mid, err
);
return 0;
}
};
info!(context, "add_smtp_job file written: {:?}", bpath);
let recipients = mimefactory.recipients_addr.join("\x1e");
param.set(Param::File, &bpath);
param.set(Param::Recipients, &recipients);
job_add(
context,
action,
(if mimefactory.loaded == Loaded::Message {
mimefactory.msg.id
} else {
0
}) as libc::c_int,
param,
0,
);
1
}
pub fn job_add(

View File

@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
use std::io::Cursor;
use std::path::Path;
use libc;
use pgp::composed::{Deserializable, SignedPublicKey, SignedSecretKey};
use pgp::ser::Serialize;
use pgp::types::{KeyTrait, SecretKeyTrait};
@@ -254,14 +253,14 @@ pub fn dc_key_save_self_keypair(
public_key: &Key,
private_key: &Key,
addr: impl AsRef<str>,
is_default: libc::c_int,
is_default: bool,
sql: &Sql,
) -> bool {
sql::execute(
context,
sql,
"INSERT INTO keypairs (addr, is_default, public_key, private_key, created) VALUES (?,?,?,?,?);",
params![addr.as_ref(), is_default, public_key.to_bytes(), private_key.to_bytes(), time()],
params![addr.as_ref(), is_default as i32, public_key.to_bytes(), private_key.to_bytes(), time()],
).is_ok()
}

View File

@@ -39,36 +39,36 @@ pub mod contact;
pub mod context;
mod e2ee;
mod imap;
pub mod imex;
pub mod job;
mod job_thread;
pub mod key;
pub mod keyring;
pub mod location;
mod login_param;
pub mod lot;
pub mod message;
mod mimefactory;
pub mod oauth2;
mod param;
pub mod peerstate;
pub mod pgp;
pub mod qr;
pub mod securejoin;
mod smtp;
pub mod sql;
mod stock;
mod token;
#[macro_use]
mod wrapmime;
pub mod dc_array;
mod dc_dehtml;
pub mod dc_imex;
pub mod dc_mimeparser;
pub mod dc_receive_imf;
mod dc_simplify;
mod dc_strencode;
pub mod dc_tools;
mod login_param;
mod mimefactory;
pub mod securejoin;
mod token;
#[macro_use]
mod wrapmime;
#[cfg(test)]
mod test_utils;

View File

@@ -16,7 +16,7 @@ use crate::chat::{self, Chat};
use crate::constants::*;
use crate::contact::*;
use crate::context::{get_version_str, Context};
use crate::dc_mimeparser::{mailmime_find_mailimf_fields, SystemMessage};
use crate::dc_mimeparser::SystemMessage;
use crate::dc_strencode::*;
use crate::dc_tools::*;
use crate::e2ee::*;
@@ -645,7 +645,7 @@ impl<'a> MimeFactory<'a> {
);
/*just a pointer into mailmime structure, must not be freed*/
let imffields_unprotected = mailmime_find_mailimf_fields(message);
let imffields_unprotected = wrapmime::mailmime_find_mailimf_fields(message);
ensure!(
!imffields_unprotected.is_null(),
"could not find mime fields"

View File

@@ -413,7 +413,7 @@ pub fn handle_securejoin_handshake(
could_not_establish_secure_connection(
context,
contact_chat_id,
if mimeparser.e2ee_helper.encrypted {
if mimeparser.encrypted {
"No valid signature."
} else {
"Not encrypted."
@@ -693,17 +693,16 @@ fn mark_peer_as_verified(context: &Context, fingerprint: impl AsRef<str>) -> Res
******************************************************************************/
fn encrypted_and_signed(mimeparser: &MimeParser, expected_fingerprint: impl AsRef<str>) -> bool {
if !mimeparser.e2ee_helper.encrypted {
if !mimeparser.encrypted {
warn!(mimeparser.context, "Message not encrypted.",);
false
} else if mimeparser.e2ee_helper.signatures.len() <= 0 {
} else if mimeparser.signatures.len() <= 0 {
warn!(mimeparser.context, "Message not signed.",);
false
} else if expected_fingerprint.as_ref().is_empty() {
warn!(mimeparser.context, "Fingerprint for comparison missing.",);
false
} else if !mimeparser
.e2ee_helper
.signatures
.contains(expected_fingerprint.as_ref())
{

View File

@@ -76,7 +76,7 @@ pub fn configure_alice_keypair(ctx: &Context) -> String {
KeyType::Private,
)
.unwrap();
let saved = key::dc_key_save_self_keypair(&ctx, &public, &private, &addr, 1, &ctx.sql);
let saved = key::dc_key_save_self_keypair(&ctx, &public, &private, &addr, true, &ctx.sql);
assert_eq!(saved, true, "Failed to save Alice's key");
addr
}

View File

@@ -21,6 +21,8 @@ if __name__ == "__main__":
sum_unsafe, sum_free, sum_gotoblocks, sum_chars = 0, 0, 0, 0
for fn, unsafe, free, gotoblocks, chars in reversed(sorted(filestats, key=lambda x: sum(x[1:]))):
if unsafe + free + gotoblocks + chars == 0:
continue
print("{0: <25} unsafe: {1: >3} free: {2: >3} ok_to_cont: {3: >3} chars: {4: >3}".format(str(fn), unsafe, free, gotoblocks, chars))
sum_unsafe += unsafe
sum_free += free

View File

@@ -1,9 +1,12 @@
use std::collections::HashSet;
use std::ffi::CString;
use std::ptr;
use crate::contact::addr_normalize;
use crate::dc_tools::*;
use crate::error::Error;
use mmime::clist::*;
// use mmime::display::*;
use mmime::mailimf::types::*;
use mmime::mailimf::types_helper::*;
use mmime::mailmime::content::*;
@@ -11,6 +14,7 @@ use mmime::mailmime::disposition::*;
use mmime::mailmime::types::*;
use mmime::mailmime::types_helper::*;
use mmime::mailmime::*;
use mmime::mmapstring::*;
use mmime::other::*;
#[macro_export]
@@ -88,6 +92,192 @@ pub fn has_decryptable_data(mime_data: *mut mailmime_data) -> bool {
}
}
pub fn get_field_from(imffields: *mut mailimf_fields) -> Result<String, Error> {
let field = mailimf_find_field(imffields, MAILIMF_FIELD_FROM as libc::c_int);
if !field.is_null() && unsafe { !(*field).fld_data.fld_from.is_null() } {
let mb_list = unsafe { (*(*field).fld_data.fld_from).frm_mb_list };
if let Some(addr) = mailimf_find_first_addr(mb_list) {
return Ok(addr);
}
}
bail!("not From field found");
}
pub fn get_field_date(imffields: *mut mailimf_fields) -> Result<i64, Error> {
let field = mailimf_find_field(imffields, MAILIMF_FIELD_ORIG_DATE as libc::c_int);
let mut message_time = 0;
if !field.is_null() && unsafe { !(*field).fld_data.fld_orig_date.is_null() } {
let orig_date = unsafe { (*field).fld_data.fld_orig_date };
if !orig_date.is_null() {
let dt = unsafe { (*orig_date).dt_date_time };
message_time = dc_timestamp_from_date(dt);
if message_time != 0 && message_time > time() {
message_time = time()
}
}
}
Ok(message_time)
}
fn mailimf_get_recipients_add_addr(recipients: &mut HashSet<String>, mb: *mut mailimf_mailbox) {
if !mb.is_null() {
let addr_norm = addr_normalize(as_str(unsafe { (*mb).mb_addr_spec }));
recipients.insert(addr_norm.into());
}
}
/*the result is a pointer to mime, must not be freed*/
pub fn mailimf_find_field(
header: *mut mailimf_fields,
wanted_fld_type: libc::c_int,
) -> *mut mailimf_field {
if header.is_null() {
return ptr::null_mut();
}
let header = unsafe { (*header) };
if header.fld_list.is_null() {
return ptr::null_mut();
}
for cur in unsafe { &(*header.fld_list) } {
let field = cur as *mut mailimf_field;
if !field.is_null() {
if unsafe { (*field).fld_type } == wanted_fld_type {
return field;
}
}
}
ptr::null_mut()
}
/*the result is a pointer to mime, must not be freed*/
pub unsafe fn mailmime_find_mailimf_fields(mime: *mut Mailmime) -> *mut mailimf_fields {
if mime.is_null() {
return ptr::null_mut();
}
match (*mime).mm_type as _ {
MAILMIME_MULTIPLE => {
for cur_data in (*(*mime).mm_data.mm_multipart.mm_mp_list).into_iter() {
let header = mailmime_find_mailimf_fields(cur_data as *mut _);
if !header.is_null() {
return header;
}
}
}
MAILMIME_MESSAGE => return (*mime).mm_data.mm_message.mm_fields,
_ => {}
}
ptr::null_mut()
}
pub unsafe fn mailimf_find_optional_field(
header: *mut mailimf_fields,
wanted_fld_name: *const libc::c_char,
) -> *mut mailimf_optional_field {
if header.is_null() || (*header).fld_list.is_null() {
return ptr::null_mut();
}
for cur_data in (*(*header).fld_list).into_iter() {
let field: *mut mailimf_field = cur_data as *mut _;
if (*field).fld_type == MAILIMF_FIELD_OPTIONAL_FIELD as libc::c_int {
let optional_field: *mut mailimf_optional_field = (*field).fld_data.fld_optional_field;
if !optional_field.is_null()
&& !(*optional_field).fld_name.is_null()
&& !(*optional_field).fld_value.is_null()
&& strcasecmp((*optional_field).fld_name, wanted_fld_name) == 0i32
{
return optional_field;
}
}
}
ptr::null_mut()
}
pub fn mailimf_get_recipients(imffields: *mut mailimf_fields) -> HashSet<String> {
/* returned addresses are normalized. */
let mut recipients: HashSet<String> = Default::default();
for cur in unsafe { (*(*imffields).fld_list).into_iter() } {
let fld = cur as *mut mailimf_field;
let fld_to: *mut mailimf_to;
let fld_cc: *mut mailimf_cc;
let mut addr_list: *mut mailimf_address_list = ptr::null_mut();
if fld.is_null() {
continue;
}
let fld = unsafe { *fld };
// TODO match on enums /rtn
match fld.fld_type {
13 => {
fld_to = unsafe { fld.fld_data.fld_to };
if !fld_to.is_null() {
addr_list = unsafe { (*fld_to).to_addr_list };
}
}
14 => {
fld_cc = unsafe { fld.fld_data.fld_cc };
if !fld_cc.is_null() {
addr_list = unsafe { (*fld_cc).cc_addr_list };
}
}
_ => {}
}
if !addr_list.is_null() {
for cur2 in unsafe { &(*(*addr_list).ad_list) } {
let adr = cur2 as *mut mailimf_address;
if adr.is_null() {
continue;
}
let adr = unsafe { *adr };
if adr.ad_type == MAILIMF_ADDRESS_MAILBOX as libc::c_int {
mailimf_get_recipients_add_addr(&mut recipients, unsafe {
adr.ad_data.ad_mailbox
});
} else if adr.ad_type == MAILIMF_ADDRESS_GROUP as libc::c_int {
let group = unsafe { adr.ad_data.ad_group };
if !group.is_null() && unsafe { !(*group).grp_mb_list.is_null() } {
for cur3 in unsafe { &(*(*(*group).grp_mb_list).mb_list) } {
mailimf_get_recipients_add_addr(
&mut recipients,
cur3 as *mut mailimf_mailbox,
);
}
}
}
}
}
}
recipients
}
pub fn mailmime_transfer_decode(mime: *mut Mailmime) -> Result<Vec<u8>, Error> {
ensure!(!mime.is_null(), "invalid inputs");
let mime_transfer_encoding =
get_mime_transfer_encoding(mime).unwrap_or(MAILMIME_MECHANISM_BINARY as i32);
let mime_data = unsafe { (*mime).mm_data.mm_single };
decode_dt_data(mime_data, mime_transfer_encoding)
}
pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
unsafe {
let mm_mime_fields = (*mime).mm_mime_fields;
@@ -108,48 +298,77 @@ pub fn get_mime_transfer_encoding(mime: *mut Mailmime) -> Option<libc::c_int> {
pub fn decode_dt_data(
mime_data: *mut mailmime_data,
mime_transfer_encoding: libc::c_int,
) -> Result<(*mut libc::c_char, libc::size_t), Error> {
) -> Result<Vec<u8>, Error> {
// Decode data according to mime_transfer_encoding
// returns Ok with a (decoded_data,decoded_data_bytes) pointer
// where the caller must make sure to free it.
// It may return Ok(ptr::null_mut(), 0)
let mut transfer_decoding_buffer: *mut libc::c_char = ptr::null_mut();
let decoded_data: *mut libc::c_char;
let mut decoded_data_bytes: libc::size_t = 0;
if mime_transfer_encoding == MAILMIME_MECHANISM_7BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_8BIT as libc::c_int
|| mime_transfer_encoding == MAILMIME_MECHANISM_BINARY as libc::c_int
{
unsafe {
decoded_data = (*mime_data).dt_data.dt_text.dt_data as *mut _;
decoded_data_bytes = (*mime_data).dt_data.dt_text.dt_length;
}
ensure!(
!decoded_data.is_null() && decoded_data_bytes > 0,
"could not decode mime message"
);
} else {
let mut current_index: libc::size_t = 0;
unsafe {
let r = mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
);
if r != MAILIMF_NO_ERROR as libc::c_int
|| transfer_decoding_buffer.is_null()
|| decoded_data_bytes <= 0
{
bail!("mailmime_part_parse returned error or invalid data");
}
decoded_data = transfer_decoding_buffer;
let decoded_data = unsafe { (*mime_data).dt_data.dt_text.dt_data };
let decoded_data_bytes = unsafe { (*mime_data).dt_data.dt_text.dt_length };
if decoded_data.is_null() || decoded_data_bytes <= 0 {
bail!("No data to decode found");
} else {
let result = unsafe {
std::slice::from_raw_parts(decoded_data as *const u8, decoded_data_bytes)
};
return Ok(result.to_vec());
}
}
Ok((decoded_data, decoded_data_bytes))
// unsafe { display_mime_data(mime_data) };
let mut current_index = 0;
let mut transfer_decoding_buffer = ptr::null_mut();
let mut decoded_data_bytes = 0;
let r = unsafe {
mailmime_part_parse(
(*mime_data).dt_data.dt_text.dt_data,
(*mime_data).dt_data.dt_text.dt_length,
&mut current_index,
mime_transfer_encoding,
&mut transfer_decoding_buffer,
&mut decoded_data_bytes,
)
};
if r == MAILIMF_NO_ERROR as libc::c_int
&& !transfer_decoding_buffer.is_null()
&& decoded_data_bytes > 0
{
let result = unsafe {
std::slice::from_raw_parts(transfer_decoding_buffer as *const u8, decoded_data_bytes)
}
.to_vec();
// we return a fresh vec and transfer_decoding_buffer is not used or passed anywhere
// so it's safe to free it right away, as mailman_part_parse has
// allocated it fresh.
unsafe { mmap_string_unref(transfer_decoding_buffer) };
return Ok(result);
}
Err(format_err!("Failed to to decode"))
}
pub fn mailimf_find_first_addr(mb_list: *const mailimf_mailbox_list) -> Option<String> {
if mb_list.is_null() {
return None;
}
for cur in unsafe { (*(*mb_list).mb_list).into_iter() } {
let mb = cur as *mut mailimf_mailbox;
if !mb.is_null() && !unsafe { (*mb).mb_addr_spec.is_null() } {
let addr = unsafe { as_str((*mb).mb_addr_spec) };
return Some(addr_normalize(addr).to_string());
}
}
None
}
/**************************************

View File

@@ -0,0 +1,125 @@
-----BEGIN PGP MESSAGE-----
Passphrase-Format: numeric9x4
Passphrase-Begin: 17
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
mngm1QCMMw2v/j8=
=9sJE
-----END PGP MESSAGE-----

File diff suppressed because one or more lines are too long