Skip to content

Commit b94d9f9

Browse files
committed
api: Add list_transports_ex() and set_transport_unpublished() functions
1 parent 39dc495 commit b94d9f9

File tree

15 files changed

+435
-49
lines changed

15 files changed

+435
-49
lines changed

deltachat-ffi/deltachat.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6784,8 +6784,8 @@ void dc_event_unref(dc_event_t* event);
67846784
* UI should update the list.
67856785
*
67866786
* The event is emitted when the transports are modified on another device
6787-
* using the JSON-RPC calls `add_or_update_transport`, `add_transport_from_qr`, `delete_transport`
6788-
* or `set_config(configured_addr)`.
6787+
* using the JSON-RPC calls `add_or_update_transport`, `add_transport_from_qr`, `delete_transport`,
6788+
* `set_transport_unpublished` or `set_config(configured_addr)`.
67896789
*/
67906790
#define DC_EVENT_TRANSPORTS_MODIFIED 2600
67916791

deltachat-jsonrpc/src/api.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ use self::types::{
6868
},
6969
};
7070
use crate::api::types::chat_list::{get_chat_list_item_by_id, ChatListItemFetchResult};
71+
use crate::api::types::login_param::Transport;
7172
use crate::api::types::qr::{QrObject, SecurejoinSource, SecurejoinUiPath};
7273

