Merge tag '2.9.4' into develop

This commit is contained in:
Geno 2021-01-17 23:28:55 +01:00
commit d2cd482a07
38 changed files with 587 additions and 428 deletions

View File

@ -11,7 +11,7 @@ android:
- '.+'
before_script:
- mkdir libs
- wget -O libs/libwebrtc-m85.aar https://gultsch.de/files/libwebrtc-m85.aar
- wget -O libs/libwebrtc-m87.aar https://gultsch.de/files/libwebrtc-m87.aar
script:
- ./gradlew assembleConversationsFreeSystemRelease
- ./gradlew assembleQuicksyFreeCompatRelease

View File

@ -1,5 +1,14 @@
# Changelog
### Version 2.9.4
* minor stability improvements for A/V calls
### Version 2.9.3
* Fixed connectivity issues when different accounts used different SCRAM mechanisms
* Add support for SCRAM-SHA-512
* Allow P2P (Jingle) file transfer with self contact
### Version 2.9.2
* Offer Easy Invite generation on supporting servers

View File

@ -80,7 +80,7 @@ dependencies {
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
implementation 'com.google.guava:guava:27.1-android'
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.11.1'
//implementation fileTree(include: ['libwebrtc-m85.aar'], dir: 'libs')
//implementation fileTree(include: ['libwebrtc-m87.aar'], dir: 'libs')
implementation 'org.webrtc:google-webrtc:1.0.32006'
}
@ -96,8 +96,8 @@ android {
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
versionCode 402
versionName "2.9.2"
versionCode 404
versionName "2.9.4"
archivesBaseName += "-$versionName"
applicationId "eu.sum7.conversations"
resValue "string", "applicationId", applicationId

View File

@ -1,7 +1,10 @@
<?xml version="1.0"?>
<?xml-stylesheet href="../style.xsl" type="text/xsl"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<Project xmlns="http://usefulinc.com/ns/doap#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:xmpp="https://linkmauve.fr/ns/xmpp-doap#">
<Project xmlns="http://usefulinc.com/ns/doap#"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:xmpp="https://linkmauve.fr/ns/xmpp-doap#"
xmlns:schema="https://schema.org/">
<name>Conversations</name>
<created>2014-01-14</created>
@ -22,13 +25,12 @@
<!-- See https://github.com/ewilderj/doap/issues/49 -->
<language>en</language>
<logo rdf:resource="https://raw.githubusercontent.com/iNPUTmice/Conversations/master/doap.rdf"/>
<schema:logo rdf:resource="https://raw.githubusercontent.com/iNPUTmice/Conversations/master/art/ic_launcher.svg"/>
<programming-language>Java</programming-language>
<os>Android</os>
<!-- TODO: Categories are URIs, find a better location for them. -->
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-xmpp"/>
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-jabber"/>
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-client"/>

View File

@ -0,0 +1,4 @@
• Fixed connectivity issues when different accounts used different SCRAM mechanisms
• Add support for SCRAM-SHA-512
• Allow P2P (Jingle) file transfer with self contact
• minor stability improvements for A/V calls

View File

@ -11,4 +11,8 @@ In ogni caso per facilitare puoi creare facilmente un account su chat.sum7.eu, u
<string name="magic_create_text_fixed">Sei stato invitato su %1$s. È già stato scelto un nome utente per te. Ti guideremo nel procedimento per creare un account.\nSarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo.</string>
<string name="your_server_invitation">Il tuo invito al server</string>
<string name="improperly_formatted_provisioning">Codice di approvvigionamento formattato male</string>
</resources>
<string name="tap_share_button_send_invite">Tocca il pulsante condividi per inviare al contatto un invito per %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Se il contatto è vicino, può anche scansionare il codice sottostante per accettare il tuo invito.</string>
<string name="easy_invite_share_text">Unisciti a %1$s e chatta con me: %2$s</string>
<string name="share_invite_with">Condividi invito con...</string>
</resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">Zostałeś zaproszony do %1$s. Nazwa użytkownika została już dla ciebie wybrana. Poprowadzimy ciebie przez proces tworzenia konta.\nBęziesz mógł komunikować się z innymi użytkownikami podając swój adres XMPP.</string>
<string name="your_server_invitation">Zaproszenie twojego serwera</string>
<string name="improperly_formatted_provisioning">Niepoprawnie sformatowany kod zaopatrywania</string>
</resources>
<string name="tap_share_button_send_invite">Użyj przycisku udostępniania aby wysłać swojemu kontaktowi zaproszenie do %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Jeśli twój kontakt jest blisko może przeskanować kod poniżej aby zaakceptować twoje zaproszenie.</string>
<string name="easy_invite_share_text">Dołącz do %1$s aby porozmawiać ze mną: %2$s</string>
<string name="share_invite_with">Udostępnij zaproszenie...</string>
</resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">Você foi convidado para %1$s. Um nome de usuário já foi escolhido para você. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nVocê conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo.</string>
<string name="your_server_invitation">Seu convite do servidor</string>
<string name="improperly_formatted_provisioning">Código de provisionamento formatado de maneira imprópria</string>
</resources>
<string name="tap_share_button_send_invite">Toque no botão compartilhar para enviar, para seu contato, um convite para %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Se seu contato estiver por perto, ele também pode escanear o código abaixo para aceitar seu convite.</string>
<string name="easy_invite_share_text">Junte-se a %1$s e converse comigo: %2$s</string>
<string name="share_invite_with">Compartilhe o convite com...</string>
</resources>

View File

@ -7,22 +7,24 @@ import eu.siacs.conversations.xml.TagWriter;
public class Anonymous extends SaslMechanism {
public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) {
super(tagWriter, account, rng);
}
public static final String MECHANISM = "ANONYMOUS";
@Override
public int getPriority() {
return 0;
}
public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) {
super(tagWriter, account, rng);
}
@Override
public String getMechanism() {
return "ANONYMOUS";
}
@Override
public int getPriority() {
return 0;
}
@Override
public String getClientFirstMessage() {
return "";
}
@Override
public String getMechanism() {
return MECHANISM;
}
@Override
public String getClientFirstMessage() {
return "";
}
}

View File

