diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e9eb28bdb610c5be4c278f26231eca5a45b28ea..47cf9350fe02e027444399fd13d6edc6d0fbdaf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### Version 2.13.1 + +* Support P2P file transfer via WebRTC data channels +* Fix interoperability issues with Bind 2.0 on ejabberd +* Bundle Let’s Encrypt root certificates for Android <= 7 + ### Version 2.13.0 * Easier access to 'Show QR code' diff --git a/build.gradle b/build.gradle index 2dbeb77d2efd4730dfec3d84c4c2fe6f4ee9e84f..9d6d88b024c7e5583525a256f0c9c154866713dd 100644 --- a/build.gradle +++ b/build.gradle @@ -45,11 +45,11 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' implementation "androidx.core:core:1.10.1" - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.3' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' implementation 'androidx.viewpager:viewpager:1.0.0' - playstoreImplementation('com.google.firebase:firebase-messaging:23.3.1') { + playstoreImplementation('com.google.firebase:firebase-messaging:23.4.0') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' @@ -61,10 +61,10 @@ dependencies { implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1' implementation("com.github.CanHub:Android-Image-Cropper:2.0.0") implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'androidx.exifinterface:exifinterface:1.3.6' + implementation 'androidx.exifinterface:exifinterface:1.3.7' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' - implementation 'com.google.android.material:material:1.10.0' + implementation 'com.google.android.material:material:1.11.0' implementation "androidx.emoji2:emoji2:1.4.0" freeImplementation "androidx.emoji2:emoji2-bundled:1.4.0" @@ -93,7 +93,7 @@ dependencies { implementation "com.squareup.retrofit2:retrofit:2.9.0" implementation "com.squareup.retrofit2:converter-gson:2.9.0" - implementation "com.squareup.okhttp3:okhttp:4.11.0" + implementation "com.squareup.okhttp3:okhttp:4.12.0" implementation 'com.google.guava:guava:32.1.3-android' implementation 'io.michaelrocks:libphonenumber-android:8.13.17' diff --git a/fastlane/metadata/android/de-DE/changelogs/4208104.txt b/fastlane/metadata/android/de-DE/changelogs/4208104.txt new file mode 100644 index 0000000000000000000000000000000000000000..9f9787c70cfc14d8696951d789936b26f0813c0e --- /dev/null +++ b/fastlane/metadata/android/de-DE/changelogs/4208104.txt @@ -0,0 +1,4 @@ +* Leichterer Zugriff auf 'QR-Code anzeigen' +* Unterstützung von PEP Native Bookmarks +* Unterstützung für SDP Offer / Answer-Model (wird von SIP Gateways verwendet) +* Anhebung der Ziel-API auf Android 14 diff --git a/fastlane/metadata/android/en-US/changelogs/4208304.txt b/fastlane/metadata/android/en-US/changelogs/4208304.txt new file mode 100644 index 0000000000000000000000000000000000000000..b6a7bb98f96ad9753ab3b2c52466098d90e0332b --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/4208304.txt @@ -0,0 +1,3 @@ +* Support P2P file transfer via WebRTC data channels +* Fix interoperability issues with Bind 2.0 on ejabberd +* Bundle Let’s Encrypt root certificates for Android <= 7 diff --git a/src/conversations/res/values-fa-rIR/strings.xml b/src/conversations/res/values-fa-rIR/strings.xml index 0f33625067a7892b02d89ed033b67187bc99069c..dcf72204c06e73fa6560c1b1f13ced5dfd78f2d8 100644 --- a/src/conversations/res/values-fa-rIR/strings.xml +++ b/src/conversations/res/values-fa-rIR/strings.xml @@ -3,4 +3,18 @@ لطفا سرویس دهنده پیام خود را انتخاب نمائید. برای مثال artalk.im از Conversations.im استفاده کنید حساب کاربری جدیدی بسازید - \ No newline at end of file + عضو %1$s شو و با من گفتگو کن: %2$s + شما به %1$s دعوت شده‌اید. ما شما را در ساخت حسابتان راهنمایی می‌کنیم. +\nوقتی شما %1$s را به عنوان سرویس‌دهندهٔ خود برگزینید، خواهید توانست با کاربران بقیهٔ سرویس‌دهنده‌ها نیز ارتباط داشته باشید. کافی است نشانی کامل XMPP خود را به آن‌ها بدهید. + آیا خودتان حساب XMPP دارید؟ اگر جایی دیگری با همین برنامه یا برنامه‌های مشابه کار می‌کنید باید داشته باشید. اگر حساب XMPP ندارید، همین‌جا می‌توانید یکی بسازید. +\nنکته: برخی از سرویس‌دهنده‌های ایمیل به شما حساب XMPP هم ارائه می‌دهند. + اگر مخاطب شما نزدیکتان است، می‌تواند برای پذیرش دعوت‌نامه کد زیر را اسکن کند. + هم‌رسانی دعوت‌نامه با… + شبکهٔ XMPP مستقل از سرویس‌دهنده‌هایش است. بنابراین شما می‌توانید این برنامه را با هر سرویس‌دهنده‌ای که می‌خواهید به کار ببرید. +\nولی برای راحتی شما، ما امکان ساخت حساب روی سرور conversations.im را می‌دهیم؛ سرویس‌دهنده‌ای که برای کار با این برنامه بهینه شده است. + شما به %1$s دعوت شده‌اید و یک نام کاربری برایتان برگزیده شده است. ما شما را در ساخت حسابتان راهنمایی می‌کنیم. +\nشما خواهید توانست با کاربران بقیهٔ سرویس‌دهنده‌ها نیز ارتباط داشته باشید. کافی است نشانی کامل XMPP خود را به آن‌ها بدهید. + کد مورد نظر اشتباه ساخته شده است + روی گزینهٔ هم‌رسانی بزنید تا مخاطب خود را به %1$s دعوت کنید. + دعوت‌نامهٔ سرور شما + \ No newline at end of file diff --git a/src/conversations/res/values-nl/strings.xml b/src/conversations/res/values-nl/strings.xml index f04a6b2de854536f5d485592953ec8a7f36ca073..bc7dbc2fab5aad37288b62e1e974e9e576de3d2b 100644 --- a/src/conversations/res/values-nl/strings.xml +++ b/src/conversations/res/values-nl/strings.xml @@ -11,4 +11,5 @@ Tik op de delen knop om een uitnodiging te versturen naar %1$s Als je contactpersoon in de buurt is, kan deze ook onderstaande code scannen om de uitnodiging te aanvaarden. Deel de uitnodiging met ... + Vergezel %1$s en chat met mij: %2$s \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/crypto/BundledTrustManager.java b/src/main/java/eu/siacs/conversations/crypto/BundledTrustManager.java new file mode 100644 index 0000000000000000000000000000000000000000..9eb20cc300f412914dc9b8a2b97ecc72b92ce143 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/BundledTrustManager.java @@ -0,0 +1,65 @@ +package eu.siacs.conversations.crypto; + +import java.io.IOException; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509TrustManager; + +public class BundledTrustManager implements X509TrustManager { + + private final X509TrustManager delegate; + + private BundledTrustManager(final KeyStore keyStore) + throws NoSuchAlgorithmException, KeyStoreException { + this.delegate = TrustManagers.createTrustManager(keyStore); + } + + public static Builder builder() throws KeyStoreException { + return new Builder(); + } + + @Override + public void checkClientTrusted(final X509Certificate[] chain, final String authType) + throws CertificateException { + this.delegate.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, final String authType) + throws CertificateException { + this.delegate.checkServerTrusted(chain, authType); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return this.delegate.getAcceptedIssuers(); + } + + public static class Builder { + + private KeyStore keyStore; + + private Builder() {} + + public Builder loadKeyStore(final InputStream inputStream, final String password) + throws CertificateException, IOException, NoSuchAlgorithmException, + KeyStoreException { + if (this.keyStore != null) { + throw new IllegalStateException("KeyStore has already been loaded"); + } + final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(inputStream, password.toCharArray()); + this.keyStore = keyStore; + return this; + } + + public BundledTrustManager build() throws NoSuchAlgorithmException, KeyStoreException { + return new BundledTrustManager(keyStore); + } + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/CombiningTrustManager.java b/src/main/java/eu/siacs/conversations/crypto/CombiningTrustManager.java new file mode 100644 index 0000000000000000000000000000000000000000..0f3c0e04415e506748908ad818613064bc3cbc50 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/CombiningTrustManager.java @@ -0,0 +1,96 @@ +package eu.siacs.conversations.crypto; + +import android.util.Log; + +import com.google.common.collect.ImmutableList; + +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import javax.net.ssl.X509TrustManager; + +import eu.siacs.conversations.Config; + +public class CombiningTrustManager implements X509TrustManager { + + private final List trustManagers; + + private CombiningTrustManager(final List trustManagers) { + this.trustManagers = trustManagers; + } + + @Override + public void checkClientTrusted(final X509Certificate[] chain, final String authType) + throws CertificateException { + for (final Iterator iterator = this.trustManagers.iterator(); + iterator.hasNext(); ) { + final X509TrustManager trustManager = iterator.next(); + try { + trustManager.checkClientTrusted(chain, authType); + } catch (final CertificateException certificateException) { + if (iterator.hasNext()) { + continue; + } + throw certificateException; + } + } + } + + @Override + public void checkServerTrusted(final X509Certificate[] chain, final String authType) + throws CertificateException { + Log.d( + Config.LOGTAG, + CombiningTrustManager.class.getSimpleName() + + " is configured with " + + this.trustManagers.size() + + " TrustManagers"); + int i = 0; + for (final Iterator iterator = this.trustManagers.iterator(); + iterator.hasNext(); ) { + final X509TrustManager trustManager = iterator.next(); + try { + trustManager.checkServerTrusted(chain, authType); + Log.d( + Config.LOGTAG, + "certificate check passed on " + trustManager.getClass().getName()+". chain length was "+chain.length); + return; + } catch (final CertificateException certificateException) { + Log.d( + Config.LOGTAG, + "failed to verify in [" + i + "]/" + trustManager.getClass().getName(), + certificateException); + if (iterator.hasNext()) { + continue; + } + throw certificateException; + } finally { + ++i; + } + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + final ImmutableList.Builder certificates = ImmutableList.builder(); + for (final X509TrustManager trustManager : this.trustManagers) { + for (final X509Certificate certificate : trustManager.getAcceptedIssuers()) { + certificates.add(certificate); + } + } + return certificates.build().toArray(new X509Certificate[0]); + } + + public static X509TrustManager combineWithDefault(final X509TrustManager... trustManagers) + throws NoSuchAlgorithmException, KeyStoreException { + final ImmutableList.Builder builder = ImmutableList.builder(); + builder.addAll(Arrays.asList(trustManagers)); + builder.add(TrustManagers.createDefaultTrustManager()); + return new CombiningTrustManager(builder.build()); + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/TrustManagers.java b/src/main/java/eu/siacs/conversations/crypto/TrustManagers.java new file mode 100644 index 0000000000000000000000000000000000000000..11fe182dde8449364c371b5005933f6a8e4d0d33 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/TrustManagers.java @@ -0,0 +1,38 @@ +package eu.siacs.conversations.crypto; + +import androidx.annotation.Nullable; + +import com.google.common.collect.Iterables; + +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +public final class TrustManagers { + + private TrustManagers() { + throw new IllegalStateException("Do not instantiate me"); + } + + public static X509TrustManager createTrustManager(@Nullable final KeyStore keyStore) + throws NoSuchAlgorithmException, KeyStoreException { + final TrustManagerFactory trustManagerFactory = + TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(keyStore); + return Iterables.getOnlyElement( + Iterables.filter( + Arrays.asList(trustManagerFactory.getTrustManagers()), + X509TrustManager.class)); + } + + public static X509TrustManager createDefaultTrustManager() + throws NoSuchAlgorithmException, KeyStoreException { + return createTrustManager(null); + } + + +} diff --git a/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java b/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java index b58f02cdfd16d5c2b53cdabc92dc6eccadc8aa63..7424de53dd59a25fa907e781f65bc2f51180aeb0 100644 --- a/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java +++ b/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java @@ -33,6 +33,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.preference.PreferenceManager; import android.util.Base64; @@ -44,9 +45,21 @@ import androidx.core.util.Consumer; import com.google.common.base.Charsets; import com.google.common.base.Joiner; +import com.google.common.base.Preconditions; import com.google.common.io.ByteStreams; import com.google.common.io.CharStreams; +import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.crypto.BundledTrustManager; +import eu.siacs.conversations.crypto.CombiningTrustManager; +import eu.siacs.conversations.crypto.TrustManagers; +import eu.siacs.conversations.crypto.XmppDomainVerifier; +import eu.siacs.conversations.entities.MTMDecision; +import eu.siacs.conversations.http.HttpConnectionManager; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.ui.MemorizingActivity; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -81,39 +94,40 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -import eu.siacs.conversations.Config; -import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.XmppDomainVerifier; -import eu.siacs.conversations.entities.MTMDecision; -import eu.siacs.conversations.http.HttpConnectionManager; -import eu.siacs.conversations.persistance.FileBackend; -import eu.siacs.conversations.ui.MemorizingActivity; - /** - * A X509 trust manager implementation which asks the user about invalid - * certificates and memorizes their decision. - *