7374
#[derive(Debug)]
@@ -528,6 +529,7 @@ impl CommandApi {
528529
/// from a server encoded in a QR code.
529530
/// - [Self::list_transports()] to get a list of all configured transports.
530531
/// - [Self::delete_transport()] to remove a transport.
532+
/// - [Self::set_transport_unpublished()] to set whether contacts see this transport.
531533
async fn add_or_update_transport(
532534
&self,
533535
account_id: u32,
@@ -553,7 +555,23 @@ impl CommandApi {
553555
/// Returns the list of all email accounts that are used as a transport in the current profile.
554556
/// Use [Self::add_or_update_transport()] to add or change a transport
555557
/// and [Self::delete_transport()] to delete a transport.
558+
/// Use [Self::list_transports_ex()] to additionally query
559+
/// whether the transports are marked as 'unpublished'.
556560
async fn list_transports(&self, account_id: u32) -> Result<Vec<EnteredLoginParam>> {
561+
let ctx = self.get_context(account_id).await?;
562+
let res = ctx
563+
.list_transports()
564+
.await?
565+
.into_iter()
566+
.map(|t| t.param.into())
567+
.collect();
568+
Ok(res)
569+
}
570+
571+
/// Returns the list of all email accounts that are used as a transport in the current profile.
572+
/// Use [Self::add_or_update_transport()] to add or change a transport
573+
/// and [Self::delete_transport()] to delete a transport.
574+
async fn list_transports_ex(&self, account_id: u32) -> Result<Vec<Transport>> {
557575
let ctx = self.get_context(account_id).await?;
558576
let res = ctx
559577
.list_transports()
@@ -571,6 +589,26 @@ impl CommandApi {
571589
ctx.delete_transport(&addr).await
572590
}
573591

592+
/// Change whether the transport is unpublished.
593+
///
594+
/// Unpublished transports are not advertised to contacts,
595+
/// and self-sent messages are not sent there,
596+
/// so that we don't cause extra messages to the corresponding inbox,
597+
/// but can still receive messages from contacts who don't know the new relay addresses yet.
598+
///
599+
/// The default is true, but when updating,
600+
/// existing secondary transports are set to unpublished,
601+
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
602+
async fn set_transport_unpublished(
603+
&self,
604+
account_id: u32,
605+
addr: String,
606+
unpublished: bool,
607+
) -> Result<()> {
608+
let ctx = self.get_context(account_id).await?;
609+
ctx.set_transport_unpublished(&addr, unpublished).await
610+
}
611+
574612
/// Signal an ongoing process to stop.
575613
async fn stop_ongoing_process(&self, account_id: u32) -> Result<()> {
576614
let ctx = self.get_context(account_id).await?;

deltachat-jsonrpc/src/api/types/login_param.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ use serde::Deserialize;
44
use serde::Serialize;
55
use yerpc::TypeDef;
66

7+
#[derive(Serialize, TypeDef, schemars::JsonSchema)]
8+
#[serde(rename_all = "camelCase")]
9+
pub struct Transport {
10+
/// The login data entered by the user.
11+
pub param: EnteredLoginParam,
12+
/// Whether this transport is set to 'unpublished'.
13+
/// See `set_transport_unpublished` / `setTransportUnpublished` for details.
14+
pub is_unpublished: bool,
15+
}
16+
717
/// Login parameters entered by the user.
818
///
919
/// Usually it will be enough to only set `addr` and `password`,
@@ -56,6 +66,15 @@ pub struct EnteredLoginParam {
5666
pub oauth2: Option<bool>,
5767
}
5868

69+
impl From<dc::Transport> for Transport {
70+
fn from(transport: dc::Transport) -> Self {
71+
Transport {
72+
param: transport.param.into(),
73+
is_unpublished: transport.is_unpublished,
74+
}
75+
}
76+
}
77+
5978
impl From<dc::EnteredLoginParam> for EnteredLoginParam {
6079
fn from(param: dc::EnteredLoginParam) -> Self {
6180
let imap_security: Socket = param.imap.security.into();

src/chat.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2737,7 +2737,6 @@ async fn prepare_send_msg(
27372737
chat_id.unarchive_if_not_muted(context, msg.state).await?;
27382738
}
27392739
chat.prepare_msg_raw(context, msg, update_msg_id).await?;
2740-
27412740
let row_ids = create_send_msg_jobs(context, msg)
27422741
.await
27432742
.context("Failed to create send jobs")?;
@@ -2844,19 +2843,12 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
28442843
let lowercase_from = from.to_lowercase();
28452844

28462845
recipients.retain(|x| x.to_lowercase() != lowercase_from);
2847-
if context.get_config_bool(Config::BccSelf).await?
2848-
|| msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage
2849-
{
2850-
smtp::add_self_recipients(context, &mut recipients, needs_encryption).await?;
2851-
}
2852-
2853-
// Default Webxdc integrations are hidden messages and must not be sent out
2854-
if msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden {
2855-
recipients.clear();
2856-
}
28572846

2858-
if recipients.is_empty() {
2859-
// may happen eg. for groups with only SELF and bcc_self disabled
2847+
// Default Webxdc integrations are hidden messages and must not be sent out:
2848+
if (msg.param.get_int(Param::WebxdcIntegration).is_some() && msg.hidden)
2849+
// This may happen eg. for groups with only SELF and bcc_self disabled:
2850+
|| (!context.get_config_bool(Config::BccSelf).await? && recipients.is_empty())
2851+
{
28602852
info!(
28612853
context,
28622854
"Message {} has no recipient, skipping smtp-send.", msg.id
@@ -2895,6 +2887,12 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
28952887
);
28962888
}
28972889

2890+
if context.get_config_bool(Config::BccSelf).await?
2891+
|| msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage
2892+
{
2893+
smtp::add_self_recipients(context, &mut recipients, rendered_msg.is_encrypted).await?;
2894+
}
2895+
28982896
if needs_encryption && !rendered_msg.is_encrypted {
28992897
/* unrecoverable */
29002898
message::set_msg_failed(

src/config.rs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -837,7 +837,7 @@ impl Context {
837837
// which only fetches from the primary transport.
838838
transaction
839839
.execute(
840-
"UPDATE transports SET add_timestamp=? WHERE addr=?",
840+
"UPDATE transports SET add_timestamp=?, is_published=1 WHERE addr=?",
841841
(time(), addr),
842842
)
843843
.context(
@@ -974,6 +974,21 @@ impl Context {
974974
.await
975975
}
976976

977+
/// Returns all published self addresses, newest first.
978+
/// See `[Context::set_transport_unpublished]`
979+
pub(crate) async fn get_published_self_addrs(&self) -> Result<Vec<String>> {
980+
self.sql
981+
.query_map_vec(
982+
"SELECT addr FROM transports WHERE is_published=1 ORDER BY add_timestamp DESC",
983+
(),
984+
|row| {
985+
let addr: String = row.get(0)?;
986+
Ok(addr)
987+
},
988+
)
989+
.await
990+
}
991+
977992
/// Returns all secondary self addresses.
978993
pub(crate) async fn get_secondary_self_addrs(&self) -> Result<Vec<String>> {
979994
self.sql.query_map_vec("SELECT addr FROM transports WHERE addr NOT IN (SELECT value FROM config WHERE keyname='configured_addr')", (), |row| {
@@ -982,6 +997,23 @@ impl Context {
982997
}).await
983998
}
984999

1000+
/// Returns all published secondary self addresses.
1001+
/// See `[Context::set_transport_unpublished]`
1002+
pub(crate) async fn get_published_secondary_self_addrs(&self) -> Result<Vec<String>> {
1003+
self.sql
1004+
.query_map_vec(
1005+
"SELECT addr FROM transports
1006+
WHERE is_published=1
1007+
AND addr NOT IN (SELECT value FROM config WHERE keyname='configured_addr')",
1008+
(),
1009+
|row| {
1010+
let addr: String = row.get(0)?;
1011+
Ok(addr)
1012+
},
1013+
)
1014+
.await
1015+
}
1016+
9851017
/// Returns the primary self address.
9861018
/// Returns an error if no self addr is configured.
9871019
pub async fn get_primary_self_addr(&self) -> Result<String> {

src/configure.rs

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ use crate::constants::NON_ALPHANUMERIC_WITHOUT_DOT;
2828
use crate::context::Context;
2929
use crate::imap::Imap;
3030
use crate::log::warn;
31-
use crate::login_param::EnteredCertificateChecks;
3231
pub use crate::login_param::EnteredLoginParam;
32+
use crate::login_param::{EnteredCertificateChecks, Transport};
3333
use crate::message::Message;
3434
use crate::net::proxy::ProxyConfig;
3535
use crate::oauth2::get_oauth2_addr;
@@ -110,6 +110,7 @@ impl Context {
110110
/// from a server encoded in a QR code.
111111
/// - [Self::list_transports()] to get a list of all configured transports.
112112
/// - [Self::delete_transport()] to remove a transport.
113+
/// - [Self::set_transport_unpublished()] to set whether contacts see this transport.
113114
pub async fn add_or_update_transport(&self, param: &mut EnteredLoginParam) -> Result<()> {
114115
self.stop_io().await;
115116
let result = self.add_transport_inner(param).await;
@@ -188,14 +189,22 @@ impl Context {
188189
/// Returns the list of all email accounts that are used as a transport in the current profile.
189190
/// Use [Self::add_or_update_transport()] to add or change a transport
190191
/// and [Self::delete_transport()] to delete a transport.
191-
pub async fn list_transports(&self) -> Result<Vec<EnteredLoginParam>> {
192+
pub async fn list_transports(&self) -> Result<Vec<Transport>> {
192193
let transports = self
193194
.sql
194-
.query_map_vec("SELECT entered_param FROM transports", (), |row| {
195-
let entered_param: String = row.get(0)?;
196-
let transport: EnteredLoginParam = serde_json::from_str(&entered_param)?;
197-
Ok(transport)
198-
})
195+
.query_map_vec(
196+
"SELECT entered_param, is_published FROM transports",
197+
(),
198+
|row| {
199+
let param: String = row.get(0)?;
200+
let param: EnteredLoginParam = serde_json::from_str(&param)?;
201+
let is_published: bool = row.get(1)?;
202+
Ok(Transport {
203+
param,
204+
is_unpublished: !is_published,
205+
})
206+
},
207+
)
199208
.await?;
200209

201210
Ok(transports)
@@ -261,6 +270,40 @@ impl Context {
261270
Ok(())
262271
}
263272

273+
/// Change whether the transport is unpublished.
274+
///
275+
/// Unpublished transports are not advertised to contacts,
276+
/// and self-sent messages are not sent there,
277+
/// so that we don't cause extra messages to the corresponding inbox,
278+
/// but can still receive messages from contacts who don't know the new relay addresses yet.
279+
///
280+
/// The default is true, but when updating,
281+
/// existing secondary transports are set to unpublished,
282+
/// so that an existing transport address doesn't suddenly get spammed with a lot of messages.
283+
pub async fn set_transport_unpublished(&self, addr: &str, unpublished: bool) -> Result<()> {
284+
// We need to update the timestamp so that the key's timestamp changes
285+
// and is recognized as newer by our peers
286+
self.sql
287+
.transaction(|trans| {
288+
let primary_addr: String = trans.query_row(
289+
"SELECT value FROM config WHERE keyname='configured_addr'",
290+
(),
291+
|row| row.get(0),
292+
)?;
293+
if primary_addr == addr && unpublished {
294+
bail!("Can't set primary relay as unpublished");
295+
}
296+
trans.execute(
297+
"UPDATE transports SET is_published=?, add_timestamp=? WHERE addr=?",
298+
(!unpublished, time(), addr),
299+
)?;
300+
Ok(())
301+
})
302+
.await?;
303+
send_sync_transports(self).await?;
304+
Ok(())
305+
}
306+
264307
async fn inner_configure(&self, param: &EnteredLoginParam) -> Result<()> {
265308
info!(self, "Configure ...");
266309

src/key.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ pub(crate) async fn load_self_public_key_opt(context: &Context) -> Result<Option
296296
.await?
297297
.context("No transports configured")?;
298298
let addr = context.get_primary_self_addr().await?;
299-
let all_addrs = context.get_all_self_addrs().await?.join(",");
299+
let all_addrs = context.get_published_self_addrs().await?.join(",");
300300
let signed_public_key =
301301
secret_key_to_public_key(context, signed_secret_key, timestamp, &addr, &all_addrs)?;
302302
*lock = Some(signed_public_key.clone());

src/login_param.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ pub struct EnteredServerLoginParam {
7979
pub password: String,
8080
}
8181

82+
/// A transport, as shown in the "relays" list in the UI.
83+
#[derive(Debug)]
84+
pub struct Transport {
85+
/// The login data entered by the user.
86+
pub param: EnteredLoginParam,
87+
/// Whether this transport is set to 'unpublished'.
88+
/// See [`Context::set_transport_unpublished`] for details.
89+
pub is_unpublished: bool,
90+
}
91+
8292
/// Login parameters entered by the user.
8393
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
8494
pub struct EnteredLoginParam {

src/mimeparser/shared_secret_decryption_tests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ async fn test_qr_code_security() -> Result<()> {
204204
let charlie_addr = charlie.get_config(Config::Addr).await?.unwrap();
205205

206206
let alice_fp = self_fingerprint(alice).await?;
207-
let secret_for_encryption = dbg!(format!("securejoin/{alice_fp}/{authcode}"));
207+
let secret_for_encryption = format!("securejoin/{alice_fp}/{authcode}");
208208
test_shared_secret_decryption_ex(
209209
bob,
210210
&charlie_addr,

src/smtp.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -701,12 +701,12 @@ pub(crate) async fn add_self_recipients(
701701
// and connection is frequently lost
702702
// before receiving status line. NB: This is not a problem for chatmail servers, so `BccSelf`
703703
// disabled by default is fine.
704-
if context.get_config_delete_server_after().await? != Some(0) || !recipients.is_empty() {
704+
if (context.get_config_delete_server_after().await? != Some(0)) || !recipients.is_empty() {
705705
// Avoid sending unencrypted messages to all transports, chatmail relays won't accept
706706
// them. Normally the user should have a non-chatmail primary transport to send unencrypted
707707
// messages.
708708
if encrypted {
709-
for addr in context.get_secondary_self_addrs().await? {
709+
for addr in context.get_published_secondary_self_addrs().await? {
710710
recipients.push(addr);
711711
}
712712
}

0 commit comments

Comments
 (0)