@ -12,79 +12,82 @@ import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.TagWriter;
public class DigestMd5 extends SaslMechanism {
public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
@Override
public int getPriority() {
return 10;
}
public static final String MECHANISM = "DIGEST-MD5";
@Override
public String getMechanism() {
return "DIGEST-MD5";
}
public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
private State state = State.INITIAL;
@Override
public int getPriority() {
return 10;
}
@Override
public String getResponse(final String challenge) throws AuthenticationException {
switch (state) {
case INITIAL:
state = State.RESPONSE_SENT;
final String encodedResponse;
try {
final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
String nonce = "";
for (final String token : tokenizer) {
final String[] parts = token.split("=", 2);
if (parts[0].equals("nonce")) {
nonce = parts[1].replace("\"", "");
} else if (parts[0].equals("rspauth")) {
return "";
}
}
final String digestUri = "xmpp/" + account.getServer();
final String nonceCount = "00000001";
final String x = account.getUsername() + ":" + account.getServer() + ":"
+ account.getPassword();
final MessageDigest md = MessageDigest.getInstance("MD5");
final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
final String cNonce = CryptoHelper.random(100,rng);
final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
(":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
final String a2 = "AUTHENTICATE:" + digestUri;
final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset
.defaultCharset())));
final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce
+ ":auth:" + ha2;
final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset
.defaultCharset())));
final String saslString = "username=\"" + account.getUsername()
+ "\",realm=\"" + account.getServer() + "\",nonce=\""
+ nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount
+ ",qop=auth,digest-uri=\"" + digestUri + "\",response="
+ response + ",charset=utf-8";
encodedResponse = Base64.encodeToString(
saslString.getBytes(Charset.defaultCharset()),
Base64.NO_WRAP);
} catch (final NoSuchAlgorithmException e) {
throw new AuthenticationException(e);
}
@Override
public String getMechanism() {
return MECHANISM;
}
return encodedResponse;
case RESPONSE_SENT:
state = State.VALID_SERVER_RESPONSE;
break;
case VALID_SERVER_RESPONSE:
if (challenge==null) {
return null; //everything is fine
}
default:
throw new InvalidStateException(state);
}
return null;
}
private State state = State.INITIAL;
@Override
public String getResponse(final String challenge) throws AuthenticationException {
switch (state) {
case INITIAL:
state = State.RESPONSE_SENT;
final String encodedResponse;
try {
final Tokenizer tokenizer = new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
String nonce = "";
for (final String token : tokenizer) {
final String[] parts = token.split("=", 2);
if (parts[0].equals("nonce")) {
nonce = parts[1].replace("\"", "");
} else if (parts[0].equals("rspauth")) {
return "";
}
}
final String digestUri = "xmpp/" + account.getServer();
final String nonceCount = "00000001";
final String x = account.getUsername() + ":" + account.getServer() + ":"
+ account.getPassword();
final MessageDigest md = MessageDigest.getInstance("MD5");
final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
final String cNonce = CryptoHelper.random(100, rng);
final byte[] a1 = CryptoHelper.concatenateByteArrays(y,
(":" + nonce + ":" + cNonce).getBytes(Charset.defaultCharset()));
final String a2 = "AUTHENTICATE:" + digestUri;
final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
final String ha2 = CryptoHelper.bytesToHex(md.digest(a2.getBytes(Charset
.defaultCharset())));
final String kd = ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce
+ ":auth:" + ha2;
final String response = CryptoHelper.bytesToHex(md.digest(kd.getBytes(Charset
.defaultCharset())));
final String saslString = "username=\"" + account.getUsername()
+ "\",realm=\"" + account.getServer() + "\",nonce=\""
+ nonce + "\",cnonce=\"" + cNonce + "\",nc=" + nonceCount
+ ",qop=auth,digest-uri=\"" + digestUri + "\",response="
+ response + ",charset=utf-8";
encodedResponse = Base64.encodeToString(
saslString.getBytes(Charset.defaultCharset()),
Base64.NO_WRAP);
} catch (final NoSuchAlgorithmException e) {
throw new AuthenticationException(e);
}
return encodedResponse;
case RESPONSE_SENT:
state = State.VALID_SERVER_RESPONSE;
break;
case VALID_SERVER_RESPONSE:
if (challenge == null) {
return null; //everything is fine
}
default:
throw new InvalidStateException(state);
}
return null;
}
}

View File

@ -1,6 +1,7 @@
package eu.siacs.conversations.crypto.sasl;
import android.util.Base64;
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
@ -8,22 +9,24 @@ import eu.siacs.conversations.xml.TagWriter;
public class External extends SaslMechanism {
public External(TagWriter tagWriter, Account account, SecureRandom rng) {
super(tagWriter, account, rng);
}
public static final String MECHANISM = "EXTERNAL";
@Override
public int getPriority() {
return 25;
}
public External(TagWriter tagWriter, Account account, SecureRandom rng) {
super(tagWriter, account, rng);
}
@Override
public String getMechanism() {
return "EXTERNAL";
}
@Override
public int getPriority() {
return 25;
}
@Override
public String getClientFirstMessage() {
return Base64.encodeToString(account.getJid().asBareJid().toEscapedString().getBytes(),Base64.NO_WRAP);
}
@Override
public String getMechanism() {
return MECHANISM;
}
@Override
public String getClientFirstMessage() {
return Base64.encodeToString(account.getJid().asBareJid().toEscapedString().getBytes(), Base64.NO_WRAP);
}
}

View File

@ -8,27 +8,30 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter;
public class Plain extends SaslMechanism {
public Plain(final TagWriter tagWriter, final Account account) {
super(tagWriter, account, null);
}
@Override
public int getPriority() {
return 10;
}
public static final String MECHANISM = "PLAIN";
@Override
public String getMechanism() {
return "PLAIN";
}
public Plain(final TagWriter tagWriter, final Account account) {
super(tagWriter, account, null);
}
@Override
public String getClientFirstMessage() {
return getMessage(account.getUsername(), account.getPassword());
}
@Override
public int getPriority() {
return 10;
}
public static String getMessage(String username, String password) {
final String message = '\u0000' + username + '\u0000' + password;
return Base64.encodeToString(message.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
}
@Override
public String getMechanism() {
return MECHANISM;
}
@Override
public String getClientFirstMessage() {
return getMessage(account.getUsername(), account.getPassword());
}
public static String getMessage(String username, String password) {
final String message = '\u0000' + username + '\u0000' + password;
return Base64.encodeToString(message.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
}
}

View File

@ -7,60 +7,63 @@ import eu.siacs.conversations.xml.TagWriter;
public abstract class SaslMechanism {
final protected TagWriter tagWriter;
final protected Account account;
final protected SecureRandom rng;
final protected TagWriter tagWriter;
final protected Account account;
final protected SecureRandom rng;
protected enum State {
INITIAL,
AUTH_TEXT_SENT,
RESPONSE_SENT,
VALID_SERVER_RESPONSE,
}
protected enum State {
INITIAL,
AUTH_TEXT_SENT,
RESPONSE_SENT,
VALID_SERVER_RESPONSE,
}
public static class AuthenticationException extends Exception {
public AuthenticationException(final String message) {
super(message);
}
public static class AuthenticationException extends Exception {
public AuthenticationException(final String message) {
super(message);
}
public AuthenticationException(final Exception inner) {
super(inner);
}
public AuthenticationException(final Exception inner) {
super(inner);
}
public AuthenticationException(final String message, final Exception exception) {
super(message,exception);
}
}
public AuthenticationException(final String message, final Exception exception) {
super(message, exception);
}
}
public static class InvalidStateException extends AuthenticationException {
public InvalidStateException(final String message) {
super(message);
}
public static class InvalidStateException extends AuthenticationException {
public InvalidStateException(final String message) {
super(message);
}
public InvalidStateException(final State state) {
this("Invalid state: " + state.toString());
}
}
public InvalidStateException(final State state) {
this("Invalid state: " + state.toString());
}
}
public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
this.tagWriter = tagWriter;
this.account = account;
this.rng = rng;
}
public SaslMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
this.tagWriter = tagWriter;
this.account = account;
this.rng = rng;
}
/**
* The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another
* mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade
* attacks).
* @return An arbitrary int representing the priority
*/
public abstract int getPriority();
/**
* The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another
* mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade
* attacks).
*
* @return An arbitrary int representing the priority
*/
public abstract int getPriority();
public abstract String getMechanism();
public String getClientFirstMessage() {
return "";
}
public String getResponse(final String challenge) throws AuthenticationException {
return "";
}
public abstract String getMechanism();
public String getClientFirstMessage() {
return "";
}
public String getResponse(final String challenge) throws AuthenticationException {
return "";
}
}

