Compare commits

...
Author SHA1 Message Date
link2xt 91aa8cf259 Add API to change database passphrase
New API is:
- dc_context_is_encrypted()
- dc_context_change_passphrase()
2022-01-29 21:01:50 +00:00
link2xt e73107006e smtp: replace thiserror with anyhow 2022-01-29 16:41:47 +00:00
link2xt ca389cc6fc Move webxdc change to Unreleased 2022-01-29 16:41:04 +00:00
link2xt 60ec7f0cbf Move last changelog entry to Unreleased 2022-01-29 16:39:28 +00:00
B. Petersen d342d59e65 use webxdc app name in chatlist/quotes/replies/etc
this uses `get_webxdc_info().name` for chatlist etc.
the previuosly used static strings comes from a time
where we just did not had the correct name.

i was also thinking about adding `get_webxdc_info().summary`,
however, as this information is dynamic,
that may open several issues, eg. quoted text may change
so that the answer is out of context.
2022-01-29 16:48:24 +01:00
link2xt 2690fa2da5 Don't watch Sent folder by default 2022-01-29 11:35:02 +00:00
B. Petersen e411c394ca add a link to search for #webxdc on github to the webxdc-docs 2022-01-29 00:42:56 +01:00
B. Petersen d69f3ba225 adapt draft to new api 2022-01-28 21:10:34 +01:00
B. Petersen 739807b1a9 add links to webxdc development tool, simulator and to advanced examples 2022-01-28 17:42:32 +01:00
dependabot[bot] d029ea7f3f cargo: bump mailparse from 0.13.7 to 0.13.8
Bumps [mailparse](https://github.com/staktrace/mailparse) from 0.13.7 to 0.13.8.
- [Release notes](https://github.com/staktrace/mailparse/releases)
- [Commits](https://github.com/staktrace/mailparse/compare/v0.13.7...v0.13.8)

---
updated-dependencies:
- dependency-name: mailparse
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-27 21:27:05 +01:00
dependabot[bot] 11098cb869 cargo: bump libc from 0.2.113 to 0.2.114
Bumps [libc](https://github.com/rust-lang/libc) from 0.2.113 to 0.2.114.
- [Release notes](https://github.com/rust-lang/libc/releases)
- [Commits](https://github.com/rust-lang/libc/compare/0.2.113...0.2.114)

---
updated-dependencies:
- dependency-name: libc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-27 21:26:23 +01:00
Hocuri f6807d6b22 Fix set_config_bool() (#3013) 2022-01-27 12:12:16 +01:00
11 changed files with 232 additions and 60 deletions
+12
View File
@@ -1,5 +1,17 @@
# Changelog
## Unreleased
### API Changes
- added APIs to check if database is encrypted and to change the passphrase:
`dc_context_is_encrypted()` and `dc_context_change_passphrase()` #3029.
### Changes
- don't watch Sent folder by default #3025
- use webxdc app name in chatlist/quotes/replies etc. #3027
- refactorings #3023
## 1.72.0
### Fixes
Generated
+5 -5
View File
@@ -2122,9 +2122,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.113"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9"
checksum = "b0005d08a8f7b65fb8073cb697aa0b12b631ed251ce73d862ce50eeb52ce3b50"
[[package]]
name = "libm"
@@ -2185,12 +2185,12 @@ dependencies = [
[[package]]
name = "mailparse"
version = "0.13.7"
version = "0.13.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d70ae0840b192a2f7d1dc46e75f38720a7e3c52dfdc968ba3202fa270668dc67"
checksum = "8cae768a50835557749599277fc59f7c728118724eb34185e8feb633ef266a32"
dependencies = [
"base64 0.13.0",
"charset",
"data-encoding",
"quoted_printable",
]
+28 -2
View File
@@ -241,6 +241,32 @@ int dc_context_open (dc_context_t *context, const char*
int dc_context_is_open (dc_context_t *context);
/**
* Return 1 if database is encrypted. Can only be checked on open database.
* Use this method to decide whether to present an option to change passphrase
* to the user.
*
* @member dc_context_t
* @param context The context object.
* @return 1 if database is encrypted, 0 if database is not encrypted or on
* error.
*/
int dc_context_is_encrypted (dc_context_t *context);
/**
* Changes passphrase for the open database. The database must be encrypted
* already, i.e. have a non-empty password. Unencrypted databases can only be
* encrypted during import/export.
* @memberof dc_context_t
* @param context The context object.
* @param passpharse New passphrase.
* @return 1 if database was reencrypted with the new passphrase, 0 on error
* (database is closed, database is not encrypted, other SQLCipher error).
*/
int dc_context_change_passphrase (dc_context_t *context, char *passphrase);
/**
* Free a context object.
*
@@ -335,8 +361,8 @@ char* dc_get_blobdir (const dc_context_t* context);
* 1=send a copy of outgoing messages to self.
* Sending messages to self is needed for a proper multi-account setup,
* however, on the other hand, may lead to unwanted notifications in non-delta clients.
* - `sentbox_watch`= 1=watch `Sent`-folder for changes (default),
* 0=do not watch the `Sent`-folder,
* - `sentbox_watch`= 1=watch `Sent`-folder for changes,
* 0=do not watch the `Sent`-folder (default),
* changes require restarting IO by calling dc_stop_io() and then dc_start_io().
* - `mvbox_move` = 1=detect chat messages,
* move them to the `DeltaChat` folder,
+28
View File
@@ -144,6 +144,34 @@ pub unsafe extern "C" fn dc_context_is_open(context: *mut dc_context_t) -> libc:
block_on(ctx.is_open()) as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_context_is_encrypted(context: *mut dc_context_t) -> libc::c_int {
if context.is_null() {
eprintln!("ignoring careless call to dc_context_is_encrypted()");
return 0;
}
let ctx = &*context;
block_on(ctx.is_encrypted()) as libc::c_int
}
#[no_mangle]
pub unsafe extern "C" fn dc_context_change_passphrase(
context: *mut dc_context_t,
passphrase: *const libc::c_char,
) -> libc::c_int {
if context.is_null() {
eprintln!("ignoring careless call to dc_context_change_passphrase()");
return 0;
}
let ctx = &*context;
let passphrase = to_string_lossy(passphrase);
block_on(ctx.change_passphrase(passphrase))
.log_err(ctx, "change_passphrase failed")
.is_ok() as libc::c_int
}
/// Release the context structure.
///
/// This function releases the memory of the `dc_context_t` structure.
+34 -14
View File
@@ -68,38 +68,46 @@ The callback is called for updates sent by you or other peers.
### getAllUpdates()
```
payloads = window.webxdc.getAllUpdates()
```js
updates = await window.webxdc.getAllUpdates();
```
In case your Webxdc was just started,
you may want to reconstruct the state from the last run -
and also incorporate updates that may have arrived while the app was not running.
- `payloads`: the function returns all previous updates in an array,
- `updates`: All previous updates in an array,
eg. `[{payload: "foo"},{payload: "bar"}]`
if `webxdc.sendUpdate("foo"); webxdc.sendUpdate("bar");` was called on the last run.
if `webxdc.sendUpdate({payload: "foo"}); webxdc.sendUpdate({payload: "bar"};` was called on the last run.
### selfAddr()
The updates are wrapped into a Promise that you can `await` for.
If you are not in an async function and cannot use `await` therefore,
you can get the updates with `then()`:
```js
addr = window.webxdc.selfAddr()
window.webxdc.getAllUpdates().then(updates => {});
```
Returns the peer's own address.
### selfAddr
```js
window.webxdc.selfAddr
```
Property with the peer's own address.
This is esp. useful if you want to differ between different peers -
just send the address along with the payload,
and, if needed, compare the payload addresses against selfAddr() later on.
### selfName()
### selfName
```js
addr = window.webxdc.selfName()
window.webxdc.selfName
```
Returns the peer's own name.
Property with the peer's own name.
This is name chosen by the user in their settings,
if there is nothing set, that defaults to the peer's address.
@@ -128,7 +136,7 @@ round corners etc. will be added by the implementations as needed.
If no icon is set, a default icon will be used.
## Webxdc Example
## Webxdc Examples
The following example shows an input field and every input is show on all peers.
@@ -155,14 +163,26 @@ The following example shows an input field and every input is show on all peers
}
window.webxdc.setUpdateListener(receiveUpdate);
window.webxdc.getAllUpdates().forEach(receiveUpdate);
window.webxdc.getAllUpdates().then(updates => updates.forEach(receiveUpdate));
</script>
</body>
</html>
```
For a more advanved example, see https://github.com/r10s/webxdc-poll/ .
[Webxdc Development Tool](https://github.com/deltachat/webxdc-dev)
offers an **Webxdc Simulator** that can be used in many browsers without any installation needed.
You can also use that repository as a template for your own app -
just clone and start adapting things to your need.
### Advanced Examples
- [2048](https://github.com/adbenitez/2048.xdc)
- [Draw](https://github.com/adbenitez/draw.xdc)
- [Poll](https://github.com/r10s/webxdc-poll/)
- [Tic Tac Toe](https://github.com/Simon-Laux/tictactoe.xdc)
- Even more with [Topic #webxdc on Github](https://github.com/topics/webxdc)
## Closing Remarks
+15 -2
View File
@@ -65,7 +65,7 @@ pub enum Config {
#[strum(props(default = "1"))]
MdnsEnabled,
#[strum(props(default = "1"))]
#[strum(props(default = "0"))]
SentboxWatch,
#[strum(props(default = "1"))]
@@ -309,7 +309,7 @@ impl Context {
}
pub async fn set_config_bool(&self, key: Config, value: bool) -> Result<()> {
self.set_config(key, if value { Some("1") } else { None })
self.set_config(key, if value { Some("1") } else { Some("0") })
.await?;
Ok(())
}
@@ -401,4 +401,17 @@ mod tests {
Ok(())
}
/// Regression test for https://github.com/deltachat/deltachat-core-rust/issues/3012
#[async_std::test]
async fn test_set_config_bool() -> Result<()> {
let t = TestContext::new().await;
// We need some config that defaults to true
let c = Config::E2eeEnabled;
assert_eq!(t.get_config_bool(c).await?, true);
t.set_config_bool(c, false).await?;
assert_eq!(t.get_config_bool(c).await?, false);
Ok(())
}
}
+44
View File
@@ -146,6 +146,12 @@ impl Context {
self.sql.is_open().await
}
/// Returns true if database is encrypted. Returns false if database is not open yet or not
/// encrypted.
pub async fn is_encrypted(&self) -> bool {
self.sql.is_encrypted().await.unwrap_or(false)
}
/// Tests the database passphrase.
///
/// Returns true if passphrase is correct.
@@ -155,6 +161,14 @@ impl Context {
self.sql.check_passphrase(passphrase).await
}
/// Changes the database passphrase.
///
/// Works only for encrypted databases. Encrypted database can only be converted to unencrypted
/// one and backwards via import/export.
pub async fn change_passphrase(&self, passphrase: String) -> Result<()> {
self.sql.change_passphrase(self, passphrase).await
}
pub(crate) async fn with_blobdir(
dbfile: PathBuf,
blobdir: PathBuf,
@@ -1069,4 +1083,34 @@ mod tests {
Ok(())
}
#[async_std::test]
async fn test_change_passphrase() -> Result<()> {
let dir = tempdir()?;
let dbfile = dir.path().join("db.sqlite");
let id = 1;
let context = Context::new_closed(dbfile.clone().into(), id)
.await
.context("failed to create context")?;
assert_eq!(context.open("foo".to_string()).await?, true);
assert_eq!(context.is_open().await, true);
assert_eq!(context.is_encrypted().await, true);
context.change_passphrase("bar".to_string()).await?;
drop(context);
let id = 2;
let context = Context::new(dbfile.into(), id)
.await
.context("failed to create context")?;
assert_eq!(context.is_open().await, false);
assert_eq!(context.check_passphrase("foo".to_string()).await?, false);
assert_eq!(context.check_passphrase("bar".to_string()).await?, true);
assert_eq!(context.open("foo".to_string()).await?, false);
assert_eq!(context.open("bar".to_string()).await?, true);
assert_eq!(context.is_encrypted().await, true);
Ok(())
}
}
+7 -36
View File
@@ -4,10 +4,10 @@ pub mod send;
use std::time::{Duration, SystemTime};
use anyhow::{format_err, Context as _};
use anyhow::{bail, format_err, Context as _, Result};
use async_smtp::smtp::client::net::ClientTlsParameters;
use async_smtp::smtp::response::{Category, Code, Detail};
use async_smtp::{error, smtp, EmailAddress, ServerAddress};
use async_smtp::{smtp, EmailAddress, ServerAddress};
use crate::constants::DC_LP_AUTH_OAUTH2;
use crate::events::EventType;
@@ -23,28 +23,6 @@ use crate::{context::Context, scheduler::connectivity::ConnectivityStore};
/// SMTP write and read timeout in seconds.
const SMTP_TIMEOUT: u64 = 30;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Bad parameters")]
BadParameters,
#[error("Invalid login address {address}: {error}")]
InvalidLoginAddress {
address: String,
#[source]
error: error::Error,
},
#[error("SMTP failed to connect: {0}")]
ConnectionFailure(#[source] smtp::error::Error),
#[error("SMTP oauth2 error {address}")]
Oauth2 { address: String },
#[error("TLS error {0}")]
Tls(#[from] async_native_tls::Error),
#[error("{0}")]
Other(#[from] anyhow::Error),
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Default)]
pub(crate) struct Smtp {
transport: Option<smtp::SmtpTransport>,
@@ -135,14 +113,11 @@ impl Smtp {
}
if lp.server.is_empty() || lp.port == 0 {
return Err(Error::BadParameters);
bail!("bad connection parameters");
}
let from =
EmailAddress::new(addr.to_string()).map_err(|err| Error::InvalidLoginAddress {
address: addr.to_string(),
error: err,
})?;
let from = EmailAddress::new(addr.to_string())
.with_context(|| format!("invalid login address {}", addr))?;
self.from = Some(from);
@@ -163,9 +138,7 @@ impl Smtp {
let send_pw = &lp.password;
let access_token = dc_get_oauth2_access_token(context, addr, send_pw, false).await?;
if access_token.is_none() {
return Err(Error::Oauth2 {
address: addr.to_string(),
});
bail!("SMTP OAuth 2 error {}", addr);
}
let user = &lp.user;
(
@@ -209,9 +182,7 @@ impl Smtp {
}
let mut trans = client.into_transport();
if let Err(err) = trans.connect().await {
return Err(Error::ConnectionFailure(err));
}
trans.connect().await.context("SMTP failed to connect")?;
self.transport = Some(trans);
self.last_success = Some(SystemTime::now());
+34
View File
@@ -97,6 +97,40 @@ impl Sql {
*self.is_encrypted.read().await
}
/// Changes the database passpharse.
///
/// The database must be open and encrypted already.
pub(crate) async fn change_passphrase(
&self,
context: &Context,
passphrase: String,
) -> Result<()> {
// Take the whole pool so nobody opens another connection in parallel.
let pool = self
.pool
.write()
.await
.take()
.context("the database must be open before rekeying")?;
// Get one connection and rekey the database.
// All other connections will stop working after that.
let connection = pool
.get()
.context("failed to get connection from the pool")?;
connection
.pragma_update(None, "rekey", &passphrase)
.context("failed to set PRAGMA rekey")?;
drop(pool);
// Reopen the database with new passphrase.
self.open(context, passphrase)
.await
.context("failed to reopen the database after rekeying")?;
Ok(())
}
/// Closes all underlying Sqlite connections.
async fn close(&self) {
let _ = self.pool.write().await.take();
+4 -1
View File
@@ -139,7 +139,10 @@ impl Message {
}
Viewtype::Webxdc => {
append_text = true;
"Webxdc".to_string()
self.get_webxdc_info(context)
.await
.map(|info| info.name)
.unwrap_or_else(|_| "ErrWebxdcName".to_string())
}
Viewtype::Text | Viewtype::Unknown => {
if self.param.get_cmd() != SystemMessage::LocationOnly {
+21
View File
@@ -488,6 +488,7 @@ mod tests {
add_contact_to_chat, create_group_chat, forward_msgs, send_msg, send_text_msg, ChatId,
ProtectionStatus,
};
use crate::chatlist::Chatlist;
use crate::contact::Contact;
use crate::dc_receive_imf::dc_receive_imf;
use crate::test_utils::TestContext;
@@ -1514,4 +1515,24 @@ sth_for_the = "future""#
Ok(())
}
#[async_std::test]
async fn test_webxdc_chatlist_summary() -> Result<()> {
let t = TestContext::new_alice().await;
let chat_id = create_group_chat(&t, ProtectionStatus::Unprotected, "chat").await?;
let mut instance = create_webxdc_instance(
&t,
"with-minimal-manifest.xdc",
include_bytes!("../test-data/webxdc/with-minimal-manifest.xdc"),
)
.await?;
send_msg(&t, chat_id, &mut instance).await?;
let chatlist = Chatlist::try_load(&t, 0, None, None).await?;
assert_eq!(chatlist.len(), 1);
let summary = chatlist.get_summary(&t, 0, None).await?;
assert_eq!(summary.text, "nice app!".to_string());
Ok(())
}
}