- * The certificate validity is checked using the system default X509 - * TrustManager, creating a query Dialog if the check fails. - *

- * WARNING: This only works if a dedicated thread is used for - * opening sockets! + * A X509 trust manager implementation which asks the user about invalid certificates and memorizes + * their decision. + * + *

The certificate validity is checked using the system default X509 TrustManager, creating a + * query Dialog if the check fails. + * + *

WARNING: This only works if a dedicated thread is used for opening sockets! */ public class MemorizingTrustManager { - private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US); - - final static String DECISION_INTENT = "de.duenndns.ssl.DECISION"; - public final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId"; - public final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert"; - public final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId"; - final static String NO_TRUST_ANCHOR = "Trust anchor for certification path not found."; - private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); - private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); - private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); - private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName()); + private static final SimpleDateFormat DATE_FORMAT = + new SimpleDateFormat("yyyy-MM-dd", Locale.US); + + static final String DECISION_INTENT = "de.duenndns.ssl.DECISION"; + public static final String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId"; + public static final String DECISION_INTENT_CERT = DECISION_INTENT + ".cert"; + public static final String DECISION_TITLE_ID = DECISION_INTENT + ".titleId"; + static final String NO_TRUST_ANCHOR = "Trust anchor for certification path not found."; + private static final Pattern PATTERN_IPV4 = + Pattern.compile( + "\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = + Pattern.compile( + "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_6HEX4DEC = + Pattern.compile( + "\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z"); + private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = + Pattern.compile( + "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z"); + private static final Pattern PATTERN_IPV6 = + Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z"); + private static final Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName()); static String KEYSTORE_DIR = "KeyStore"; static String KEYSTORE_FILE = "KeyStore.bks"; private static int decisionId = 0; @@ -129,21 +143,22 @@ public class MemorizingTrustManager { private final DaneVerifier daneVerifier; /** - * Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager. - *

- * You need to supply the application context. This has to be one of: - * - Application - * - Activity - * - Service - *

- * The context is used for file management, to display the dialog / - * notification and for obtaining translated strings. + * Creates an instance of the MemorizingTrustManager class that falls back to a custom + * TrustManager. + * + *

You need to supply the application context. This has to be one of: - Application - + * Activity - Service * - * @param m Context for the application. - * @param defaultTrustManager Delegate trust management to this TM. If null, the user must accept every certificate. + *

The context is used for file management, to display the dialog / notification and for + * obtaining translated strings. + * + * @param context Context for the application. + * @param defaultTrustManager Delegate trust management to this TM. If null, the user must + * accept every certificate. */ - public MemorizingTrustManager(Context m, X509TrustManager defaultTrustManager) { - init(m); + public MemorizingTrustManager( + final Context context, final X509TrustManager defaultTrustManager) { + init(context); this.appTrustManager = getTrustManager(appKeyStore); this.defaultTrustManager = defaultTrustManager; this.daneVerifier = new DaneVerifier(); @@ -151,34 +166,55 @@ public class MemorizingTrustManager { /** * Creates an instance of the MemorizingTrustManager class using the system X509TrustManager. - *

- * You need to supply the application context. This has to be one of: - * - Application - * - Activity - * - Service - *

- * The context is used for file management, to display the dialog / - * notification and for obtaining translated strings. * - * @param m Context for the application. + *

You need to supply the application context. This has to be one of: - Application - + * Activity - Service + * + *

The context is used for file management, to display the dialog / notification and for + * obtaining translated strings. + * + * @param context Context for the application. */ - public MemorizingTrustManager(Context m) { - init(m); + public MemorizingTrustManager(final Context context) { + init(context); this.appTrustManager = getTrustManager(appKeyStore); - this.defaultTrustManager = getTrustManager(null); this.daneVerifier = new DaneVerifier(); + try { + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) { + this.defaultTrustManager = defaultWithBundledLetsEncrypt(context); + } else { + this.defaultTrustManager = TrustManagers.createDefaultTrustManager(); + } + } catch (final NoSuchAlgorithmException + | KeyStoreException + | CertificateException + | IOException e) { + throw new RuntimeException(e); + } + } + + private static X509TrustManager defaultWithBundledLetsEncrypt(final Context context) + throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { + final BundledTrustManager bundleTrustManager = + BundledTrustManager.builder() + .loadKeyStore( + context.getResources().openRawResource(R.raw.letsencrypt), + "letsencrypt") + .build(); + return CombiningTrustManager.combineWithDefault(bundleTrustManager); } private static boolean isIp(final String server) { - return server != null && ( - PATTERN_IPV4.matcher(server).matches() + return server != null + && (PATTERN_IPV4.matcher(server).matches() || PATTERN_IPV6.matcher(server).matches() || PATTERN_IPV6_6HEX4DEC.matcher(server).matches() || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches() || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches()); } - private static String getBase64Hash(X509Certificate certificate, String digest) throws CertificateEncodingException { + private static String getBase64Hash(X509Certificate certificate, String digest) + throws CertificateEncodingException { MessageDigest md; try { md = MessageDigest.getInstance(digest); @@ -193,8 +229,7 @@ public class MemorizingTrustManager { StringBuffer si = new StringBuffer(); for (int i = 0; i < data.length; i++) { si.append(String.format("%02x", data[i])); - if (i < data.length - 1) - si.append(":"); + if (i < data.length - 1) si.append(":"); } return si.toString(); } @@ -225,20 +260,22 @@ public class MemorizingTrustManager { } } - void init(final Context m) { - master = m; - masterHandler = new Handler(m.getMainLooper()); - notificationManager = (NotificationManager) master.getSystemService(Context.NOTIFICATION_SERVICE); + void init(final Context context) { + master = context; + masterHandler = new Handler(context.getMainLooper()); + notificationManager = + (NotificationManager) master.getSystemService(Context.NOTIFICATION_SERVICE); Application app; - if (m instanceof Application) { - app = (Application) m; - } else if (m instanceof Service) { - app = ((Service) m).getApplication(); - } else if (m instanceof AppCompatActivity) { - app = ((AppCompatActivity) m).getApplication(); + if (context instanceof Application) { + app = (Application) context; + } else if (context instanceof Service) { + app = ((Service) context).getApplication(); + } else if (context instanceof AppCompatActivity) { + app = ((AppCompatActivity) context).getApplication(); } else - throw new ClassCastException("MemorizingTrustManager context must be either Activity or Service!"); + throw new ClassCastException( + "MemorizingTrustManager context must be either Activity or Service!"); File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE); keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE); @@ -263,12 +300,9 @@ public class MemorizingTrustManager { /** * Removes the given certificate from MTMs key store. * - *

- * WARNING: this does not immediately invalidate the certificate. It is - * well possible that (a) data is transmitted over still existing connections or - * (b) new connections are created using TLS renegotiation, without a new cert - * check. - *

+ *

WARNING: this does not immediately invalidate the certificate. It is well possible + * that (a) data is transmitted over still existing connections or (b) new connections are + * created using TLS renegotiation, without a new cert check. * * @param alias the certificate's alias as returned by {@link #getCertificates()}. * @throws KeyStoreException if the certificate could not be deleted. @@ -278,20 +312,21 @@ public class MemorizingTrustManager { keyStoreUpdated(); } - X509TrustManager getTrustManager(KeyStore ks) { + private X509TrustManager getTrustManager(final KeyStore keyStore) { + Preconditions.checkNotNull(keyStore); try { TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); - tmf.init(ks); + tmf.init(keyStore); for (TrustManager t : tmf.getTrustManagers()) { if (t instanceof X509TrustManager) { return (X509TrustManager) t; } } - } catch (Exception e) { + } catch (final Exception e) { // Here, we are covering up errors. It might be more useful // however to throw them out of the constructor so the // embedding app knows something went wrong. - LOGGER.log(Level.SEVERE, "getTrustManager(" + ks + ")", e); + LOGGER.log(Level.SEVERE, "getTrustManager(" + keyStore + ")", e); } return null; } @@ -364,10 +399,18 @@ public class MemorizingTrustManager { } } - - private void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive, String verifiedHostname, int port, Consumer daneCb) + private void checkCertTrusted( + X509Certificate[] chain, + String authType, + String domain, + boolean isServer, + boolean interactive, + String verifiedHostname, + int port, + Consumer daneCb) throws CertificateException { - LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")"); + LOGGER.log( + Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")"); try { LOGGER.log(Level.FINE, "checkCertTrusted: trying appTrustManager"); if (isServer) { @@ -390,17 +433,15 @@ public class MemorizingTrustManager { } catch (final CertificateException ae) { LOGGER.log(Level.FINER, "checkCertTrusted: appTrustManager failed", ae); if (isCertKnown(chain[0])) { - LOGGER.log(Level.INFO, "checkCertTrusted: accepting cert already stored in keystore"); + LOGGER.log( + Level.INFO, "checkCertTrusted: accepting cert already stored in keystore"); return; } try { - if (defaultTrustManager == null) - throw ae; + if (defaultTrustManager == null) throw ae; LOGGER.log(Level.FINE, "checkCertTrusted: trying defaultTrustManager"); - if (isServer) - defaultTrustManager.checkServerTrusted(chain, authType); - else - defaultTrustManager.checkClientTrusted(chain, authType); + if (isServer) defaultTrustManager.checkServerTrusted(chain, authType); + else defaultTrustManager.checkClientTrusted(chain, authType); } catch (final CertificateException e) { if (interactive) { interactCert(chain, authType, e); @@ -412,7 +453,9 @@ public class MemorizingTrustManager { } private X509Certificate[] getAcceptedIssuers() { - return defaultTrustManager == null ? new X509Certificate[0] : defaultTrustManager.getAcceptedIssuers(); + return defaultTrustManager == null + ? new X509Certificate[0] + : defaultTrustManager.getAcceptedIssuers(); } private int createDecisionId(MTMDecision d) { @@ -425,7 +468,8 @@ public class MemorizingTrustManager { return myId; } - private void certDetails(final StringBuffer si, final X509Certificate c, final boolean showValidFor) { + private void certDetails( + final StringBuffer si, final X509Certificate c, final boolean showValidFor) { si.append("\n"); if (showValidFor) { @@ -462,8 +506,7 @@ public class MemorizingTrustManager { // not found", so we use string comparison. if (NO_TRUST_ANCHOR.equals(e.getMessage())) { si.append(master.getString(R.string.mtm_trust_anchor)); - } else - si.append(e.getLocalizedMessage()); + } else si.append(e.getLocalizedMessage()); si.append("\n"); } si.append("\n"); @@ -471,7 +514,7 @@ public class MemorizingTrustManager { si.append("\n\n"); si.append(master.getString(R.string.mtm_cert_details)); si.append('\n'); - for(int i = 0; i < chain.length; ++i) { + for (int i = 0; i < chain.length; ++i) { certDetails(si, chain[i], i == 0); } return si.toString(); @@ -491,24 +534,25 @@ public class MemorizingTrustManager { MTMDecision choice = new MTMDecision(); final int myId = createDecisionId(choice); - masterHandler.post(new Runnable() { - public void run() { - Intent ni = new Intent(master, MemorizingActivity.class); - ni.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - ni.setData(Uri.parse(MemorizingTrustManager.class.getName() + "/" + myId)); - ni.putExtra(DECISION_INTENT_ID, myId); - ni.putExtra(DECISION_INTENT_CERT, message); - ni.putExtra(DECISION_TITLE_ID, titleId); - - // we try to directly start the activity and fall back to - // making a notification - try { - getUI().startActivity(ni); - } catch (Exception e) { - LOGGER.log(Level.FINE, "startActivity(MemorizingActivity)", e); - } - } - }); + masterHandler.post( + new Runnable() { + public void run() { + Intent ni = new Intent(master, MemorizingActivity.class); + ni.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + ni.setData(Uri.parse(MemorizingTrustManager.class.getName() + "/" + myId)); + ni.putExtra(DECISION_INTENT_ID, myId); + ni.putExtra(DECISION_INTENT_CERT, message); + ni.putExtra(DECISION_TITLE_ID, titleId); + + // we try to directly start the activity and fall back to + // making a notification + try { + getUI().startActivity(ni); + } catch (Exception e) { + LOGGER.log(Level.FINE, "startActivity(MemorizingActivity)", e); + } + } + }); LOGGER.log(Level.FINE, "openDecisions: " + openDecisions + ", waiting on " + myId); try { @@ -579,7 +623,6 @@ public class MemorizingTrustManager { public X509Certificate[] getAcceptedIssuers() { return MemorizingTrustManager.this.getAcceptedIssuers(); } - } private class InteractiveMemorizingTrustManager implements X509TrustManager { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java index 983ce433f610f977d5df317560b621cd0829623e..0f7247fdc8d3af2e35654512a47df79afb7af484 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java @@ -212,7 +212,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection jinglePacket.setSecurity( Iterables.getOnlyElement(contentMap.contents.keySet()), xmppAxolotlMessage); } - Log.d(Config.LOGTAG, "--> " + jinglePacket.toString()); jinglePacket.setTo(id.with); xmppConnectionService.sendIqPacket( id.account, @@ -235,7 +234,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } private void receiveSessionAccept(final JinglePacket jinglePacket) { - Log.d(Config.LOGTAG, "receive session accept " + jinglePacket); + Log.d(Config.LOGTAG, "receive file transfer session accept"); if (isResponder()) { receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT); return; @@ -450,7 +449,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection setLocalContentMap(contentMap); final var jinglePacket = contentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); - Log.d(Config.LOGTAG, "--> " + jinglePacket.toString()); send(jinglePacket); // this needs to come after session-accept or else our candidate-error might arrive first this.transport.connect(); @@ -537,7 +535,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } private void receiveSessionInfo(final JinglePacket jinglePacket) { - Log.d(Config.LOGTAG, "<-- " + jinglePacket); respondOk(jinglePacket); final var sessionInfo = FileTransferDescription.getSessionInfo(jinglePacket); if (sessionInfo instanceof FileTransferDescription.Checksum checksum) { @@ -556,6 +553,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } private void receiveSessionTerminate(final JinglePacket jinglePacket) { + respondOk(jinglePacket); final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason(); final State previous = this.state; Log.d( @@ -722,7 +720,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.TRANSPORT_REPLACE); return; } - Log.d(Config.LOGTAG, "receive transport replace " + jinglePacket); final GenericTransportInfo transportInfo; try { transportInfo = FileTransferContentMap.of(jinglePacket).requireOnlyTransportInfo(); @@ -745,16 +742,29 @@ public class JingleFileTransferConnection extends AbstractJingleConnection private void receiveTransportReplace( final JinglePacket jinglePacket, final GenericTransportInfo transportInfo) { respondOk(jinglePacket); - final Transport transport; + final Transport currentTransport = this.transport; + if (currentTransport != null) { + Log.d( + Config.LOGTAG, + "terminating " + + currentTransport.getClass().getSimpleName() + + " upon receiving transport-replace"); + currentTransport.setTransportCallback(null); + currentTransport.terminate(); + } + final Transport nextTransport; try { - transport = setupTransport(transportInfo); + nextTransport = setupTransport(transportInfo); } catch (final RuntimeException e) { sendSessionTerminate(Reason.of(e), e.getMessage()); return; } - this.transport = transport; + this.transport = nextTransport; + Log.d( + Config.LOGTAG, + "replacing transport with " + nextTransport.getClass().getSimpleName()); this.transport.setTransportCallback(this); - final var transportInfoFuture = transport.asTransportInfo(); + final var transportInfoFuture = nextTransport.asTransportInfo(); Futures.addCallback( transportInfoFuture, new FutureCallback<>() { @@ -779,7 +789,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection contentMap .transportInfo() .toJinglePacket(JinglePacket.Action.TRANSPORT_ACCEPT, id.sessionId); - Log.d(Config.LOGTAG, "sending transport accept " + jinglePacket); send(jinglePacket); transport.connect(); } @@ -959,7 +968,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection new JinglePacket(JinglePacket.Action.SESSION_INFO, this.id.sessionId); jinglePacket.addJingleChild(sessionInfo.asElement()); jinglePacket.setTo(this.id.with); - Log.d(Config.LOGTAG, "--> " + jinglePacket); send(jinglePacket); } @@ -982,6 +990,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection transport.terminate(); if (isInitiator()) { this.transport = setupLastResortTransport(); + Log.d( + Config.LOGTAG, + "replacing transport with " + this.transport.getClass().getSimpleName()); this.transport.setTransportCallback(this); final var transportInfoFuture = this.transport.asTransportInfo(); Futures.addCallback( @@ -1011,7 +1022,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection contentMap .transportInfo() .toJinglePacket(JinglePacket.Action.TRANSPORT_REPLACE, id.sessionId); - Log.d(Config.LOGTAG, "sending transport replace " + jinglePacket); send(jinglePacket); } @@ -1039,7 +1049,6 @@ public class JingleFileTransferConnection extends AbstractJingleConnection } final JinglePacket jinglePacket = transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); - Log.d(Config.LOGTAG, "--> " + jinglePacket); send(jinglePacket); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java index 0773610fbb07f13d174c5e6a39cbd87c0c768d80..e4dd730d518cd8c3b34ba2134b298b7e65b7daff 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/transports/WebRTCDataChannelTransport.java @@ -174,7 +174,14 @@ public class WebRTCDataChannelTransport implements Transport { } private void onIceConnectionFailed() { - this.transportCallback.onTransportSetupFailed(); + final var callback = this.transportCallback; + if (callback == null) { + Log.d( + Config.LOGTAG, + "not calling onTransportSetupFailed(). Transport likely has been replaced"); + return; + } + callback.onTransportSetupFailed(); } private void setDataChannel(final DataChannel dataChannel) { @@ -184,7 +191,6 @@ public class WebRTCDataChannelTransport implements Transport { new OnMessageObserver() { @Override public void onMessage(final DataChannel.Buffer buffer) { - Log.d(Config.LOGTAG, "onMessage() (the other one)"); try { WebRTCDataChannelTransport.this.writableByteChannel.write(buffer.data); } catch (final IOException e) { @@ -577,8 +583,7 @@ public class WebRTCDataChannelTransport implements Transport { Log.d(Config.LOGTAG, "DataChannelWriter reached EOF"); return; } - dataChannel.send( - new DataChannel.Buffer(ByteBuffer.wrap(buffer, 0, count), true)); + send(ByteBuffer.wrap(buffer, 0, count)); } } catch (final InterruptedException | InterruptedIOException e) { if (isSending.get()) { @@ -591,6 +596,16 @@ public class WebRTCDataChannelTransport implements Transport { } } + private void send(final ByteBuffer byteBuffer) throws IOException { + try { + dataChannel.send(new DataChannel.Buffer(byteBuffer, true)); + } catch (final IllegalStateException e) { + // dataChannel can be 'disposed' if we waited too long between `isSending` check and + // actually trying to send + throw new IOException(e); + } + } + public void close() { this.isSending.set(false); terminate(this.dataChannel); diff --git a/src/main/res/raw/letsencrypt.bks b/src/main/res/raw/letsencrypt.bks new file mode 100644 index 0000000000000000000000000000000000000000..32fd83a6d275cbefdfdc8ee7c210baacb167d775 Binary files /dev/null and b/src/main/res/raw/letsencrypt.bks differ diff --git a/src/main/res/values-fa-rIR/strings.xml b/src/main/res/values-fa-rIR/strings.xml index 8c30590ffdd8e8c7a6b3f649f1f8b1e2a233dc7c..e95dbcced52c7e4899bc6e8edb546aecf4da9d56 100644 --- a/src/main/res/values-fa-rIR/strings.xml +++ b/src/main/res/values-fa-rIR/strings.xml @@ -24,8 +24,8 @@ هم اکنون 1 دقیقه قبل %d دقیقه قبل - در حال ارسال... - در حال رمزگشایی پیام. لطفا صبور باشید... + در حال ارسال… + در حال رمزگشایی پیام. لطفا صبور باشید… پیام رمز شده به وسیله OpenPGP نام مستعار قبلا استفاده شده نام مستعار نادرست است @@ -38,7 +38,7 @@ مسدود شده ثبت نام حساب جدید بر روی سرور تغییر رمز عبور بر روی سرور - به اشتراک گذاری با ... + هم‌رسانی با… شروع گفتگو دعوت از مخاطب مخاطبین @@ -56,7 +56,7 @@ پیوست فایل افزودن مخاطب ارسال ناموفق بود - در حال به اشتراک گذاری فایل ها. لطفا صبور باشید... + در حال هم‌رسانی پرونده‌ها. لطفاً صبر کنید… پاک سازی تاریخچه پاک سازی تاریخچه گفتگو ها انتخاب دستگاه @@ -72,37 +72,37 @@ راه اندازی مجدد نصب لطفا OpenKeychain را نصب نمایید - ارائه ... - انتظار... + پیشنهاد می‌شود… + در انتظار… کلید OpenPGP یافت نشد کلید های OpenPGP یافت نشدند عمومی - پذیرفتن فایل ها - پذیرفتن خودکار فایل های کوچکتر از ... - پیوست ها + پذیرفتن پرونده‌ها + پذیرفتن خودکار پرونده‌های کوچک‌تر از… + پیوست‌ها اعلان لرزش هنگام دریافت پیام جدید بلرز - اعلان از طریق LED - چشمک زدن چراغ اعلان هنگام رسیدن پیام جدید + اعلان از راه LED + چشمک زدن چراغ اعلان هنگام رسیدن پیام تازه آهنگ زنگ - مهلت + مدت خاموشی پیشرفته هیچ وقت گزارش خرابی را ارسال نکن - پیام ها را تایید کن + تأیید دریافت پیام رابط کاربری پذیرفتن - فایلی که انتخاب نموده اید تصویر نیست - فایل پیدا نشد + پرونده‌ای که انتخاب کرده‌اید تصویر نیست + پرونده پیدا نشد ناشناخته - موقتا غیر فعال شد + موقتاً غیرفعال شد آنلاین آفلاین - غیر مجاز + غیرمجاز سرور پیدا نشد عدم اتصال ثبت نام ناموفق بود - نام کاربری قبلا استفاده شده + نام کاربری قبلاً استفاده شده ثبت نام به پایان رسید برقرای ارتباط امن با شکست مواجه شد نقض سیاست @@ -113,54 +113,939 @@ OpenPGP OMEMO حذف حساب کاربری - موقتا غیر فعال کن - انتشار آواتار + موقتاً غیر فعال کن + انتشار تصویر نمایه انتشار کلید عمومی OpenPGP حذف کلید عمومی OpenPGP - فعال سازی حساب کاربری + فعال‌سازی حساب کاربری ضبط صدا - username@example.com - کلمه عبور - آیا می خواهید %s را به مخاطبان خود اضافه کنید؟ + ‪username@example.com‬ + رمز + آیا می خواهید %s را به مخاطبان خود بیفزایید؟ مشخصات سرور در دسترس خارج از دسترس - آخرین بار لحظاتی قبل مشاهده شده - آخرین بار %d دقیقه قبل مشاهده شده - آخرین بار %d ساعت قبل مشاهده شده - آخرین بار %d روز قبل مشاهده شده - دیگر دستگاه ها - در حال دریافت کلید ها... - رمز گشایی + آخرین بار همین الان + آخرین بار %d دقیقه پیش + آخرین بار %d ساعت پیش + آخرین بار %d روز پیش + دستگاه‌های دیگر + در حال دریافت کلیدها… + رمزگشایی جستجو - وارد کردن مخاطب + واردکردن مخاطب حذف مخاطب مشاهده جزییات مخاطب - بلاک مخاطب - غیر بلاک کردن مخاطب - ارسال مجدد - آدرس وب + مسدودسازی مخاطب + برداشتن مسدودسازی مخاطب + فرستادن دوباره + نشانی وب آفلاین مخاطب - نمایش موقعیت + نمایش موقعیت مکانی لغو آنلاین متوسط - گواهی ناشناخته را بپذیر؟ - هم اکنون غیر فعال کن - سایز فونت - فعال به صورت پیش فرض - غیر فعال به صورت پیش فرض + تأییدیهٔ امنیتی ناشناخته پذیرفته شود؟ + هم اکنون غیرفعال کن + اندازهٔ قلم + فعال به صورت پیش‌فرض + غیرفعال به صورت پیش‌فرض کوچک متوسط بزرگ - پیام برای این دستگاه رمزگذاری نشده - کپی کردن موقعیت - به اشتراک گذاری موقعیت - به اشتراک گذاری موقعیت - نمایش موقعیت - به اشتراک گذاری - لطفا صبر کنید... - جستجو در پیام ها - مشاهده گفتگو - + پیام برای این دستگاه رمز نشده. + کپی موقعیت مکانی + هم‌رسانی موقعیت مکانی + هم‌رسانی موقعیت مکانی + نمایش موقعیت مکانی + هم‌رسانی + لطفا صبر کنید… + جستجوی پیام‌ها + دیدن گفتگو + اثر انگشت OMEMO کپی شد + نمایش مسدودسازی‌ها + سپس این گفتگو را ببند + سرور push دلخواه کاربر برای رساندن اعلان‌های push از راه XMPP به دستگاه شما. + وصل شدن + ورود + شما وصل نیستید. بعداً دوباره تلاش کنید + ضبط پیام صوتی + گرفتن اجازه‌های عضویت + هیچ نشانی XMPPای پیدا نشد + لطفاً با احتیاط تغییر دهید + XEP-0363: HTTP File Upload + مخاطب تازه‌ای بیفزایید، گفتگوی گروهی بسازید یا بپیوندید، یا کانال‌های تازه بیابید + حساب شما + فرستاده می‌شود (%1$d%% انجام شده) + پاسخ + خموشاندن + %s دارند تایپ می‌کنند… + %s فرستاده می‌شود + گزینه‌های پیام + شکست موقتی برای تأیید هویت + گرفتن عکس + تاریخچه از سرور دریافت می‌شود + آیا می‌خواهید %s را از فهرست مخاطبان خود پاک کنید؟ گفتگوها با این مخاطب پاک نخواهند شد. + تنظیم + XEP-0280: Message Carbons + برنامه‌ای که با آن این عکس را هم‌رسانی کرده‌اید اجازهٔ کافی نمی‌دهد. + اثر انگشت‌های OMEMO مورد اعتماد + کنش سریع را برگزینید + خصوصی، فقط برای اعضا + نهفتن اعلان‌ها + آیا می‌خواهید حساب کاربری خود را حذف کنید؟ این کار تاریخچهٔ همهٔ گفتگوهای شما را از بین می‌برد + این ویژگی با هیچ حساب فعالی ممکن نیست + نگذارید سیستم‌عامل اتصال شما را قطع کند + نشانی XMPP کپی شد + بیرون‌انداخته‌شده + %s تا این‌جا خوانده است + v\\اثر انگشت OMEMO (منبع پیام) + تماس ویدیویی + جایگزینی دکمهٔ «ارسال» با کنش‌های سریع + انتقال پرونده لغو شد + %s برای بارگیری پیشنهاد شده + تماس ویدیویی جاری + تماس دریافتی + تماس جاری + هشدار: فرستادن بدون به‌روزرسانی وصل‌بودن از هر دو طرف مشکلاتی به وجود می‌آورد. +\n +\nبه «جزئیات مخاطب» بروید تا مطمئن شوید که به‌روزرسانی وصل‌بودن شما فعال است. + بازیابی پشتیبان + ‪ channel@conference.example.com‬ + تغییر رمز + نوشتن + جزئیات کانال + سند متنی + بگذارید مخاطبان‌تان بدانند که پیام‌های آن‌ها را گرفته یا خوانده‌اید + فعال‌سازی همهٔ حساب‌ها + XEP-0357: Push + مخاطب شما دستگاه‌های تأییدنشده به کار می‌برد. بارکد دوبعدی آن‌ها را برای تأیید اسکن کنید تا امکان شنود با «حملهٔ مرد میانی» را از بین ببرید. + مشغول + قطع + هم‌رسانی پرونده ممکن نبود + حذف حساب از سرور + گواهینامه‌های امنیتی را حذف کن + تبدیل تصویر ممکن نبود + پشتیبان‌گیری + عضویت + تا اطلاع ثانوی + موضوع + پرونده حذف شد + ادامه + Username + تنظیمات گفتگوی گروهی تغییر کرد! + ثبت نام با این سرور ممکن نیست + پاسخ + تماس ویدیویی دوباره وصل می‌شود + تغییر دوربین ممکن نبود + پشتیبان‌گیری آغاز شد. شما از پایانش باخبر خواهید شد. + ثبت‌نام حساب تازه ممکن نیست + برای تماس Tor را خاموش کنید + ساختن + سنجاق‌کردن به بالا + شبکهٔ Tor در دسترس نیست + آیا می‌خواهید مسدودکردن %s را بردارید تا او دوباره بتواند به شما پیام بفرستد؟ + آیا می‌خواهید این کانال عمومی را حذف کنید؟ +\n +\nهشدار: این کانال کاملاً از سرور حذف خواهد شد. + گزارش هرزنامه و مسدودسازی فرستنده + %1$s شکست خورد + مشکل در تأیید + شما از این گفتگوی گروهی به خاطر دلایل فنی خارج شده‌اید + رمز تازه + جستجوی مخاطبان + تازگی به‌کاررفته + ساختن دوبارهٔ کلید OMEMO + گشودن %s + بارگیری %s + انجام کنش با + شناسهٔ کلید OpenPGP + لغو انتقال + %s تایپ می‌کند… + رد درخواست تبدیل به ویدیو + این گفتگوی گروهی فقط برای اعضاست + این گفتگوی گروهی تعطیل شده است + + %1$d عضو را ببینید + %1$d عضو را ببینید + + برگزیدن پرونده + تصویر نمایه منتشر شد! + حذف گفتگوی گروهی ممکن نبود + + %d گواهینامهٔ امنیتی حذف شد + %d گواهینامهٔ امنیتی حذف شد + + حذف %s + فرستادن موقعیت مکانی + دریافت به‌روزرسانی وصل‌بودن + %s تا این‌جا خوانده‌اند + بگذارید مخاطبانتان پیام‌ها را پس از ارسال ویرایش کنند + تنها یک تماس در هر زمان ممکن است. + %1$s +%2$d تا این‌جا خوانده‌اند + XEP-0191: Blocking Command + هیچ + حافظه کم آمد. تصویر خیلی بزرگ است + دربارهٔ %s + این مخاطب از قبل وجود دارد + پخش صدا + پاک‌کردن پرونده + (یا نگه دارید تا به حالت پیش‌فرض برگردد) + فعال‌کردن ساعت‌های خاموشی + حذف از کانال + شما می‌خواهید %s را از یک کانال عمومی بیرون کنید. تنها راهش این است که این کاربر را برای همیشه ممنوع کنید. + مسدودکردن همهٔ مخاطبان از %s را بردارید؟ + نشانی پرونده + حذف کانال ممکن نبود + تماس دریافتی (%s) · %s + سرور شما نمی‌تواند تصویر نمایه را منتشر کند + خروج از حساب کاربری + تماس ازدست‌رفته · %s + پاک‌کردن دستگاه‌ها + XEP-0215: External Service Discovery + آیا می‌خواهید کلید عمومی OpenPGP خود را از اعلان حضور خود بردارید؟ +\nمخاطبان شما نخواهند توانست پیام‌های رمزشده با OpenPGP بفرستند. + حالت پیشرفته + بگذارید مخاطبانتان بدانند که دارید برای آن‌ها تایپ می‌کنید + آیا می‌خواهید همهٔ دستگاه‌های دیگر را از اعلام OMEMO دربیاورید؟ دفعهٔ بعدی که آن دستگاه‌ها وصل شوند، دوباره خودشان را اعلام می‌کنند، ولی ممکن است پیغام‌هایی را که در این بین فرستاده می‌شود نگیرند. + حساب XMPP + رمز فعلی + گفتگوی گروهی + تغییر نقش %s ممکن نبود + رمز نمی‌تواند خالی باشد + رمز عوض شد! + همهٔ گواهینامه‌های امنیتی باید مستقیماً تأیید شوند + رمزگذاری پیام ممکن نبود چون مخاطبان شما کلید عمومی خود را منتشر نمی‌کنند. +\n +\nلطفاً از آن‌ها بخواهید تا OpenPGP را راه بیندازند. + با فرستادن گزارش‌های خرابی به توسعهٔ این برنامه کمک می‌کنید + نام کاربری + نسخهٔ پشتیبان شما ساخته شده است + جستجوی گفتگوی گروهی + رمزشده با OMEMO + تصویر + %1$s دریافت می‌شود (%2$d%% انجام شده) + ویرایش موضوع گفتگوی گروهی + راهنما + انتخاب عکس + XEP-0163: PEP (Avatars / OMEMO) + برنامهٔ ناسازگار + هیچ (غیرفعال) + روی تصویر نمایه بزنید تا یک تصویر تازه از گالری برگزینید + اجازهٔ اصلاح پیام‌ها + خطای عمومی خواندن/نوشتن. شاید فضای ذخیره‌سازی شما پرشده؟ + مدیریت حساب + صدای زنگ برای تماس‌های دریافتی + ذخیره به عنوان نشانک + درخواست دهید + رد کردن + تصویر نمایهٔ شما + ساختن پرونده‌های پشتیبان + تماس پایان می‌یابد + برای مدیریت حساب‌ها این‌جا بروید + اعطای اجازه‌های عضویت + ممنوع کن + تماس دوباره وصل می‌شود + بارگیری شکست خورد: نوشتن پرونده ممکن نبود + نادیده بگیرید + تماس ازدست‌رفته + بررسی اندازهٔ %1$s روی %2$s + هم‌گام‌سازی نشانک‌ها + بررسی اندازهٔ %s + فرستادن با Enter + پرونده‌های پشتیبان در %s ذخیره می‌شوند + لطفاً از مخاطب خود درخواست به‌روزرسانی وصل‌بودن کنید. +\n +\nبا این کار می‌شود فهمید که او چه برنامه‌ای را به کار می‌برد. + همه تا این‌جا خوانده‌اند + v\\اثر انگشت OMEMO + %s پیشنهاد می‌شود + OpenKeychain یک خطا داشت. + خروج + این نام کاربری معتبر نیست + دامنه قابل تأیید نیست + همهٔ مخاطبان از %s را مسدود کنید؟ + تماس‌ها هنگام به‌کارگیری Tor غیرفعال هستند + آفلاین‌ها را پنهان کن + تماس خروجی (%s) · %s + نمایش برچسب‌های فقط-خواندنی زیر مخاطبان + سرور انتشار شما را نپذیرفت + نقل‌قول + جزئیات حساب + آماده‌سازی برای هم‌رسانی پرونده + شما از این گفتگوی گروهی اخراج شده‌اید + امنیت + ارسال‌های شکست‌خورده + بارگیری شکست خورد: پروندهٔ نامعتبر + غیرفعال‌سازی همهٔ حساب‌ها + به صادرکنندگان گواهینامه‌های امنیتی سیستم اعتماد نکن + اعطای اجازه‌های مالکیت + تصویر نمایهٔ %s + کلید عموم OpenPGP منتشر شد. + %s دیگر تایپ نمی‌کند + مخاطب جدید به فهرست مخاطبانتان افزوده شود؟ + زمان پایان + اتصال به حساب ممکن نبود + کنش سریع + هیچ برنامه‌ای پیدا نشد + لغو مسدودسازی عضو + ممنوعیت نماگرفت + تماس ویدیویی دریافتی + محدودیت منابع + ترک + چشم‌پوشی + بستن گفتگو + پروندهٔ چندرسانه‌ای + آماده‌سازی برای فرستادن عکس + فرستادن پیام خصوصی به %s + موقعیت مکانی + آیا می‌خواهید همهٔ پیام‌ها در این گفتگو را پاک کنید؟ +\n +\nهشدار: این کار تأثیری روی پیام‌هایی که روی دستگاه‌ها یا سرورهای دیگر ذخیره شده‌اند ندارد. + آیا می‌خواهید %s را مسدود کنید تا نتواند پیامی به شما بدهد؟ + کپی اثر انگشت OMEMO + کانال را مدیریت‌شده کن + برنامهٔ اندروید + بارگیری شکست خورد: اتصال به میزبان ممکن نبود + شما دیگر در این گفتگوی گروهی نیستید + افزودن متقابل + خطا در گشودن ارتباط + اتصال از بین رفت + گفتگوی گروهی را ترک کرد + رمزگذاری پیام ممکن نبود چون مخاطب شما کلید عمومی‌اش را منتشر نمی‌کند. +\n +\nلطفاً از مخاطب خود بخواهید تا OpenPGP را راه بیندازد. + تماس وصل نشد + اعطای اجازه‌های مدیریت + تصویر برداری + میکروفون شما دردسترس نیست + آخرین یک روز پیش + ذخیرهٔ تصویر نمایه ممکن نبود + با این سرور نمی‌توان دعوت‌نامه ساخت + هیچ کلید قابل استفاده‌ای برای این مخاطب وجود ندارد. +\nدریافت کلیدهای تازه از سرور ممکن نبود. شاید سرور مخاطب شما خراب است؟ + با حساب %s + نشانی XMPP را مسدود کن + تبدیل پروندهٔ عکس ممکن نبود + هیچ سروری برای گفتگوی گروهی پیدا نشد + خارج‌شده از حساب کاربری + + %d تماس ازدست‌رفته + %d تماس ازدست‌رفته + + برگزیدن + آماده‌سازی برای فرستادن عکس‌‌ها + به‌روز می‌شود… + ‪ channel@conference.example.com/nick‬ + اتصال به برخی از حساب‌ها ممکن نبود + تلاش دوباره + محتوای برنامه را در منوی تغییر برنامه‌ها سیاه کن و جلوی ذخیرهٔ نماگرفت را بگیر + حذف از گفتگوی گروهی + مسیر GPX + تماس رد شد + در حال عضویت در گفتگوی گروهی… + تبدیل به ویدیو + نام نمایشی تازه + این حساب وجود دارد + حذف نشانک + شما دستگاه‌های تأییدنشده به کار می‌برید. بارکد دوبعدی دستگاه‌های دیگرتان را برای تأیید اسکن کنید تا امکان شنود با «حملهٔ مرد میانی» را از بین ببرید. + پیکربندی کانال عمومی + دعوت‌نامه مفهوم نیست + توقف پخش صدا + اعلان تایپ‌کردن + فرستادن به‌روزرسانی وصل‌بودن + درخواست دریافت به‌روزرسانی وصل‌بودن + پیام رمز شده است. برای بازکردنش OpenKeychain را نصب کنید. + انتشار + نمایش کلید Enter + حذف کانال + زمان آغاز + به %s + دوباره وصل می‌شود + گرفتن اجازه‌های مدیریت + آیا می‌خواهید این پرونده را پاک کنید؟ +\n +\nهشدار: با این کار نسخه‌های این پرونده که روی دستگاه‌ها یا سرورهای دیگر هستند پاک نمی‌شود. + تماس خروجی · %s + سرور Push + + %1$d تماس ازدست‌رفته از %2$s + %1$d تماس ازدست‌رفته از %2$s + + در حال بررسی %s روی میزبان HTTP + حذف تصویر نمایه + تغییر کلید شکلک‌ها به کلید Enter + حذف انتخاب‌ها + برنامه‌ای برای گشودن پرونده وجود ندارد + درخواست عضویت را خودبه‌خود بپذیر + گواهینامه‌های امنیتی تأییدشده را حذف کن + آخرین بار یک ساعت پیش + تأیید + رمزشده با OpenPGP + تماس صوتی + بازگشت به گفتگو + فعال‌کردن + تغییر تنظیمات گفتگوی گروهی ممکن نبود + تنظیمات پیشرفته + شما از این گفتگوی گروهی منع شده‌اید + میزبانی شده روی %s + + %1$d تماس ازدست‌رفته از %2$d مخاطب + %1$d تماس ازدست‌رفته از %2$d مخاطب + + خطای برنامه + فرستادن پیام خصوصی + فعال‌سازی حساب را فراموش نکنید. + ذخیره به عنوان گفتگوی گروهی + کتاب صوتی + نشانی‌های XMPP را برای همه نمایان کن + وصل می‌شود… + پشتیبان شما بازیابی شد + متفرقه + وصل + خطایی رخ داد + اصلاح پیام ممکن نبود + برنامه‌ای برای دیدن مخاطب وجود ندارد + تماس دریافتی + سند PDF + توزیع‌کنندهٔ UnifiedPush + پرونده‌های پشتیبان در %s ذخیره شده‌اند + فعال‌کردن ویدیو ممکن نبود. + اسکن بارکد دوبعدی + XEP-0237: Roster Versioning + پیغام خصوصی + صدای اعلان + کد ثبت‌نام اشتباه + کلید اشتباه برای رمرگذاری. + ویدیو + + ارسال یک پیام ممکن نبود + ارسال برخی از پیام‌ها ممکن نبود + + زنگ می‌خورد + گزارش هرزنامه + کانال عمومی را ترک کرد + برداشتن سنجاق از بالا + رمزنشده + گفتگو بسته شد + حسابی که از راه آن اعلان‌های push دریافت می‌شوند. + مدت زمانی که اعلان‌های این برنامه، پس از این که شما روی دستگاه‌های دیگرتان فعالیت می‌کنید، خاموش می‌مانند. + عضو + شما عضو نیستید + ساخت گفتگوی گروهی ممکن نبود + جاگذاری مثل نقل‌قول + گواهینامه‌های امنیتی را حذف کن + نشانی کپی شد + هرگز + منع‌کردن از گفتگوی گروهی + اثر انگشت OMEMO (منبع پیام) + %1$s برنامهٔ <b>OpenKeychain</b> را به کار می‌برد تا پیام‌ها را رمزنگاری کند و کلید عمومی شما را مدیریت کند.<br><br>با پروانهٔ ‪GPLv3+‬ منتشر شده است و روی اف-دروید و گوگل‌پلی در دسترس است.<br><br><small>(لطفاً پس از آن %1$s را دوباره اجرا کنید.)</small> + پیکربندی گفتگوی گروهی خصوصی + مخاطب به فهرست مخاطبان افزوده شد + حذف گفتگوی گروهی + این یک نشانی XMPP معتبر نیست + منتشر می‌شود… + گزینه‌های بیشتر + XEP-0313: MAM + نمایش بارکد دوبعدی + ساعت‌های خاموشی + یکی چیزی کار نکرد + با کلید Enter پیام فرستاده می‌شود. حتی اگر این گزینه فعال نباشد، همیشه می‌توانید با Ctrl+Enter پیغام را بفرستید. + مسدودسازی عضو + گفتگوی گروهی رمز می‌خواهد + تنظیم ‪“autojoin”‬ را فعال کنید تا ورود و خروج در گفتگوهای گروهی از دستگاه‌های دیگر این‌جا هم اعمال شود. + تغییر رمز ممکن نبود + برچسب‌های پویا + این گفتگو + کپی نشانی اصلی + برنامه‌ای برای نمایش موقعیت مکانی پیدا نشد + %s دیگر تایپ نمی‌کنند + تماس خروجی + رد + برنامه‌ای که با آن این عکس را انتخاب کرده‌اید اجازهٔ خواندنش را نمی‌دهد. +\n +\nبرنامهٔ دیگری برای انتخاب عکس به کار ببرید. + تماس پذیرفته می‌شود + تصویر نمایهٔ حساب + اثر انگشت OMEMO + پیغام خطا کپی شد + پرونده + برنامه‌ای برای گشودن پیوند وجود ندارد + حذف حساب از سرور ممکن نبود + تاریخچهٔ بیشتری در سرور نیست + نشست ایجاد شد + ورود رمز + صدای اعلان برای پیام‌های تازه + خطا + بارگیری شکست خورد: سرور پیدا نشد + XEP-0198: Stream Management + همهٔ گفتگوها + قطع اعلان‌ها + آیا می‌خواهید نشانک‌گذاری %s را بردارید؟ گفتگوها با این نشانک پاک نخواهند شد. + بازگشت به تماس جاری + آخرین بار یک دقیقه پیش + تغییر نسبت %s ممکن نبود + هیچ گواهینامهٔ امنیتی‌ای مستقیماً تأیید نشده + تبدیل به تماس ویدیویی؟ + علامت‌گذاری به عنوان خوانده‌شده + گرفتن اجازه‌های مالکیت + هیچ کلید قابل استفاده‌ای برای این مخاطب وجود ندارد. +\nمطمئن شوید که هر دوی شما به‌روزرسانی وصل‌شدن را می‌گیرید. + آیا می‌خواهید این گفتگوی گروهی را حذف کنید؟ +\n +\nهشدار: گفتگوی گروهی کاملاً از سرور حذف خواهد شد. + پیام تازهٔ رمزشده با OpenPGP پیدا شد + + %d گفتگوی خوانده‌نشده + %d گفتگوی خوانده‌نشده + + صدا + بدون نسبت + وصل می‌شود + فرستادن اطلاعات مربوط به اشکال برنامه از راه حساب XMPP شما به توسعهٔ %1$s در آینده کمک می‌کند. + سرویس پیش‌زمینه + منع‌کردن از کانال + نشانی XMPP + اعلان‌ها در این مدت خاموش خواهند ماند + XEP-0352: Client State Indication + فعال‌سازی اعلان‌ها + بارگیری شکست خورد: پرونده پیدا نشد + دعوت + یافتن دستگاه‌ها + %1$s از گفتگوی گروهی رفت + دعوت به Conversations + انجام شد + کلید عمومی اطلاع‌رسانی نشده + در حال تأیید… + شما از این حساب خارج شده‌اید + پیغام‌ها به خاطر زمان نگه‌داری آفلاین دریافت نمی‌شوند. + تأییدیه‌های امنیتی سرورهایی که دارای نام میزبان تأییدشده هستند تأیید می‌شوند + عضو کانال عمومی شوید + پشتیان Conversations + دور از دسترس + این دستگاه دیگر فعالیتی ندارد + مسیریابی + + %d ساعت + %d ساعت + + فشرده‌سازی ویدیو + فقط تصاویر بزرگ + این دستگاه تأیید شده است + دستگاه شما از غیرفعال‌سازی صرفه‌جویی داده برای %1$s پشتیبانی نمی‌کند. + مخاطب مسدود شده است. + نام سرور با وجود عدم تطابق پذیرفته شود؟ + مسدودسازی غریبه‌ها + حذف خودکار پیام‌ها + کپی + اجازهٔ استفاده از اینترنت را بده + + %d هفته + %d هفته + + غیرفعال‌سازی رمزگذاری + تأیید %s + ویرایش پیغام وضعیت + آیا می‌خواهید در هر حال وصل شوید؟ + سیستم‌عامل شما %1$s را وقتی در پس‌زمینه است برای دسترسی به اینترنت محدود می‌کند. برای دریافت اعلان پیغام‌های تازه باید در این حالت به %1$s اجازهٔ نامحدود بدهید. +\n%1$s در صورت امکان تلاش می‌کند که در مصرف داده صرفه‌جویی کند. + هم‌رسانی URI با… + %1$s نمی‌تواند به %2$s پیام‌های رمزشده بفرستد. شاید چون مخاطب شما سرور قدیمی‌ای به کار می‌برد یا برنامه‌ای دارد که از OMEMO پشتیبانی نمی‌کند. + متوسط (360p) + ارسال‌های شکست‌خورده + ذخیرهٔ پیکربندی کانال ممکن نبود + تیره + اعضا + لطفاً رمز این حساب را وارد کنید + تماس‌های ازدست‌رفته + تأییدیهٔ امنیتی دارای نشانی XMPP نیست + سلب اعتماد از دستگاه + هیچ برنامه‌ای برای نصب برنامه‌ها نصب نیست. + تصویر با %s هم‌رسانی شد + بارگیری پیام‌های بیشتر + فشرده‌سازی تصاویر + مدیریت دسترسی‌ها + روی میزبان دیگر دوباره وصل شو + تنظیمات اعلان تماس‌های دریافتی + میزبان‌ها را از راه DNSSEC راستی‌آزمایی کن + کپی نشانی XMPP + این نام میزبان معتبر نیست + پیام + دریافت فهرست دستگاه‌ها ممکن نبود + موقتاً در دسترس نیست. بعداً دوباره تلاش کنید. + برای انتشار تصویر نمایه باید وصل باشید. + برای بازیابی پشتیبان رمز خود را برای حساب %s وارد کنید. + اثر انگشت تأییدشده + مشغول + همهٔ اتصال‌ها را از شبکهٔ Tor رد کنید. نیاز به Orbot دارد + نشانی XMPP را وارد کنید + نام شما + نمایش به عنوان غایب اگر گوشی قفل شده باشد + پس از فرستادن پیام به پایین فهرست پیام‌ها برو + ساخت گفتگوی گروهی… + پیام‌ها از این دستگاه را که قدیمی‌تر زمان تعیین‌شده هستند به طور خودکار پاک کن. + با همهٔ پیام‌ها اعلان بده + اصلی (فشرده‌نشده) + بیشتر کاربران باید برای پیشنهادهای بهتر از همهٔ شبکهٔ عمومی XMPP گزینهٔ ‪‘jabber.network’‬ را برگزینند. + بارکد دوبعدی نامعتبر + بگذارید همه دیگران را دعوت کنند + نصب Orbot + برنامه‌های محافظت‌شده + کش را خالی کن + کپی اثر انگشت دیجیتال + اعلان‌های شناور + انتخاب حساب + این گفتگوی گروهی خصوصی هیچ عضوی ندارد. + بازیابی پشتیبان + برای دریافت اعلان‌ها حتی وقتی که صفحه خاموش است باید این برنامه را به فهرست برنامه‌های محافظت‌شده بیفزایید. + بارکد را به کمک دوربین اسکن کنید + برای تنظیم نام دکمهٔ ویرایش را به‌کار ببرید. + همیشه برای گفتگوهای تکی و گروهای خصوصی OMEMO به کار برود. + به حافظهٔ خارجی دسترسی %1$s را بدهید + پیام‌های دریافتی از غریبه‌ها + این شمارهٔ تلفن هم‌اینک با یک دستگاه دیگر وارد شده است. + غایب با گوشی قفل‌شده + نام گفتگوی گروهی + مسدودسازی کل دامنه + خیر + اتصال با Tor + ترجیحات آرشیوگیری + تنظیمات وضعیت را دستی انجام دهید + هم‌رسانی به شکل ‪XMPP URI‬ + هم‌اینک آنلاین + وضعیت + نمایش غیرفعال‌ها + پاک‌کردن فضای ذخیرهٔ خصوصی + تنظیمات اعلان پیام‌ها + سرور محلی + شما قرار است کلیدهای OMEMO متعلق به %1$s را با کلیک روی یک پیوند تأیید کنید. این کار فقط وقتی امن است که شما این پیوند را از منبع مورداعتمادی گرفته باشید که فقط %2$s می‌توانسته منتشر کند. + خراب + همه می‌توانند دیگران را دعوت کنند. + آیا می‌خواهید رمزگذاری OMEMO را برای این گفتگو غیرفعال کنید؟ +\nبا این کار مدیر سرور شما می‌تواند پیام‌هایتان را بخواند، ولی شاید این تنها راهی باشد که با آن بتوانید با افراد دارای برنامهٔ قدیمی پیام بفرستید. + + %d روز + %d روز + + jabber.network + تماس‌های دریافتی + این کانال عمومی هیچ عضوی ندارد. مخاطبان خود را به آن بیفزایید یا با دکمهٔ هم‌رسانی نشانی XMPP آن را پخش کنید. + اطلاعات وصل‌بودن + جستجوی کانال‌ها + گوشی همراه + ساخت حساب + سرور دیگر سافته نشد + به جای نقشهٔ توکار، افزونهٔ هم‌رسانی موقعیت مکانی را به‌کار ببر + یافتن کانال‌ها + کنسول + یک بار + یک راهنما برای ساخت حساب در conversations.im فراهم است. +\nاگر conversations.im را به عنوان سرویس‌دهنده برگزینید خواهید توانست با کاربران بقیهٔ سرویس‌دهنده‌ها ارتباط داشته باشید اگر نشانی XMPP کامل خود را به آن‌ها بدهید. + فشرده‌سازی ویدیو + سرویس پیش‌زمینه + همه می‌توانند موضوع را ویرایش کنند. + ما به شما پیامک دیگری با یک کد ۶ رقمی فرستاده‌ایم. + خطا در دریافت کلید OMEMO! + نیاز به CAPTCHA + بازیابی + سازوکار SASL به عقب بازگردانده شد + GIF + پرونده زیادی بزرگ + مدیران می‌توانند موضوع را ویرایش کنند. + مرورگر رسانه + فضای ذخیرهٔ خصوصی را که پرونده‌ها را نگه می‌دارد پاک کن (پرونده‌ها دوباره از سرور بارگرفته می‌شوند) + به‌روزرسانی + برای گفتگوهای تازه OMEMO باید صریحاً روشن شود. + یک کشور برگزینید + این بخش اجباری است + بازسازی تأییدیهٔ امنیتی + (هیچ حساب فعالی نیست) + + %d پیام + %d پیام + + جستجوی اعضا + اعلان‌ها غیرفعال است + روشن + ثبت حساب تازه + تماس‌های خروجی + لطفاً صبر کنید تا کلیدها دریافت شوند + برای پیام‌ها و تماس‌ها از غریبه‌ها اعلان نشان بده. + دعوت دوباره + پورت + کلیدهای OMEMO خود را دوباره بسازید. همهٔ مخاطبان باید دوباره تأییدتان کنند. فقط به عنوان آخرین راه حل این کار را بکنید. + پیغام وضعیت + گفتگوی گروهی خصوصی بسازید + این ویژگی پیاده‌سازی نشده است + هم‌رسانی به شکل بارکد + به دستگاه‌های تازهٔ مخاطبان تأییدنشده آسان‌گیرانه اعتماد کن، ولی برای دستگاه‌های تازهٔ مخاطبان تأییدشده درخواست تأیید بفرست. + پرداخت ضروری است + این کانال از قبل وجود دارد + نام کانال + برنامهٔ هم‌رساننده اجازهٔ دسترسی به این پرونده را نداد. + بله + + %d ثانیه + %d ثانیه + + آیا می‌خواهید فرایند ثبت‌نام را متوقف کنید؟ + ما به شما پیامکی به %s فرستاده‌ایم. + شما دارید یک قالب قدیمی نسخهٔ پشتیبان را وارد می‌کنید + کانال عمومی ساخته می‌شود… + هم‌رسانی موقعیت مکانی غیرفعال است + دریافت ترجیحات آرشیوگیری ممکن نبود + نام نمایشی + بارکد شامل اثر انگشت دیجیتالی این گفتگو نیست. + حذف هویت‌های OMEMO + شما قرار است کلیدهای OMEMO حساب خودتان را تأیید کنید. این کار فقط وقتی امن است که شما این پیوند را از منبع مورد اعتمادی گرفته باشید که فقط خودتان می‌توانستید منتشرش کنید. + این حساب قبلاً ساخته شده است + آغاز ضبط ممکن نبود + بهینه‌سازی مصرف باتری فعال + بازگشت + هنگام پردازش درخواست شما یک چیزی کار نکرد. + به کلیدهای OMEMO آسان‌گیرانه اعتماد شد، یعنی ممکن است مال شخص دیگری باشد یا کسی دارد شنود می‌کند. + ذخیرهٔ ضبط ممکن نبود + کیفیت پایین‌تر یعنی پرونده‌های کوچک‌تر + این یک نشانی XMPP است. لطفاً یک نام بدهید. + مرورگر + به دوربین دسترسی %1$s را بدهید + جستجوی کشورها + پیام‌ها + برقراری ارتباط امن ممکن نبود. + ویژگی پشتیبان‌گیری را برای کپی کردن برنامه (اجرای همزمان روی دستگاه‌های مختلف) به‌کار نبرید. بازیابی نسخهٔ پشتیان فقط برای مهاجرت به دستگاه تازه یا برای وقتی است که دستگاه قبلی را گم کرده‌اید. + نمایش اعلان‌های شناور + بلند + نوشتهٔ داخل عکس بالا را وارد کنید + یک رمز امن ساخته شده است + نشانی کامل XMPP شما این خواهد بود: %s + لطفاً کد ۶ رقمی خود را وارد کنید. + رویداد + پرونده‌ای که برگزیده‌اید یک نسخهٔ پشتیبان این برنامه نیست + باید از وب روی سرور ثبت‌نام کنید + پذیرفتن و ادامه + دستگاه شما شدیداً‌مصرف باتری را برای %1$s بهینه می‌کند که ممکن است باعث تأخیر در اعلان‌ها یا حتی ازدست‌رفتن پیام‌ها شود. +\nتوصیه می‌شود که آن را غیرفعال کنید. + کانال عمومی بسازید + برای گفتگوهای تازه OMEMO به کار برود. + رمزگذاری پیام‌ها + نام مخاطب + ضبط ویدیو + بازیابی پشتیبان ممکن نبود. + این شبیه یک نشانی دامنه است + دریافت ترجیحات آرشیوگیری. لطفاً صبر کنید… + پشتیبان‌گیری و بازیابی + این شمارهٔ پورت معتبر نیست + نشانی XMPP + من این پیوند را از منبع مورد اعتمادی پی گرفته‌ام + رد درخواست + نمایش به عنوان مشغول اگر گوشی بی‌صدا شده باشد + نقض احتمالی حریم خصوصی! + لطفاً شمارهٔ تلفن خود را وارد کنید. + نوشته با %s هم‌رسانی شد + لطفاً %s دیگر دوباره تلاش کنید + %1$s نیاز به دسترسی به فهرست مخاطبانتان دارد تا آن را با فهرست مخاطبان XMPP شما مقایسه کند. +\nبا این کار نام کامل و تصویر نمایهٔ مخاطبانتان نشان داده خواهد شد. +\n +\n%1$s فهرست مخاطبان شما را فقط به طور آفلاین مقایسه می‌کند، بی‌آن‌که چیزی را به سرور بفرستد. + اعتمادنشده + ‪xmpp.example.com‬ + لرزش معادل بی‌صدا + لطفاً کد ۶ رقمی را در زیر وارد کنید. + نشانی‌های XMPP را مدیران می‌بینند. + لطفاً یک نشانی XMPP بدهید + انتشار فعالیت + دریافت کلیدهای رمزگذاری ممکن نبود + پیام‌ها + تأییدیهٔ امنیتی سرور را هیچ مرکز تأیید شناخته‌شده‌ای امضا نکرده است. + پشتیبان + گشودن وبسایت + کوتاه + ادامه + گشودن با… + این نوع از اعلان‌ها برای اعلان‌هایی است که هیچ صدایی تولید نمی‌کنند. مثلاً برای وقتی که شما روی دستگاه دیگری فعالیت می‌کنید (زمان خاموشی). + بگذارید همه موضوع را ویرایش کنند + ترجیحات آرشیوگیری سمت سرور + این حساب XMPP را برای فرستادن هرزنامه گزارش کنید. + در حال درخواست پیامک… + نکته: اگر «فرستادن پرونده» را به جای «فرستادن تصویر» به کار ببرید، تصاویر شما همیشه فشرده‌نشده فرستاده خواهند شد. + تنظیمات بیشتر اتصال + شما عضو کانال موجودی شده‌اید + در صفحهٔ «آغاز گفتگو» صفحه‌کلید را باز کن و مکان‌نما را روی کادر جستجو بگذار + لطفاً نام خود را وارد کنید تا کسانی که شما را در فهرست مخاطبانشان ندارند بدانند که شما چه کسی هستید. + برنامه‌ای برای گشودن وبسایت نبود + من خودم حساب دارم + پیام‌های خصوصی فعال نیستند + من + واگردانی + ثابت‌کردن موقعیت مکانی + اجازه‌ای برای دسترسی به %s نیست + هم‌رسانی HTTP پرونده‌ها برای S3 + رمزگذاری OMEMO + پاسخ ناشناخته از سرور. + یافتن سرور ممکن نبود. + کیفیت ویدیو + به هر حال اضافه کن + فرستادن پیام ویرایش‌شده + خطای نشست + پیام کپی شد + گفتگوهای گروهی و کانال‌ها + اجرای Orbot + کد کشور نامعتبر + گفتگوی گروهی بسازید + ثبت نام شکست خورد: رمز زیادی ضعیف است + تلاش‌های بیش از حد + این برنامه به شما پیامکی می‌فرستد (ممکن است برایتان هزینه داشته باشد) تا شمارهٔ تلفن شما را تأیید کند. کد کشور و شمارهٔ تلفن خود را وارد کنید: + همگام‌سازی با مخاطبان + خطای اتصال + تأییدیهٔ امنیتی قابل خواندن نبود + پیش‌نویس: + %1$d حساب از %2$d تا وصل هستند + همیشه + سرور دیگر پاسخ نداد + اعلان‌ها متوقف شده‌اند + شما در حال نوشتن پیامی هستید. + این نوع از اعلان به طور دائمی نشان می‌دهد که %1$s در حال اجراست. + تماس‌ها + اتصال به سرور ممکن نبود. + رمزگشایی پیام OMEMO موفق نبود. + هیچ برنامه‌ای برای هم‌رسانی URI یافت نشد + این کانال نشانی XMPP شما را عمومی می‌کند + لطفاً صبر کنید (%s) + بخش برگزیده زیادی بزرگ است + وضعیت خود را هنگام نوشتن پیغام وضعیت خود تنظیم کنید. + مالکان می‌توانند موضوع را ویرایش کنند. + فرستادن دوبارهٔ پیامک + نشانی‌های XMPP را همه می‌بینند. + دیروز + دستگاه شما از مجموعهٔ تأییدیه‌های امنیتی پشتیبانی نمی‌کند! + جستجوی مستقیم + سرویس‌دهندهٔ خودم را به‌کار ببر + کامپیوتر + نمایش پیغام خطا + شما نسخهٔ قدیمی از این برنامه را به‌کار می‌برید. + + %d دقیقه + %d دقیقه + + تصویرها با %s هم‌رسانی شدند + پوشهٔ کش را (برای دوربین) خالی کن + نشانی سرور یا ‪.onion‬ + نام کاربری خود را برگزینید + غیرفعال + %s یک شماره تلفن معتبر نیست. + دستگاه شما از غیرفعال‌سازی بهینه‌سازی باتری پشتیبانی نمی‌کند + نام خود را وارد کنید + انجام این کنش ممکن نبود + ورود با تأییدیهٔ امنیتی + گشودن پشتیبان + لغوشده + غیرفعال + تنها وقتی نام‌برده شدم اعلان بده + تماسی بگیرید + اعتماد آسان‌گیرانه پیش از تأیید دستی + حذف کلیدهای انتخاب‌شده + خطای امنیتی: دسترسی نامعتبر به پرونده! + شمار تلاش‌های شما محدود شده است + لطفاً حسابی را فعال کنید + کلید OMEMO با تأییدیهٔ امنیتی تأیید شد! + اعلان‌ها از غریبه‌ها + ورودی نامعتبر از کاربر + گفتگوهای مربوطه بسته شده‌اند. + دستگاه شما شدیداً‌مصرف باتری را برای %1$s بهینه می‌کند که ممکن است باعث تأخیر در اعلان‌ها یا حتی ازدست‌رفتن پیام‌ها شود. +\n +\nهم‌اینک از شما خواسته می‌شود که آن را غیرفعال کنید. + ثبت نام شکست خورد: بعداً دوباره تلاش کنید + غایب + نام + افزودن صوت تازه؟ + جزئیات تأییدیهٔ امنیتی: + شمارهٔ تلفن خود را تأیید کنید + رمزگشایی دوباره + تنها مالک می‌تواند تصویر نمایهٔ گفتگوی عمومی را عوض کند + این نوع از اعلان وقتی نمایش می‌یابد که در اتصال یک حساب مشکلی وجود داشته باشد. + برای پیام‌های ورودی پس‌زمینهٔ سبز بگذار + تصویر نمایهٔ Conversations + پرونده با %s هم‌رسانی شد + تبلت + دیدن رسانه + به %1$s اجازهٔ دسترسی به میکروفن بده + پس‌زمینهٔ سبز + ارائهٔ نام اختیاری است + هم‌رسانی به شکل پیوند HTTP + این سرور مسئول این دامنه نیست + مشغول با گوشی بی‌صدا + اجازه + نمایش تنظیمات نام میزبان و پورت هنگام ساخت حساب + افزودن حساب فعلی + رمزگشایی از پشتیبان ممکن نبود. رمز درست است؟ + ما این شماره را تأیید خواهیم کرد

%s

. آیا درست است یا می‌خواهید شماره را ویرایش کنید؟
+ خطای ناشناخته در شبکه. + شما این حساب را غیرفعال کرده‌اید + اتصال + مخاطب می‌خواهد وضعیت وصل‌بودن شما را بداند + کاوش کانال‌ها یک سرویس خارجی به نام <a href=https://search.jabber.network>search.jabber.network</a> را به‌کار می‌برد.<br><br>به‌کاربردن این ویژگی نشانی IP و کلمات مورد جستجوی شما را به این سرویس می‌فرستد. برای اطلاعات بیشتر <a href=https://search.jabber.network/privacy>سیاست محرمانگی</a> آن‌ها را ببینید. + کدی که وارد کردید نادرست است. + شماره تلفن + خودکار + پیام‌های بی‌صدا + نمایش به عنوان مشغول اگر گوشی در حالت لرزش باشد + محرمانگی + + %d ماه + %d ماه + + افزونهٔ هم‌رسانی موقعیت مکانی + شما همهٔ کلیدهای OMEMO را که در اختیارتان است تأیید کرده‌اید + ساخت پروندهٔ موقتی ممکن نبود + اندازهٔ نسبی قلم به کار رفته در برنامه. + نکته: این مشکل را می‌توان گاهی با افزودن متقابل به فهرست مخاطبانتان حل کرد. + آیا می‌خواهید تأیید این دستگاه را بردارید؟ +\nاین دستگاه و پیغام‌ها از آن به عنوان اعتمادنشده علامت‌گذاری خواهند شد. + برداشتن موقعیت مکانی ثابت + برگزیدن اعضا + این شبیه نشانی یک کانال است + پیوست + پیغام خطا + نسخه‌های پشتیبان را که خودتان نساخته‌اید بازیابی نکنید! + اسکن‌کنندهٔ کد QR باید به دوربین دسترسی داشته باشد + ساخت میان‌بر + اتصال به OpenKeychain ممکن نبود + زنجیرهٔ تأییدیهٔ امنیتی غیرقابل‌اعتماد + نهفتن غیرفعال‌ها + کتاب الکترونیک + کد احتمالی خودبه‌خود جاگذاری شد. + مشکلات اتصال + پرونده به خاطر نقض امنیت نادیده گرفته شد. + هم‌رسانی پرونده‌های پشتیبان + تصویر نمایهٔ گفتگوی گروهی + روش کشف کانال‌ها + شما پیش از این به اثر انگشت دیجیتالی این شخص اعتماد کرده‌اید. با انتخاب «انجام» شما فقط تأیید می‌کنید که %s بخشی از این گفتگوی گروهی است. + الگوی رنگی را برگزینید + مالکان می‌توانند دیگران را دعوت کنند. + این گفتگوی گروهی حذف شده است + کلیدهای OMEMO را تأیید کن + میزبان از تصویر نمایهٔ گفتگوهای عمومی پشتیبانی نمی‌کند + لطفاً نامی به کانال بدهید + صرفه‌جویی در داده فعال است + ناکامل + اتصال شبکه موجود نیست. + درباره + ویرایش پیام + اهمیت، صدا، لرزش + رفتن به پایین + سرور نمی‌تواند به عنوان %s اعتبارسنجی شود. تأییدیهٔ امنیتی فقط برای این معتبر است: + فرستادن دوبارهٔ پیامک (%s) + نشانی XMPP با تأییدیهٔ امنیتی جور نیست + عضویت در کانال عمومی… + آماده برای گپ + بگذارید مخاطبانتان بدانند که چه وقتی با این برنامه کار می‌کنید + کدی که وارد کردید منقضی شده است. + زیاد (720p) + امروز + ویرایش پیغام وضعیت + تم + نام میزبان + کپی نشانی وب + به‌روزرسانی حساب ممکن نبود + \ No newline at end of file diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index bfa004720fb4b3716cab324b5643bdd2a7c3fb0f..c68f67a73cd38fa265f2b61c26f432b006043574 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -157,7 +157,7 @@ \nUsa un xestor de ficheiros diferente para escoller a imaxe. A app que usaches para compartir este ficheiro non concedeu os permisos suficientes. Descoñecido - Desactivado temporalmente + Desactivada temporalmente Conectado Conectando\u2026 Desconectado @@ -251,7 +251,7 @@ Non se desfixo a conversa en grupo Non se puido eliminar a canle Editar o tema da conversa en grupo - Asunto + Tema Entrando na conversa en grupo… Saír Contacto engadido a túa lista de contactos @@ -790,7 +790,7 @@ número de teléfono Valida o teu número de teléfono Quicksy vaiche enviar unha mensaxe SMS (podería ter custos) para validar o teu número de teléfono. Escribe o código de país e número de teléfono: -
%s

É correcto, ou quere modificar o número?]]>
+ Validaremos o número de teléfono

%s

É correcto, ou queres modificar o número?
%s non é un número de teléfono válido. Por favor escribe o teu número de teléfono. Buscar países @@ -861,11 +861,11 @@ Esta canle xa existe Entraches nunha canle existente Non se gardaron os axustes da canle - Permitir que calquera cambie o asunto + Permitir que calquera cambie o tema Permitir que calquera poida convidar - Calquera pode editar o asunto. - As propietarias poden editar o asunto. - Admins poden editar o asunto. + Calquera pode editar o tema. + As propietarias poden editar o tema. + Admins poden editar o tema. Propietarias poden convidar a outras. Calquera pode convidar a outras. Os enderezos XMPP son visibles para a administración. diff --git a/src/main/res/values/about.xml b/src/main/res/values/about.xml index d84cc0ae355345f8fd48b9c3cd81d80dedd9b1d3..7a98c210d2aac63c562fc20f33d4c581707bedce 100644 --- a/src/main/res/values/about.xml +++ b/src/main/res/values/about.xml @@ -31,7 +31,7 @@ Conversations • the very last word in instant messaging. - \n\nCopyright © 2014-2023 Daniel Gultsch + \n\nCopyright © 2014-2024 Daniel Gultsch \n\nThis program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or diff --git a/src/quicksy/res/values-fa/strings.xml b/src/quicksy/res/values-fa/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..ac627b332858b841a747437b09ecdc407348d655 --- /dev/null +++ b/src/quicksy/res/values-fa/strings.xml @@ -0,0 +1,12 @@ + + + برای دریافت اعلان‌ها حتی وقتی که صفحه خاموش است باید این برنامه را به فهرست برنامه‌های محافظت‌شده بیفزایید. + با فرستادن گزارش‌های خرابی به توسعهٔ این برنامه کمک خواهید کرد + تأیید هویت سرور ممکن نیست. + خطای امنیتی ناشناخته. + پاسخی هنگام اتصال به سرور دریافت نشد. + این برنامه در کشور شما دردسترس نیست. + تصویر نمایهٔ Quicksy + مدت زمانی که این برنامه خاموش می‌ماند پس از این که فعالیت روی دستگاه دیگری کشف شد + بگذارید همهٔ مخاطبانتان بدانند چه وقتی این برنامه را به‌کار می‌برید + \ No newline at end of file