View File

@ -1,53 +1,74 @@
package eu.siacs.conversations.crypto.sasl;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.Base64;
import android.util.LruCache;
import com.google.common.base.Objects;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.SecureRandom;
import java.util.concurrent.ExecutionException;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.TagWriter;
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
abstract class ScramMechanism extends SaslMechanism {
// TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage.
private final static String GS2_HEADER = "n,,";
private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes();
private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes();
private static final LruCache<String, KeyPair> CACHE;
static HMac HMAC;
static Digest DIGEST;
static {
CACHE = new LruCache<String, KeyPair>(10) {
protected KeyPair create(final String k) {
// Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations,SASL-Mechanism".
// Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()'
// is applied to prevent commas in the strings breaking things.
final String[] kParts = k.split(",", 5);
try {
final byte[] saltedPassword, serverKey, clientKey;
saltedPassword = hi(CryptoHelper.hexToString(kParts[1]).getBytes(),
Base64.decode(CryptoHelper.hexToString(kParts[2]), Base64.DEFAULT), Integer.parseInt(kParts[3]));
serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
protected abstract HMac getHMAC();
return new KeyPair(clientKey, serverKey);
} catch (final InvalidKeyException | NumberFormatException e) {
return null;
}
}
};
protected abstract Digest getDigest();
private static final Cache<CacheKey, KeyPair> CACHE = CacheBuilder.newBuilder().maximumSize(10).build();
private static class CacheKey {
final String algorithm;
final String password;
final String salt;
final int iterations;
private CacheKey(String algorithm, String password, String salt, int iterations) {
this.algorithm = algorithm;
this.password = password;
this.salt = salt;
this.iterations = iterations;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CacheKey cacheKey = (CacheKey) o;
return iterations == cacheKey.iterations &&
Objects.equal(algorithm, cacheKey.algorithm) &&
Objects.equal(password, cacheKey.password) &&
Objects.equal(salt, cacheKey.salt);
}
@Override
public int hashCode() {
return Objects.hashCode(algorithm, password, salt, iterations);
}
}
private KeyPair getKeyPair(final String password, final String salt, final int iterations) throws ExecutionException {
return CACHE.get(new CacheKey(getHMAC().getAlgorithmName(), password, salt, iterations), () -> {
final byte[] saltedPassword, serverKey, clientKey;
saltedPassword = hi(password.getBytes(), Base64.decode(salt, Base64.DEFAULT), iterations);
serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
return new KeyPair(clientKey, serverKey);
});
}
private final String clientNonce;
@ -63,20 +84,21 @@ abstract class ScramMechanism extends SaslMechanism {
clientFirstMessageBare = "";
}
private static synchronized byte[] hmac(final byte[] key, final byte[] input)
throws InvalidKeyException {
HMAC.init(new KeyParameter(key));
HMAC.update(input, 0, input.length);
final byte[] out = new byte[HMAC.getMacSize()];
HMAC.doFinal(out, 0);
private byte[] hmac(final byte[] key, final byte[] input) throws InvalidKeyException {
final HMac hMac = getHMAC();
hMac.init(new KeyParameter(key));
hMac.update(input, 0, input.length);
final byte[] out = new byte[hMac.getMacSize()];
hMac.doFinal(out, 0);
return out;
}
public static synchronized byte[] digest(byte[] bytes) {
DIGEST.reset();
DIGEST.update(bytes, 0, bytes.length);
final byte[] out = new byte[DIGEST.getDigestSize()];
DIGEST.doFinal(out, 0);
public byte[] digest(byte[] bytes) {
final Digest digest = getDigest();
digest.reset();
digest.update(bytes, 0, bytes.length);
final byte[] out = new byte[digest.getDigestSize()];
digest.doFinal(out, 0);
return out;
}
@ -85,7 +107,7 @@ abstract class ScramMechanism extends SaslMechanism {
* pseudorandom function (PRF) and with dkLen == output length of
* HMAC() == output length of H().
*/
private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations)
private byte[] hi(final byte[] key, final byte[] salt, final int iterations)
throws InvalidKeyException {
byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE));
byte[] out = u.clone();
@ -171,15 +193,10 @@ abstract class ScramMechanism extends SaslMechanism {
final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ','
+ clientFinalMessageWithoutProof).getBytes();
// Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations,SASL-Mechanism".
final KeyPair keys = CACHE.get(
CryptoHelper.bytesToHex(CryptoHelper.saslPrep(account.getJid().asBareJid().toEscapedString()).getBytes()) + ","
+ CryptoHelper.bytesToHex(CryptoHelper.saslPrep(account.getPassword()).getBytes()) + ","
+ CryptoHelper.bytesToHex(salt.getBytes()) + ","
+ iterationCount + ","
+ getMechanism()
);
if (keys == null) {
final KeyPair keys;
try {
keys = getKeyPair(CryptoHelper.saslPrep(account.getPassword()), salt, iterationCount);
} catch (ExecutionException e) {
throw new AuthenticationException("Invalid keys generated");
}
final byte[] clientSignature;

View File

@ -1,5 +1,6 @@
package eu.siacs.conversations.crypto.sasl;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.macs.HMac;
@ -9,22 +10,30 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter;
public class ScramSha1 extends ScramMechanism {
static {
DIGEST = new SHA1Digest();
HMAC = new HMac(new SHA1Digest());
}
public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
public static final String MECHANISM = "SCRAM-SHA-1";
@Override
public int getPriority() {
return 20;
}
@Override
protected HMac getHMAC() {
return new HMac(new SHA1Digest());
}
@Override
public String getMechanism() {
return "SCRAM-SHA-1";
}
@Override
protected Digest getDigest() {
return new SHA1Digest();
}
public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
@Override
public int getPriority() {
return 20;
}
@Override
public String getMechanism() {
return MECHANISM;
}
}

View File

@ -1,5 +1,6 @@
package eu.siacs.conversations.crypto.sasl;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.macs.HMac;
@ -9,22 +10,30 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter;
public class ScramSha256 extends ScramMechanism {
static {
DIGEST = new SHA256Digest();
HMAC = new HMac(new SHA256Digest());
}
public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
public static final String MECHANISM = "SCRAM-SHA-256";
@Override
public int getPriority() {
return 25;
}
@Override
protected HMac getHMAC() {
return new HMac(new SHA256Digest());
}
@Override
public String getMechanism() {
return "SCRAM-SHA-256";
}
@Override
protected Digest getDigest() {
return new SHA256Digest();
}
public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
@Override
public int getPriority() {
return 25;
}
@Override
public String getMechanism() {
return MECHANISM;
}
}

View File

@ -0,0 +1,39 @@
package eu.siacs.conversations.crypto.sasl;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.macs.HMac;
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter;
public class ScramSha512 extends ScramMechanism {
public static final String MECHANISM = "SCRAM-SHA-512";
@Override
protected HMac getHMAC() {
return new HMac(new SHA512Digest());
}
@Override
protected Digest getDigest() {
return new SHA512Digest();
}
public ScramSha512(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
@Override
public int getPriority() {
return 30;
}
@Override
public String getMechanism() {
return MECHANISM;
}
}

View File

@ -10,69 +10,69 @@ import java.util.NoSuchElementException;
* A tokenizer for GS2 header strings
*/
public final class Tokenizer implements Iterator<String>, Iterable<String> {
private final List<String> parts;
private int index;
private final List<String> parts;
private int index;
public Tokenizer(final byte[] challenge) {
final String challengeString = new String(challenge);
parts = new ArrayList<>(Arrays.asList(challengeString.split(",")));
// Trim parts.
for (int i = 0; i < parts.size(); i++) {
parts.set(i, parts.get(i).trim());
}
index = 0;
}
public Tokenizer(final byte[] challenge) {
final String challengeString = new String(challenge);
parts = new ArrayList<>(Arrays.asList(challengeString.split(",")));
// Trim parts.
for (int i = 0; i < parts.size(); i++) {
parts.set(i, parts.get(i).trim());
}
index = 0;
}
/**
* Returns true if there is at least one more element, false otherwise.
*
* @see #next
*/
@Override
public boolean hasNext() {
return parts.size() != index + 1;
}
/**
* Returns true if there is at least one more element, false otherwise.
*
* @see #next
*/
@Override
public boolean hasNext() {
return parts.size() != index + 1;
}
/**
* Returns the next object and advances the iterator.
*
* @return the next object.
* @throws java.util.NoSuchElementException if there are no more elements.
* @see #hasNext
*/
@Override
public String next() {
if (hasNext()) {
return parts.get(index++);
} else {
throw new NoSuchElementException("No such element. Size is: " + parts.size());
}
}
/**
* Returns the next object and advances the iterator.
*
* @return the next object.
* @throws java.util.NoSuchElementException if there are no more elements.
* @see #hasNext
*/
@Override
public String next() {
if (hasNext()) {
return parts.get(index++);
} else {
throw new NoSuchElementException("No such element. Size is: " + parts.size());
}
}
/**
* Removes the last object returned by {@code next} from the collection.
* This method can only be called once between each call to {@code next}.
*
* @throws UnsupportedOperationException if removing is not supported by the collection being
* iterated.
* @throws IllegalStateException if {@code next} has not been called, or {@code remove} has
* already been called after the last call to {@code next}.
*/
@Override
public void remove() {
if(index <= 0) {
throw new IllegalStateException("You can't delete an element before first next() method call");
}
parts.remove(--index);
}
/**
* Removes the last object returned by {@code next} from the collection.
* This method can only be called once between each call to {@code next}.
*
* @throws UnsupportedOperationException if removing is not supported by the collection being
* iterated.
* @throws IllegalStateException if {@code next} has not been called, or {@code remove} has
* already been called after the last call to {@code next}.
*/
@Override
public void remove() {
if (index <= 0) {
throw new IllegalStateException("You can't delete an element before first next() method call");
}
parts.remove(--index);
}
/**
* Returns an {@link java.util.Iterator} for the elements in this object.
*
* @return An {@code Iterator} instance.
*/
@Override
public Iterator<String> iterator() {
return parts.iterator();
}
/**
* Returns an {@link java.util.Iterator} for the elements in this object.
*
* @return An {@code Iterator} instance.
*/
@Override
public Iterator<String> iterator() {
return parts.iterator();
}
}

View File

@ -158,8 +158,11 @@ public class MucOptions {
}
public boolean allowInvites() {
final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowinvites");
return field != null && "1".equals(field.getValue());
final Field allowInvitesField = getRoomInfoForm().getFieldByName("muc#roomconfig_allowinvites");
final boolean allowInvites = allowInvitesField != null && "1".equals(allowInvitesField.getValue());
final Field allowMemberInvitesField = getRoomInfoForm().getFieldByName("muc#roomconfig_allowmemberinvites");
final boolean allowMemberInvites = allowMemberInvitesField != null && "1".equals(allowMemberInvitesField.getValue());
return allowInvites || allowMemberInvites;
}
public boolean canChangeSubject() {

View File

@ -77,7 +77,7 @@ public class FileBackend {
private static final String FILE_PROVIDER = ".files";
private static final float IGNORE_PADDING = 0.15f;
private XmppConnectionService mXmppConnectionService;
private final XmppConnectionService mXmppConnectionService;
public FileBackend(XmppConnectionService service) {
this.mXmppConnectionService = service;

View File

@ -2,6 +2,8 @@ package eu.siacs.conversations.services;
import android.util.Log;
import org.jetbrains.annotations.NotNull;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashSet;
@ -615,6 +617,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
}
}
@NotNull
@Override
public String toString() {
StringBuilder builder = new StringBuilder();

View File

@ -182,7 +182,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private Toast messageLoaderToast;
private ConversationsActivity activity;
private boolean reInitRequiredOnStart = true;
private OnClickListener clickToMuc = new OnClickListener() {
private final OnClickListener clickToMuc = new OnClickListener() {
@Override
public void onClick(View v) {
@ -192,14 +192,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
startActivity(intent);
}
};
private OnClickListener leaveMuc = new OnClickListener() {
private final OnClickListener leaveMuc = new OnClickListener() {
@Override
public void onClick(View v) {
activity.xmppConnectionService.archiveConversation(conversation);
}
};
private OnClickListener joinMuc = new OnClickListener() {
private final OnClickListener joinMuc = new OnClickListener() {
@Override
public void onClick(View v) {
@ -207,7 +207,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
};
private OnClickListener acceptJoin = new OnClickListener() {
private final OnClickListener acceptJoin = new OnClickListener() {
@Override
public void onClick(View v) {
conversation.setAttribute("accept_non_anonymous", true);
@ -216,7 +216,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
};
private OnClickListener enterPassword = new OnClickListener() {
private final OnClickListener enterPassword = new OnClickListener() {
@Override
public void onClick(View v) {
@ -231,7 +231,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
});
}
};
private OnScrollListener mOnScrollListener = new OnScrollListener() {
private final OnScrollListener mOnScrollListener = new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
@ -310,7 +310,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
};
private EditMessage.OnCommitContentListener mEditorContentListener = new EditMessage.OnCommitContentListener() {
private final EditMessage.OnCommitContentListener mEditorContentListener = new EditMessage.OnCommitContentListener() {
@Override
public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] contentMimeTypes) {
// try to get permission to read the image, if applicable
@ -333,7 +333,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
};
private Message selectedMessage;
private OnClickListener mEnableAccountListener = new OnClickListener() {
private final OnClickListener mEnableAccountListener = new OnClickListener() {
@Override
public void onClick(View v) {
final Account account = conversation == null ? null : conversation.getAccount();
@ -343,7 +343,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
};
private OnClickListener mUnblockClickListener = new OnClickListener() {
private final OnClickListener mUnblockClickListener = new OnClickListener() {
@Override
public void onClick(final View v) {
v.post(() -> v.setVisibility(View.INVISIBLE));
@ -354,8 +354,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
};
private OnClickListener mBlockClickListener = this::showBlockSubmenu;
private OnClickListener mAddBackClickListener = new OnClickListener() {
private final OnClickListener mBlockClickListener = this::showBlockSubmenu;
private final OnClickListener mAddBackClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
@ -366,8 +366,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
};
private View.OnLongClickListener mLongPressBlockListener = this::showBlockSubmenu;
private OnClickListener mAllowPresenceSubscription = new OnClickListener() {
private final View.OnLongClickListener mLongPressBlockListener = this::showBlockSubmenu;
private final OnClickListener mAllowPresenceSubscription = new OnClickListener() {
@Override
public void onClick(View v) {
final Contact contact = conversation == null ? null : conversation.getContact();
@ -400,8 +400,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
updateSnackBar(conversation);
}
};
private AtomicBoolean mSendingPgpMessage = new AtomicBoolean(false);
private OnEditorActionListener mEditorActionListener = (v, actionId, event) -> {
private final AtomicBoolean mSendingPgpMessage = new AtomicBoolean(false);
private final OnEditorActionListener mEditorActionListener = (v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_SEND) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null && imm.isFullscreenMode()) {
@ -413,7 +413,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return false;
}
};
private OnClickListener mScrollButtonListener = new OnClickListener() {
private final OnClickListener mScrollButtonListener = new OnClickListener() {
@Override
public void onClick(View v) {
@ -421,7 +421,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
setSelection(binding.messagesView.getCount() - 1, true);
}
};
private OnClickListener mSendButtonListener = new OnClickListener() {
private final OnClickListener mSendButtonListener = new OnClickListener() {
@Override
public void onClick(View v) {
@ -517,7 +517,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private static Conversation getConversation(Activity activity, @IdRes int res) {
final Fragment fragment = activity.getFragmentManager().findFragmentById(res);
if (fragment != null && fragment instanceof ConversationFragment) {
if (fragment instanceof ConversationFragment) {
return ((ConversationFragment) fragment).getConversation();
} else {
return null;
@ -527,11 +527,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
public static ConversationFragment get(Activity activity) {
FragmentManager fragmentManager = activity.getFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment);
if (fragment != null && fragment instanceof ConversationFragment) {
if (fragment instanceof ConversationFragment) {
return (ConversationFragment) fragment;
} else {
fragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
return fragment != null && fragment instanceof ConversationFragment ? (ConversationFragment) fragment : null;
return fragment instanceof ConversationFragment ? (ConversationFragment) fragment : null;
}
}
@ -986,7 +986,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
menuCall.setVisible(false);
menuOngoingCall.setVisible(false);
} else {
final Optional<OngoingRtpSession> ongoingRtpSession = activity.xmppConnectionService.getJingleConnectionManager().getOngoingRtpConnection(conversation.getContact());
final XmppConnectionService service = activity.xmppConnectionService;
final Optional<OngoingRtpSession> ongoingRtpSession = service == null ? Optional.absent() : service.getJingleConnectionManager().getOngoingRtpConnection(conversation.getContact());
if (ongoingRtpSession.isPresent()) {
menuOngoingCall.setVisible(true);
menuCall.setVisible(false);
@ -998,7 +999,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
menuContactDetails.setVisible(!this.conversation.withSelf());
menuMucDetails.setVisible(false);
final XmppConnectionService service = activity.xmppConnectionService;
menuInviteContact.setVisible(service != null && service.findConferenceServer(conversation.getAccount()) != null);
}
if (conversation.isMuted()) {

View File

@ -48,6 +48,7 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.google.common.collect.Collections2;
@ -371,7 +372,10 @@ public class ConversationsOverviewFragment extends XmppFragment {
private void selectAccountToStartEasyInvite() {
final List<Account> accounts = EasyOnboardingInvite.getSupportingAccounts(activity.xmppConnectionService);
if (accounts.size() == 1) {
if (accounts.size() == 0) {
//This can technically happen if opening the menu item races with accounts reconnecting or something
Toast.makeText(getActivity(),R.string.no_active_accounts_support_this, Toast.LENGTH_LONG).show();
} else if (accounts.size() == 1) {
openEasyInviteScreen(accounts.get(0));
} else {
final AtomicReference<Account> selectedAccount = new AtomicReference<>(accounts.get(0));

View File

@ -39,6 +39,7 @@ import android.text.TextWatcher;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
@ -130,7 +131,8 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
public void onCreateContextMenu(final ContextMenu menu, final View v, ContextMenu.ContextMenuInfo menuInfo) {
v.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0));
AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo) menuInfo;
final Message message = this.messages.get(acmi.position);
this.selectedMessageReference = new WeakReference<>(message);

View File

@ -368,12 +368,13 @@ public abstract class XmppActivity extends ActionBarActivity {
public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) {
final Contact contact = conversation.getContact();
if (!contact.showInRoster()) {
showAddToRosterDialog(conversation.getContact());
} else {
if (contact.showInRoster() || contact.isSelf()) {
final Presences presences = contact.getPresences();
if (presences.size() == 0) {
if (!contact.getOption(Contact.Options.TO)
if (contact.isSelf()) {
conversation.setNextCounterpart(null);
listener.onPresenceSelected();
} else if (!contact.getOption(Contact.Options.TO)
&& !contact.getOption(Contact.Options.ASKING)
&& contact.getAccount().getStatus() == Account.State.ONLINE) {
showAskForPresenceDialog(contact);
@ -391,6 +392,8 @@ public abstract class XmppActivity extends ActionBarActivity {
} else {
PresenceSelector.showPresenceSelectionDialog(this, conversation, listener);
}
} else {
showAddToRosterDialog(conversation.getContact());
}
}

View File

@ -29,7 +29,7 @@ public class SSLSocketHelper {
final Collection<String> supportedProtocols = new LinkedList<>(
Arrays.asList(sslSocket.getSupportedProtocols()));
supportedProtocols.remove("SSLv3");
supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]);
supportProtocols = supportedProtocols.toArray(new String[0]);
sslSocket.setEnabledProtocols(supportProtocols);

View File

@ -64,6 +64,7 @@ import eu.siacs.conversations.crypto.sasl.Plain;
import eu.siacs.conversations.crypto.sasl.SaslMechanism;
import eu.siacs.conversations.crypto.sasl.ScramSha1;
import eu.siacs.conversations.crypto.sasl.ScramSha256;
import eu.siacs.conversations.crypto.sasl.ScramSha512;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
@ -106,32 +107,29 @@ public class XmppConnection implements Runnable {
private static final int PACKET_IQ = 0;
private static final int PACKET_MESSAGE = 1;
private static final int PACKET_PRESENCE = 2;
public final OnIqPacketReceived registrationResponseListener = new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.RESULT) {
account.setOption(Account.OPTION_REGISTER, false);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully registered new account on server");
throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL);
} else {
final List<String> PASSWORD_TOO_WEAK_MSGS = Arrays.asList(
"The password is too weak",
"Please use a longer password.");
Element error = packet.findChild("error");
Account.State state = Account.State.REGISTRATION_FAILED;
if (error != null) {
if (error.hasChild("conflict")) {
state = Account.State.REGISTRATION_CONFLICT;
} else if (error.hasChild("resource-constraint")
&& "wait".equals(error.getAttribute("type"))) {
state = Account.State.REGISTRATION_PLEASE_WAIT;
} else if (error.hasChild("not-acceptable")
&& PASSWORD_TOO_WEAK_MSGS.contains(error.findChildContent("text"))) {
state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK;
}
public final OnIqPacketReceived registrationResponseListener = (account, packet) -> {
if (packet.getType() == IqPacket.TYPE.RESULT) {
account.setOption(Account.OPTION_REGISTER, false);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": successfully registered new account on server");
throw new StateChangingError(Account.State.REGISTRATION_SUCCESSFUL);
} else {
final List<String> PASSWORD_TOO_WEAK_MSGS = Arrays.asList(
"The password is too weak",
"Please use a longer password.");
Element error = packet.findChild("error");
Account.State state = Account.State.REGISTRATION_FAILED;
if (error != null) {
if (error.hasChild("conflict")) {
state = Account.State.REGISTRATION_CONFLICT;
} else if (error.hasChild("resource-constraint")
&& "wait".equals(error.getAttribute("type"))) {
state = Account.State.REGISTRATION_PLEASE_WAIT;
} else if (error.hasChild("not-acceptable")
&& PASSWORD_TOO_WEAK_MSGS.contains(error.findChildContent("text"))) {
state = Account.State.REGISTRATION_PASSWORD_TOO_WEAK;
}
throw new StateChangingError(state);
}
throw new StateChangingError(state);
}
};
protected final Account account;
@ -159,10 +157,10 @@ public class XmppConnection implements Runnable {
private long lastSessionStarted = 0;
private long lastDiscoStarted = 0;
private boolean isMamPreferenceAlways = false;
private AtomicInteger mPendingServiceDiscoveries = new AtomicInteger(0);
private AtomicBoolean mWaitForDisco = new AtomicBoolean(true);
private AtomicBoolean mWaitingForSmCatchup = new AtomicBoolean(false);
private AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0);
private final AtomicInteger mPendingServiceDiscoveries = new AtomicInteger(0);
private final AtomicBoolean mWaitForDisco = new AtomicBoolean(true);
private final AtomicBoolean mWaitingForSmCatchup = new AtomicBoolean(false);
private final AtomicInteger mSmCatchupMessageCounter = new AtomicInteger(0);
private boolean mInteractive = false;
private int attempt = 0;
private OnPresencePacketReceived presenceListener = null;
@ -744,7 +742,7 @@ public class XmppConnection implements Runnable {
}
}
private void processMessage(final Tag currentTag) throws XmlPullParserException, IOException {
private void processMessage(final Tag currentTag) throws IOException {
final MessagePacket packet = (MessagePacket) processPacket(currentTag, PACKET_MESSAGE);
if (!packet.valid()) {
Log.e(Config.LOGTAG, "encountered invalid message from='" + packet.getFrom() + "' to='" + packet.getTo() + "'");
@ -753,7 +751,7 @@ public class XmppConnection implements Runnable {
this.messageListener.onMessagePacketReceived(account, packet);
}
private void processPresence(final Tag currentTag) throws XmlPullParserException, IOException {
private void processPresence(final Tag currentTag) throws IOException {
PresencePacket packet = (PresencePacket) processPacket(currentTag, PACKET_PRESENCE);
if (!packet.valid()) {
Log.e(Config.LOGTAG, "encountered invalid presence from='" + packet.getFrom() + "' to='" + packet.getTo() + "'");
@ -807,7 +805,7 @@ public class XmppConnection implements Runnable {
return sslSocket;
}
private void processStreamFeatures(final Tag currentTag) throws XmlPullParserException, IOException {
private void processStreamFeatures(final Tag currentTag) throws IOException {
this.streamFeatures = tagReader.readElement(currentTag);
final boolean isSecure = features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS || account.isOnion();
final boolean needsBinding = !isBound && !account.isOptionSet(Account.OPTION_REGISTER);
@ -843,20 +841,21 @@ public class XmppConnection implements Runnable {
}
private void authenticate() throws IOException {
final List<String> mechanisms = extractMechanisms(streamFeatures
.findChild("mechanisms"));
final List<String> mechanisms = extractMechanisms(streamFeatures.findChild("mechanisms"));
final Element auth = new Element("auth", Namespace.SASL);
if (mechanisms.contains("EXTERNAL") && account.getPrivateKeyAlias() != null) {
if (mechanisms.contains(External.MECHANISM) && account.getPrivateKeyAlias() != null) {
saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG());
} else if (mechanisms.contains("SCRAM-SHA-256")) {
} else if (mechanisms.contains(ScramSha512.MECHANISM)) {
saslMechanism = new ScramSha512(tagWriter, account, mXmppConnectionService.getRNG());
} else if (mechanisms.contains(ScramSha256.MECHANISM)) {
saslMechanism = new ScramSha256(tagWriter, account, mXmppConnectionService.getRNG());
} else if (mechanisms.contains("SCRAM-SHA-1")) {
} else if (mechanisms.contains(ScramSha1.MECHANISM)) {
saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG());
} else if (mechanisms.contains("PLAIN") && !account.getJid().getDomain().toEscapedString().equals("nimbuzz.com")) {
} else if (mechanisms.contains(Plain.MECHANISM) && !account.getJid().getDomain().toEscapedString().equals("nimbuzz.com")) {
saslMechanism = new Plain(tagWriter, account);
} else if (mechanisms.contains("DIGEST-MD5")) {
} else if (mechanisms.contains(DigestMd5.MECHANISM)) {
saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG());
} else if (mechanisms.contains("ANONYMOUS")) {
} else if (mechanisms.contains(Anonymous.MECHANISM)) {
saslMechanism = new Anonymous(tagWriter, account, mXmppConnectionService.getRNG());
}
if (saslMechanism != null) {
@ -1238,27 +1237,27 @@ public class XmppConnection implements Runnable {
request.setTo(account.getDomain());
request.addChild("query", Namespace.DISCO_ITEMS).setAttribute("node", Namespace.COMMANDS);
sendIqPacket(request, (account, response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) {
final Element query = response.findChild("query",Namespace.DISCO_ITEMS);
if (query == null) {
return;
}
final HashMap<String, Jid> commands = new HashMap<>();
for(final Element child : query.getChildren()) {
if ("item".equals(child.getName())) {
final String node = child.getAttribute("node");
final Jid jid = child.getAttributeAsJid("jid");
if (node != null && jid != null) {
commands.put(node, jid);
}
}
}
Log.d(Config.LOGTAG,commands.toString());
synchronized (this.commands) {
this.commands.clear();
this.commands.putAll(commands);
}
}
if (response.getType() == IqPacket.TYPE.RESULT) {
final Element query = response.findChild("query", Namespace.DISCO_ITEMS);
if (query == null) {
return;
}
final HashMap<String, Jid> commands = new HashMap<>();
for (final Element child : query.getChildren()) {
if ("item".equals(child.getName())) {
final String node = child.getAttribute("node");
final Jid jid = child.getAttributeAsJid("jid");
if (node != null && jid != null) {
commands.put(node, jid);
}
}
}
Log.d(Config.LOGTAG, commands.toString());
synchronized (this.commands) {
this.commands.clear();
this.commands.putAll(commands);
}
}
});
}
@ -1297,7 +1296,7 @@ public class XmppConnection implements Runnable {
iq.query("http://jabber.org/protocol/disco#items");
this.sendIqPacket(iq, (account, packet) -> {
if (packet.getType() == IqPacket.TYPE.RESULT) {
HashSet<Jid> items = new HashSet<Jid>();
final HashSet<Jid> items = new HashSet<>();
final List<Element> elements = packet.query().getChildren();
for (final Element element : elements) {
if (element.getName().equals("item")) {
@ -1325,23 +1324,19 @@ public class XmppConnection implements Runnable {
private void sendEnableCarbons() {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
iq.addChild("enable", "urn:xmpp:carbons:2");
this.sendIqPacket(iq, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
if (!packet.hasChild("error")) {
Log.d(Config.LOGTAG, account.getJid().asBareJid()
+ ": successfully enabled carbons");
features.carbonsEnabled = true;
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid()
+ ": error enableing carbons " + packet.toString());
}
this.sendIqPacket(iq, (account, packet) -> {
if (!packet.hasChild("error")) {
Log.d(Config.LOGTAG, account.getJid().asBareJid()
+ ": successfully enabled carbons");
features.carbonsEnabled = true;
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid()
+ ": error enableing carbons " + packet.toString());
}
});
}
private void processStreamError(final Tag currentTag) throws XmlPullParserException, IOException {
private void processStreamError(final Tag currentTag) throws IOException {
final Element streamError = tagReader.readElement(currentTag);
if (streamError == null) {
return;
@ -1594,8 +1589,8 @@ public class XmppConnection implements Runnable {
}
public List<String> getMucServersWithholdAccount() {
List<String> servers = getMucServers();
servers.remove(account.getDomain());
final List<String> servers = getMucServers();
servers.remove(account.getDomain().toEscapedString());
return servers;
}
@ -1766,7 +1761,7 @@ public class XmppConnection implements Runnable {
}
}
private class StateChangingError extends Error {
private static class StateChangingError extends Error {
private final Account.State state;
public StateChangingError(Account.State state) {
@ -1774,7 +1769,7 @@ public class XmppConnection implements Runnable {
}
}
private class StateChangingException extends IOException {
private static class StateChangingException extends IOException {
private final Account.State state;
public StateChangingException(Account.State state) {

View File

@ -143,14 +143,16 @@ public class SessionDescription {
final ArrayListMultimap<String, String> mediaAttributes = ArrayListMultimap.create();
final String ufrag = transport.getAttribute("ufrag");
final String pwd = transport.getAttribute("pwd");
if (!Strings.isNullOrEmpty(ufrag)) {
mediaAttributes.put("ice-ufrag", ufrag);
if (Strings.isNullOrEmpty(ufrag)) {
throw new IllegalArgumentException("Transport element is missing required ufrag attribute");
}
checkNoWhitespace(ufrag, "ufrag value must not contain any whitespaces");
if (!Strings.isNullOrEmpty(pwd)) {
mediaAttributes.put("ice-pwd", pwd);
mediaAttributes.put("ice-ufrag", ufrag);
if (Strings.isNullOrEmpty(pwd)) {
throw new IllegalArgumentException("Transport element is missing required pwd attribute");
}
checkNoWhitespace(pwd, "pwd value must not contain any whitespaces");
mediaAttributes.put("ice-pwd", pwd);
mediaAttributes.put("ice-options", HARDCODED_ICE_OPTIONS);
final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint();
if (fingerprint != null) {

View File

@ -14,6 +14,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
@ -93,6 +94,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
if (pair.length == 2 && "candidate".equals(pair[0])) {
final String[] segments = pair[1].split(" ");
if (segments.length >= 6) {
final String id = UUID.randomUUID().toString();
final String foundation = segments[0];
final String component = segments[1];
final String transport = segments[2].toLowerCase(Locale.ROOT);
@ -109,6 +111,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
candidate.setAttribute("generation", additional.get("generation"));
candidate.setAttribute("rel-addr", additional.get("raddr"));
candidate.setAttribute("rel-port", additional.get("rport"));
candidate.setAttribute("id", id);
candidate.setAttribute("ip", connectionAddress);
candidate.setAttribute("port", port);
candidate.setAttribute("priority", priority);

View File

@ -4,7 +4,9 @@
<string name="action_add">Nová konverzace</string>
<string name="action_accounts">Nastavení účtů</string>
<string name="action_account">Nastavení účtu</string>
<string name="action_end_conversation">Zavřít konverzaci</string>
<string name="action_contact_details">Detaily kontaktu</string>
<string name="action_muc_details">Detaily skupinového chatu</string>
<string name="action_add_account">Přidat účet</string>
<string name="action_edit_contact">Upravit jméno</string>
<string name="action_add_phone_book">Přidat do adresáře</string>
@ -17,6 +19,7 @@
<string name="title_activity_settings">Nastavení</string>
<string name="title_activity_sharewith">Sdílet s konverzací</string>
<string name="title_activity_start_conversation">Začít konverzaci</string>
<string name="title_activity_choose_contact">Vybrat kontakt</string>
<string name="title_activity_block_list">Seznam blokovaných</string>
<string name="just_now">právě teď</string>
<string name="minute_ago">před minutou</string>
@ -30,14 +33,17 @@
<string name="moderator">Moderátor</string>
<string name="participant">Účastník</string>
<string name="visitor">Návštěvník</string>
<string name="remove_contact_text">Přejete si odstranit %s ze seznamu kontaktů? Předešlé rozhovory nebudou odstraněny.</string>
<string name="block_contact_text">Chcete zablokovat příjem zpráv od %s?</string>
<string name="unblock_contact_text">Chcete odblokovat příjem zpráv od %s?</string>
<string name="block_domain_text">Zablokovat všechny kontakty z %s?</string>
<string name="unblock_domain_text">Odblokovat všechny kontakty z %s?</string>
<string name="contact_blocked">Kontakty zablokovány</string>
<string name="remove_bookmark_text">Přejete si odstranit %s ze záložek? Předešlé rozhovory pod záložkou nebudou odstraněny.</string>
<string name="register_account">Registrovat nový účet na serveru</string>
<string name="change_password_on_server">Změnit heslo na serveru</string>
<string name="share_with">Sdílet s...</string>
<string name="start_conversation">Začít konverzaci</string>
<string name="invite_contact">Pozvat kontakt</string>
<string name="contacts">Kontakty</string>
<string name="cancel">Zrušit</string>
@ -144,9 +150,14 @@
<string name="server_info_unavailable">nedostupný</string>
<string name="missing_public_keys">Chybí oznámení o veřejném klíči</string>
<string name="last_seen_now">právě spatřen</string>
<string name="last_seen_min">naposledy spatřen před minutou</string>
<string name="last_seen_mins">naposledy spatřen před %d minutami</string>
<string name="last_seen_hour">naposledy spatřen před hodinou</string>
<string name="last_seen_hours">naposledy spatřen před %d hodinami</string>
<string name="last_seen_day">naposledy spatřen včera</string>
<string name="last_seen_days">naposledy spatřen před %d dny</string>
<string name="install_openkeychain">Šifrovaná zpráva. Nainstalujte OpenKeychain pro její dešifrování.</string>
<string name="openpgp_messages_found">Nalezeny nové OpenPGP šifrované zprávy</string>
<string name="openpgp_key_id">OpenPGP ID klíče</string>
<string name="omemo_fingerprint">OMEMO otisk</string>
<string name="omemo_fingerprint_x509">v\\OMEMO otisk</string>
@ -160,6 +171,7 @@
<string name="bookmarks">Záložky</string>
<string name="search">Hledat</string>
<string name="enter_contact">Vložit kontakt</string>
<string name="delete_contact">Smazat kontakt</string>
<string name="view_contact_details">Zobrazit detaily kontaktu</string>
<string name="block_contact">Zablokovat kontakt</string>
<string name="unblock_contact">Odblokovat kontakt</string>

View File

@ -951,4 +951,5 @@
<string name="invite_to_app">Einladung zu Conversations</string>
<string name="unable_to_parse_invite">Einladung kann nicht gelesen werden</string>
<string name="server_does_not_support_easy_onboarding_invites">Server unterstützt keine Generierung von Einladungen</string>
<string name="no_active_accounts_support_this">Keine aktiven Konten unterstützen diese Funktion</string>
</resources>

View File

@ -951,4 +951,5 @@
<string name="invite_to_app">Convida a Conversations</string>
<string name="unable_to_parse_invite">Non se puido enviar o convite</string>
<string name="server_does_not_support_easy_onboarding_invites">O servidor non soporta a creación de convites</string>
<string name="no_active_accounts_support_this">Ningunha conta activa soporta esta función</string>
</resources>

View File

@ -948,4 +948,7 @@
<string name="failed_deliveries">Recapiti falliti</string>
<string name="more_options">Altre opzioni</string>
<string name="no_application_found">Nessuna applicazione trovata</string>
<string name="invite_to_app">Invita su Conversations</string>
<string name="unable_to_parse_invite">Impossibile analizzare l\'invito</string>
<string name="server_does_not_support_easy_onboarding_invites">Il server non supporta la generazione di inviti</string>
</resources>

View File

@ -970,4 +970,8 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż
<string name="failed_deliveries">Nie dostarczone wiadomości</string>
<string name="more_options">Więcej ustawień</string>
<string name="no_application_found">Nie znaleziono żadnej aplikacji</string>
<string name="invite_to_app">Zaproś do Conversations</string>
<string name="unable_to_parse_invite">Nie można przetworzyć zaproszenia</string>
<string name="server_does_not_support_easy_onboarding_invites">Serwer nie wspiera tworzenia zaproszeń</string>
<string name="no_active_accounts_support_this">Nie ma aktywnych kont wspierających tę funkcję</string>
</resources>

View File

@ -947,4 +947,9 @@
</plurals>
<string name="failed_deliveries">Entregas não efetuadas</string>
<string name="more_options">Mais opções</string>
<string name="no_application_found">Não foi encontrado nenhum aplicativo</string>
<string name="invite_to_app">Convidar para o Conversations</string>
<string name="unable_to_parse_invite">Não foi possível processar o convite</string>
<string name="server_does_not_support_easy_onboarding_invites">O servidor não suporta a criação de convites</string>
<string name="no_active_accounts_support_this">Nenhuma conta ativa suporta esse recurso</string>
</resources>

View File

@ -961,4 +961,5 @@
<string name="invite_to_app">Invitați la Conversations</string>
<string name="unable_to_parse_invite">Nu s-a putut procesa invitația</string>
<string name="server_does_not_support_easy_onboarding_invites">Serverul nu suportă generarea de invitații</string>
<string name="no_active_accounts_support_this">Nici un cont activ nu suporta această caracteristică</string>
</resources>

View File

@ -974,4 +974,5 @@
<string name="invite_to_app">Conversations\'a davet et</string>
<string name="unable_to_parse_invite">Davet iletilemedi</string>
<string name="server_does_not_support_easy_onboarding_invites">Sunucu, davet oluşturulmasını desteklemiyor</string>
<string name="no_active_accounts_support_this">Bu özelliği destekleyen aktif bir hesap yok</string>
</resources>

View File

@ -953,4 +953,5 @@
<string name="invite_to_app">Invite to Conversations</string>
<string name="unable_to_parse_invite">Unable to parse invite</string>
<string name="server_does_not_support_easy_onboarding_invites">Server does not support generating invites</string>
<string name="no_active_accounts_support_this">No active accounts support this feature</string>
</resources>