Merge tag '2.5.8' into develop

This commit is contained in:
Martin/Geno 2019-09-12 08:23:37 +00:00
commit fa0452761f
No known key found for this signature in database
GPG Key ID: E7907CD4A3C7A6C1
50 changed files with 565 additions and 318 deletions

View File

@ -1,5 +1,10 @@
# Changelog # Changelog
## Version 2.5.8
* fixed connection issues over Tor
* P2P file transfer (Jingle) now offers direct candidates
* Support XEP-0396: Jingle Encrypted Transports - OMEMO
### Version 2.5.7 ### Version 2.5.7
* fixed crash when scanning QR codes on Android 6 and lower * fixed crash when scanning QR codes on Android 6 and lower
* when sharing a message from and to Conversations insert it as quote * when sharing a message from and to Conversations insert it as quote

View File

@ -83,8 +83,8 @@ android {
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 25 targetSdkVersion 25
versionCode 337 versionCode 338
versionName "2.5.7" versionName "2.5.8"
archivesBaseName += "-$versionName" archivesBaseName += "-$versionName"
applicationId "eu.sum7.conversations" applicationId "eu.sum7.conversations"
resValue "string", "applicationId", applicationId resValue "string", "applicationId", applicationId

View File

@ -101,6 +101,7 @@ public final class Config {
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true;
public static final boolean DISABLE_HTTP_UPLOAD = false; public static final boolean DISABLE_HTTP_UPLOAD = false;
public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts
public static final boolean BACKGROUND_STANZA_LOGGING = false; //log all stanzas that were received while the app is in background public static final boolean BACKGROUND_STANZA_LOGGING = false; //log all stanzas that were received while the app is in background

View File

@ -29,6 +29,8 @@ public abstract class AbstractGenerator {
Content.Version.FT_5.getNamespace(), Content.Version.FT_5.getNamespace(),
Namespace.JINGLE_TRANSPORTS_S5B, Namespace.JINGLE_TRANSPORTS_S5B,
Namespace.JINGLE_TRANSPORTS_IBB, Namespace.JINGLE_TRANSPORTS_IBB,
Namespace.JINGLE_ENCRYPTED_TRANSPORT,
Namespace.JINGLE_ENCRYPTED_TRANSPORT_OMEMO,
"http://jabber.org/protocol/muc", "http://jabber.org/protocol/muc",
"jabber:x:conference", "jabber:x:conference",
Namespace.OOB, Namespace.OOB,

View File

@ -39,6 +39,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.URL; import java.net.URL;
import java.security.DigestOutputStream; import java.security.DigestOutputStream;
@ -359,6 +360,15 @@ public class FileBackend {
} }
} }
public static void close(final ServerSocket socket) {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
}
}
}
public static boolean weOwnFile(Context context, Uri uri) { public static boolean weOwnFile(Context context, Uri uri) {
if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return false; return false;

View File

@ -26,6 +26,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.CryptoHelper;
public class AbstractConnectionManager { public class AbstractConnectionManager {

View File

@ -11,7 +11,6 @@ import android.content.SharedPreferences;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.Icon;
import android.media.AudioAttributes; import android.media.AudioAttributes;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
@ -23,7 +22,6 @@ import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.BigPictureStyle; import android.support.v4.app.NotificationCompat.BigPictureStyle;
import android.support.v4.app.NotificationCompat.Builder; import android.support.v4.app.NotificationCompat.Builder;
import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation;
import android.support.v4.app.Person; import android.support.v4.app.Person;
import android.support.v4.app.RemoteInput; import android.support.v4.app.RemoteInput;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
@ -32,7 +30,6 @@ import android.text.SpannableString;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -64,7 +61,6 @@ import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection;
import rocks.xmpp.addr.Jid;
public class NotificationService { public class NotificationService {
@ -469,10 +465,7 @@ public class NotificationService {
private Builder buildMultipleConversation(final boolean notify, final boolean quietHours) { private Builder buildMultipleConversation(final boolean notify, final boolean quietHours) {
final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages")); final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
style.setBigContentTitle(notifications.size() style.setBigContentTitle(mXmppConnectionService.getString(R.string.x_unread_conversations,notifications.size()));
+ " "
+ mXmppConnectionService
.getString(R.string.unread_conversations));
final StringBuilder names = new StringBuilder(); final StringBuilder names = new StringBuilder();
Conversation conversation = null; Conversation conversation = null;
for (final ArrayList<Message> messages : notifications.values()) { for (final ArrayList<Message> messages : notifications.values()) {
@ -497,10 +490,8 @@ public class NotificationService {
if (names.length() >= 2) { if (names.length() >= 2) {
names.delete(names.length() - 2, names.length()); names.delete(names.length() - 2, names.length());
} }
mBuilder.setContentTitle(notifications.size() mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.x_unread_conversations, notifications.size()));
+ " " mBuilder.setTicker(mXmppConnectionService.getString(R.string.x_unread_conversations, notifications.size()));
+ mXmppConnectionService
.getString(R.string.unread_conversations));
mBuilder.setContentText(names.toString()); mBuilder.setContentText(names.toString());
mBuilder.setStyle(style); mBuilder.setStyle(style);
if (conversation != null) { if (conversation != null) {
@ -627,8 +618,11 @@ public class NotificationService {
CharSequence text = getMergedBodies(tmp); CharSequence text = getMergedBodies(tmp);
bigPictureStyle.setSummaryText(text); bigPictureStyle.setSummaryText(text);
builder.setContentText(text); builder.setContentText(text);
builder.setTicker(text);
} else { } else {
builder.setContentText(UIHelper.getFileDescriptionString(mXmppConnectionService, message)); final String description = UIHelper.getFileDescriptionString(mXmppConnectionService, message);
builder.setContentText(description);
builder.setTicker(description);
} }
builder.setStyle(bigPictureStyle); builder.setStyle(bigPictureStyle);
} catch (final IOException e) { } catch (final IOException e) {
@ -685,7 +679,9 @@ public class NotificationService {
} else { } else {
if (messages.get(0).getConversation().getMode() == Conversation.MODE_SINGLE) { if (messages.get(0).getConversation().getMode() == Conversation.MODE_SINGLE) {
builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages))); builder.setStyle(new NotificationCompat.BigTextStyle().bigText(getMergedBodies(messages)));
builder.setContentText(UIHelper.getMessagePreview(mXmppConnectionService, messages.get(messages.size()-1)).first); final CharSequence preview = UIHelper.getMessagePreview(mXmppConnectionService, messages.get(messages.size()-1)).first;
builder.setContentText(preview);
builder.setTicker(preview);
builder.setNumber(messages.size()); builder.setNumber(messages.size());
} else { } else {
final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
@ -703,8 +699,11 @@ public class NotificationService {
styledString = new SpannableString(name + ": " + messages.get(0).getBody()); styledString = new SpannableString(name + ": " + messages.get(0).getBody());
styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0); styledString.setSpan(new StyleSpan(Typeface.BOLD), 0, name.length(), 0);
builder.setContentText(styledString); builder.setContentText(styledString);
builder.setTicker(styledString);
} else { } else {
builder.setContentText(mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count)); final String text = mXmppConnectionService.getResources().getQuantityString(R.plurals.x_messages, count, count);
builder.setContentText(text);
builder.setTicker(text);
} }
} }
} }

View File

@ -66,12 +66,16 @@ public class Resolver {
Result result = new Result(); Result result = new Result();
result.hostname = DNSName.from(hostname); result.hostname = DNSName.from(hostname);
result.port = port; result.port = port;
result.directTls = port == 443 || port == 5223; result.directTls = useDirectTls(port);
result.authenticated = true; result.authenticated = true;
return Collections.singletonList(result); return Collections.singletonList(result);
} }
public static boolean useDirectTls(final int port) {
return port == 443 || port == 5223;
}
public static List<Result> resolve(String domain) { public static List<Result> resolve(String domain) {
final List<Result> ipResults = fromIpAddress(domain); final List<Result> ipResults = fromIpAddress(domain);
if (ipResults.size() > 0) { if (ipResults.size() > 0) {

View File

@ -20,6 +20,9 @@ public class SocksSocketFactory {
proxyOs.write(new byte[]{0x05, 0x01, 0x00}); proxyOs.write(new byte[]{0x05, 0x01, 0x00});
byte[] response = new byte[2]; byte[] response = new byte[2];
proxyIs.read(response); proxyIs.read(response);
if (response[0] != 0x05 || response[1] != 0x00) {
throw new SocksConnectionException("Socks 5 handshake failed");
}
byte[] dest = destination.getBytes(); byte[] dest = destination.getBytes();
ByteBuffer request = ByteBuffer.allocate(7 + dest.length); ByteBuffer request = ByteBuffer.allocate(7 + dest.length);
request.put(new byte[]{0x05, 0x01, 0x00, 0x03}); request.put(new byte[]{0x05, 0x01, 0x00, 0x03});
@ -30,10 +33,25 @@ public class SocksSocketFactory {
response = new byte[7 + dest.length]; response = new byte[7 + dest.length];
proxyIs.read(response); proxyIs.read(response);
if (response[1] != 0x00) { if (response[1] != 0x00) {
throw new SocksConnectionException(); if (response[1] == 0x04) {
throw new HostNotFoundException("Host unreachable");
}
if (response[1] == 0x05) {
throw new HostNotFoundException("Connection refused");
}
throw new SocksConnectionException("Unable to connect to destination "+(int) (response[1]));
} }
} }
public static boolean contains(byte needle, byte[] haystack) {
for(byte hay : haystack) {
if (hay == needle) {
return true;
}
}
return false;
}
public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException { public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException {
Socket socket = new Socket(); Socket socket = new Socket();
try { try {
@ -49,11 +67,19 @@ public class SocksSocketFactory {
return createSocket(new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050), destination, port); return createSocket(new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050), destination, port);
} }
static class SocksConnectionException extends IOException { private static class SocksConnectionException extends IOException {
SocksConnectionException(String message) {
super(message);
}
} }
public static class SocksProxyNotFoundException extends IOException { public static class SocksProxyNotFoundException extends IOException {
} }
public static class HostNotFoundException extends SocksConnectionException {
HostNotFoundException(String message) {
super(message);
}
}
} }

View File

@ -30,4 +30,6 @@ public final class Namespace {
public static final String PING = "urn:xmpp:ping"; public static final String PING = "urn:xmpp:ping";
public static final String PUSH = "urn:xmpp:push:0"; public static final String PUSH = "urn:xmpp:push:0";
public static final String COMMANDS = "http://jabber.org/protocol/commands"; public static final String COMMANDS = "http://jabber.org/protocol/commands";
public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0";
public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0";
} }

View File

@ -267,8 +267,18 @@ public class XmppConnection implements Runnable {
destination = account.getHostname(); destination = account.getHostname();
this.verifiedHostname = destination; this.verifiedHostname = destination;
} }
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": connect to " + destination + " via Tor");
localSocket = SocksSocketFactory.createSocketOverTor(destination, account.getPort()); final int port = account.getPort();
final boolean directTls = Resolver.useDirectTls(port);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": connect to " + destination + " via Tor. directTls="+directTls);
localSocket = SocksSocketFactory.createSocketOverTor(destination, port);
if (directTls) {
localSocket = upgradeSocketToTls(localSocket);
features.encryptionEnabled = true;
}
try { try {
startXmpp(localSocket); startXmpp(localSocket);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -310,29 +320,13 @@ public class XmppConnection implements Runnable {
+ ": using values from resolver " + ": using values from resolver "
+ result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); + result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled);
if (!features.encryptionEnabled) { localSocket = new Socket();
localSocket = new Socket(); localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
} else {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
localSocket = tlsFactoryVerifier.factory.createSocket();
if (localSocket == null) { if (features.encryptionEnabled) {
throw new IOException("could not initialize ssl socket"); localSocket = upgradeSocketToTls(localSocket);
}
SSLSocketHelper.setSecurity((SSLSocket) localSocket);
SSLSocketHelper.setHostname((SSLSocket) localSocket, account.getServer());
SSLSocketHelper.setApplicationProtocol((SSLSocket) localSocket, "xmpp-client");
localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
if (!tlsFactoryVerifier.verifier.verify(account.getServer(), verifiedHostname, ((SSLSocket) localSocket).getSession())) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed");
FileBackend.close(localSocket);
throw new StateChangingException(Account.State.TLS_ERROR);
}
} }
localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000); localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000);
if (startXmpp(localSocket)) { if (startXmpp(localSocket)) {
localSocket.setSoTimeout(0); //reset to 0; once the connection is established we dont want this localSocket.setSoTimeout(0); //reset to 0; once the connection is established we dont want this
@ -363,6 +357,8 @@ public class XmppConnection implements Runnable {
this.changeStatus(e.state); this.changeStatus(e.state);
} catch (final UnknownHostException | ConnectException e) { } catch (final UnknownHostException | ConnectException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND); this.changeStatus(Account.State.SERVER_NOT_FOUND);
} catch (final SocksSocketFactory.HostNotFoundException e) {
this.changeStatus(Account.State.SERVER_NOT_FOUND);
} catch (final SocksSocketFactory.SocksProxyNotFoundException e) { } catch (final SocksSocketFactory.SocksProxyNotFoundException e) {
this.changeStatus(Account.State.TOR_NOT_AVAILABLE); this.changeStatus(Account.State.TOR_NOT_AVAILABLE);
} catch (final IOException | XmlPullParserException e) { } catch (final IOException | XmlPullParserException e) {
@ -775,46 +771,41 @@ public class XmppConnection implements Runnable {
private void switchOverToTls() throws XmlPullParserException, IOException { private void switchOverToTls() throws XmlPullParserException, IOException {
tagReader.readTag(); tagReader.readTag();
final Socket socket = this.socket;
final SSLSocket sslSocket = upgradeSocketToTls(socket);
tagReader.setInputStream(sslSocket.getInputStream());
tagWriter.setOutputStream(sslSocket.getOutputStream());
sendStartStream();
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS connection established");
features.encryptionEnabled = true;
final Tag tag = tagReader.readTag();
if (tag != null && tag.isStart("stream")) {
SSLSocketHelper.log(account, sslSocket);
processStream();
} else {
throw new StateChangingException(Account.State.STREAM_OPENING_ERROR);
}
sslSocket.close();
}
private SSLSocket upgradeSocketToTls(final Socket socket) throws IOException {
final TlsFactoryVerifier tlsFactoryVerifier;
try { try {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); tlsFactoryVerifier = getTlsFactoryVerifier();
final InetAddress address = socket == null ? null : socket.getInetAddress(); } catch (final NoSuchAlgorithmException | KeyManagementException e) {
if (address == null) {
throw new IOException("could not setup ssl");
}
final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true);
if (sslSocket == null) {
throw new IOException("could not initialize ssl socket");
}
SSLSocketHelper.setSecurity(sslSocket);
SSLSocketHelper.setHostname(sslSocket, account.getServer());
SSLSocketHelper.setApplicationProtocol(sslSocket, "xmpp-client");
if (!tlsFactoryVerifier.verifier.verify(account.getServer(), this.verifiedHostname, sslSocket.getSession())) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed");
throw new StateChangingException(Account.State.TLS_ERROR);
}
tagReader.setInputStream(sslSocket.getInputStream());
tagWriter.setOutputStream(sslSocket.getOutputStream());
sendStartStream();
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS connection established");
features.encryptionEnabled = true;
final Tag tag = tagReader.readTag();
if (tag != null && tag.isStart("stream")) {
SSLSocketHelper.log(account, sslSocket);
processStream();
} else {
throw new StateChangingException(Account.State.STREAM_OPENING_ERROR);
}
sslSocket.close();
} catch (final NoSuchAlgorithmException | KeyManagementException e1) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed");
throw new StateChangingException(Account.State.TLS_ERROR); throw new StateChangingException(Account.State.TLS_ERROR);
} }
final InetAddress address = socket.getInetAddress();
final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true);
SSLSocketHelper.setSecurity(sslSocket);
SSLSocketHelper.setHostname(sslSocket, account.getServer());
SSLSocketHelper.setApplicationProtocol(sslSocket, "xmpp-client");
if (!tlsFactoryVerifier.verifier.verify(account.getServer(), this.verifiedHostname, sslSocket.getSession())) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed");
FileBackend.close(sslSocket);
throw new StateChangingException(Account.State.TLS_ERROR);
}
return sslSocket;
} }
private void processStreamFeatures(final Tag currentTag) throws XmlPullParserException, IOException { private void processStreamFeatures(final Tag currentTag) throws XmlPullParserException, IOException {

View File

@ -0,0 +1,64 @@
package eu.siacs.conversations.xmpp.jingle;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.UUID;
import rocks.xmpp.addr.Jid;
public class DirectConnectionUtils {
private static List<InetAddress> getLocalAddresses() {
final List<InetAddress> addresses = new ArrayList<>();
final Enumeration<NetworkInterface> interfaces;
try {
interfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
return addresses;
}
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
final Enumeration<InetAddress> inetAddressEnumeration = networkInterface.getInetAddresses();
while (inetAddressEnumeration.hasMoreElements()) {
final InetAddress inetAddress = inetAddressEnumeration.nextElement();
if (inetAddress.isLoopbackAddress() || inetAddress.isLinkLocalAddress()) {
continue;
}
if (inetAddress instanceof Inet6Address) {
//let's get rid of scope
try {
addresses.add(Inet6Address.getByAddress(inetAddress.getAddress()));
} catch (UnknownHostException e) {
//ignored
}
} else {
addresses.add(inetAddress);
}
}
}
return addresses;
}
public static List<JingleCandidate> getLocalCandidates(Jid jid) {
SecureRandom random = new SecureRandom();
ArrayList<JingleCandidate> candidates = new ArrayList<>();
for (InetAddress inetAddress : getLocalAddresses()) {
final JingleCandidate candidate = new JingleCandidate(UUID.randomUUID().toString(), true);
candidate.setHost(inetAddress.getHostAddress());
candidate.setPort(random.nextInt(60000) + 1024);
candidate.setType(JingleCandidate.TYPE_DIRECT);
candidate.setJid(jid);
candidate.setPriority(8257536 + candidates.size());
candidates.add(candidate);
}
return candidates;
}
}

View File

@ -110,15 +110,12 @@ public class JingleCandidate {
} }
public static JingleCandidate parse(Element candidate) { public static JingleCandidate parse(Element candidate) {
JingleCandidate parsedCandidate = new JingleCandidate( JingleCandidate parsedCandidate = new JingleCandidate(candidate.getAttribute("cid"), false);
candidate.getAttribute("cid"), false);
parsedCandidate.setHost(candidate.getAttribute("host")); parsedCandidate.setHost(candidate.getAttribute("host"));
parsedCandidate.setJid(InvalidJid.getNullForInvalid(candidate.getAttributeAsJid("jid"))); parsedCandidate.setJid(InvalidJid.getNullForInvalid(candidate.getAttributeAsJid("jid")));
parsedCandidate.setType(candidate.getAttribute("type")); parsedCandidate.setType(candidate.getAttribute("type"));
parsedCandidate.setPriority(Integer.parseInt(candidate parsedCandidate.setPriority(Integer.parseInt(candidate.getAttribute("priority")));
.getAttribute("priority"))); parsedCandidate.setPort(Integer.parseInt(candidate.getAttribute("port")));
parsedCandidate
.setPort(Integer.parseInt(candidate.getAttribute("port")));
return parsedCandidate; return parsedCandidate;
} }
@ -127,7 +124,9 @@ public class JingleCandidate {
element.setAttribute("cid", this.getCid()); element.setAttribute("cid", this.getCid());
element.setAttribute("host", this.getHost()); element.setAttribute("host", this.getHost());
element.setAttribute("port", Integer.toString(this.getPort())); element.setAttribute("port", Integer.toString(this.getPort()));
element.setAttribute("jid", this.getJid().toString()); if (jid != null) {
element.setAttribute("jid", jid.toEscapedString());
}
element.setAttribute("priority", Integer.toString(this.getPriority())); element.setAttribute("priority", Integer.toString(this.getPriority()));
if (this.getType() == TYPE_DIRECT) { if (this.getType() == TYPE_DIRECT) {
element.setAttribute("type", "direct"); element.setAttribute("type", "direct");

View File

@ -43,6 +43,8 @@ import rocks.xmpp.addr.Jid;
public class JingleConnection implements Transferable { public class JingleConnection implements Transferable {
private static final String JET_OMEMO_CIPHER = "urn:xmpp:ciphers:aes-128-gcm-nopadding";
private static final int JINGLE_STATUS_INITIATED = 0; private static final int JINGLE_STATUS_INITIATED = 0;
private static final int JINGLE_STATUS_ACCEPTED = 1; private static final int JINGLE_STATUS_ACCEPTED = 1;
private static final int JINGLE_STATUS_FINISHED = 4; private static final int JINGLE_STATUS_FINISHED = 4;
@ -72,6 +74,7 @@ public class JingleConnection implements Transferable {
private String contentName; private String contentName;
private String contentCreator; private String contentCreator;
private Transport initialTransport; private Transport initialTransport;
private boolean remoteSupportsOmemoJet;
private int mProgress = 0; private int mProgress = 0;
@ -171,7 +174,10 @@ public class JingleConnection implements Transferable {
@Override @Override
public void failed() { public void failed() {
Log.d(Config.LOGTAG, "proxy activation failed"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": proxy activation failed");
if (initiating()) {
sendFallbackToIbb();
}
} }
}; };
@ -292,8 +298,10 @@ public class JingleConnection implements Transferable {
this.contentName = this.mJingleConnectionManager.nextRandomId(); this.contentName = this.mJingleConnectionManager.nextRandomId();
this.message = message; this.message = message;
this.account = message.getConversation().getAccount(); this.account = message.getConversation().getAccount();
upgradeNamespace(); final List<String> remoteFeatures = getRemoteFeatures();
this.initialTransport = getRemoteFeatures().contains(Namespace.JINGLE_TRANSPORTS_S5B) ? Transport.SOCKS : Transport.IBB; upgradeNamespace(remoteFeatures);
this.initialTransport = remoteFeatures.contains(Namespace.JINGLE_TRANSPORTS_S5B) ? Transport.SOCKS : Transport.IBB;
this.remoteSupportsOmemoJet = remoteFeatures.contains(Namespace.JINGLE_ENCRYPTED_TRANSPORT_OMEMO);
this.message.setTransferable(this); this.message.setTransferable(this);
this.mStatus = Transferable.STATUS_UPLOADING; this.mStatus = Transferable.STATUS_UPLOADING;
this.initiator = this.account.getJid(); this.initiator = this.account.getJid();
@ -302,10 +310,9 @@ public class JingleConnection implements Transferable {
this.transportId = this.mJingleConnectionManager.nextRandomId(); this.transportId = this.mJingleConnectionManager.nextRandomId();
if (this.initialTransport == Transport.IBB) { if (this.initialTransport == Transport.IBB) {
this.sendInitRequest(); this.sendInitRequest();
} else if (this.candidates.size() > 0) {
this.sendInitRequest();
} else { } else {
this.mJingleConnectionManager.getPrimaryCandidate(account, (success, candidate) -> { gatherAndConnectDirectCandidates();
this.mJingleConnectionManager.getPrimaryCandidate(account, initiating(), (success, candidate) -> {
if (success) { if (success) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, candidate); final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, candidate);
connections.put(candidate.getCid(), socksConnection); connections.put(candidate.getCid(), socksConnection);
@ -313,22 +320,20 @@ public class JingleConnection implements Transferable {
@Override @Override
public void failed() { public void failed() {
Log.d(Config.LOGTAG, Log.d(Config.LOGTAG, "connection to our own proxy65 candidate failed");
"connection to our own primary candidete failed");
sendInitRequest(); sendInitRequest();
} }
@Override @Override
public void established() { public void established() {
Log.d(Config.LOGTAG, Log.d(Config.LOGTAG, "successfully connected to our own proxy65 candidate");
"successfully connected to our own primary candidate");
mergeCandidate(candidate); mergeCandidate(candidate);
sendInitRequest(); sendInitRequest();
} }
}); });
mergeCandidate(candidate); mergeCandidate(candidate);
} else { } else {
Log.d(Config.LOGTAG, "no primary candidate of our own was found"); Log.d(Config.LOGTAG, "no proxy65 candidate of our own was found");
sendInitRequest(); sendInitRequest();
} }
}); });
@ -336,11 +341,28 @@ public class JingleConnection implements Transferable {
} }
private void upgradeNamespace() { private void gatherAndConnectDirectCandidates() {
List<String> features = getRemoteFeatures(); final List<JingleCandidate> directCandidates;
if (features.contains(Content.Version.FT_5.getNamespace())) { if (Config.USE_DIRECT_JINGLE_CANDIDATES) {
if (account.isOnion() || mXmppConnectionService.useTorToConnect()) {
directCandidates = Collections.emptyList();
} else {
directCandidates = DirectConnectionUtils.getLocalCandidates(account.getJid());
}
} else {
directCandidates = Collections.emptyList();
}
for (JingleCandidate directCandidate : directCandidates) {
final JingleSocks5Transport socksConnection = new JingleSocks5Transport(this, directCandidate);
connections.put(directCandidate.getCid(), socksConnection);
candidates.add(directCandidate);
}
}
private void upgradeNamespace(List<String> remoteFeatures) {
if (remoteFeatures.contains(Content.Version.FT_5.getNamespace())) {
this.ftVersion = Content.Version.FT_5; this.ftVersion = Content.Version.FT_5;
} else if (features.contains(Content.Version.FT_4.getNamespace())) { } else if (remoteFeatures.contains(Content.Version.FT_4.getNamespace())) {
this.ftVersion = Content.Version.FT_4; this.ftVersion = Content.Version.FT_4;
} }
} }
@ -408,8 +430,18 @@ public class JingleConnection implements Transferable {
} }
this.fileOffer = content.getFileOffer(this.ftVersion); this.fileOffer = content.getFileOffer(this.ftVersion);
if (fileOffer != null) { if (fileOffer != null) {
boolean remoteIsUsingJet = false;
Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX); Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX);
if (encrypted == null) {
final Element security = content.findChild("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
if (security != null && AxolotlService.PEP_PREFIX.equals(security.getAttribute("type"))) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received jingle file offer with JET");
encrypted = security.findChild("encrypted", AxolotlService.PEP_PREFIX);
remoteIsUsingJet = true;
}
}
if (encrypted != null) { if (encrypted != null) {
this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().asBareJid()); this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().asBareJid());
} }
@ -450,7 +482,10 @@ public class JingleConnection implements Transferable {
} }
} }
message.resetFileParams(); message.resetFileParams();
this.file.setExpectedSize(size); //legacy OMEMO encrypted file transfers reported the file size after encryption
//JET reports the plain text size. however lower levels of our receiving code still
//expect the cipher text size. so we just + 16 bytes (auth tag size) here
this.file.setExpectedSize(size + (remoteIsUsingJet ? 16 : 0));
if (mJingleConnectionManager.hasStoragePermission() if (mJingleConnectionManager.hasStoragePermission()
&& size < this.mJingleConnectionManager.getAutoAcceptFileSize() && size < this.mJingleConnectionManager.getAutoAcceptFileSize()
&& mXmppConnectionService.isDataSaverDisabled()) { && mXmppConnectionService.isDataSaverDisabled()) {
@ -499,8 +534,21 @@ public class JingleConnection implements Transferable {
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
this.file.setKey(mXmppAxolotlMessage.getInnerKey()); this.file.setKey(mXmppAxolotlMessage.getInnerKey());
this.file.setIv(mXmppAxolotlMessage.getIV()); this.file.setIv(mXmppAxolotlMessage.getIV());
this.file.setExpectedSize(file.getSize() + 16); //legacy OMEMO encrypted file transfer reported file size of the encrypted file
content.setFileOffer(this.file, false, this.ftVersion).addChild(mXmppAxolotlMessage.toElement()); //JET uses the file size of the plain text file. The difference is only 16 bytes (auth tag)
this.file.setExpectedSize(file.getSize() + (this.remoteSupportsOmemoJet ? 0 : 16));
final Element file = content.setFileOffer(this.file, false, this.ftVersion);
if (remoteSupportsOmemoJet) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": remote announced support for JET");
final Element security = new Element("security", Namespace.JINGLE_ENCRYPTED_TRANSPORT);
security.setAttribute("name", this.contentName);
security.setAttribute("cipher", JET_OMEMO_CIPHER);
security.setAttribute("type", AxolotlService.PEP_PREFIX);
security.addChild(mXmppAxolotlMessage.toElement());
content.addChild(security);
} else {
file.addChild(mXmppAxolotlMessage.toElement());
}
} else { } else {
this.file.setExpectedSize(file.getSize()); this.file.setExpectedSize(file.getSize());
content.setFileOffer(this.file, false, this.ftVersion); content.setFileOffer(this.file, false, this.ftVersion);
@ -564,7 +612,8 @@ public class JingleConnection implements Transferable {
} }
private void sendAcceptSocks() { private void sendAcceptSocks() {
this.mJingleConnectionManager.getPrimaryCandidate(this.account, (success, candidate) -> { gatherAndConnectDirectCandidates();
this.mJingleConnectionManager.getPrimaryCandidate(this.account, initiating(), (success, candidate) -> {
final JinglePacket packet = bootstrapPacket("session-accept"); final JinglePacket packet = bootstrapPacket("session-accept");
final Content content = new Content(contentCreator, contentName); final Content content = new Content(contentCreator, contentName);
content.setFileOffer(fileOffer, ftVersion); content.setFileOffer(fileOffer, ftVersion);
@ -576,7 +625,7 @@ public class JingleConnection implements Transferable {
@Override @Override
public void failed() { public void failed() {
Log.d(Config.LOGTAG, "connection to our own primary candidate failed"); Log.d(Config.LOGTAG, "connection to our own proxy65 candidate failed");
content.socks5transport().setChildren(getCandidatesAsElements()); content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content); packet.setContent(content);
sendJinglePacket(packet); sendJinglePacket(packet);
@ -585,7 +634,7 @@ public class JingleConnection implements Transferable {
@Override @Override
public void established() { public void established() {
Log.d(Config.LOGTAG, "connected to primary candidate"); Log.d(Config.LOGTAG, "connected to proxy65 candidate");
mergeCandidate(candidate); mergeCandidate(candidate);
content.socks5transport().setChildren(getCandidatesAsElements()); content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content); packet.setContent(content);
@ -594,7 +643,7 @@ public class JingleConnection implements Transferable {
} }
}); });
} else { } else {
Log.d(Config.LOGTAG, "did not find a primary candidate for ourself"); Log.d(Config.LOGTAG, "did not find a proxy65 candidate for ourselves");
content.socks5transport().setChildren(getCandidatesAsElements()); content.socks5transport().setChildren(getCandidatesAsElements());
packet.setContent(content); packet.setContent(content);
sendJinglePacket(packet); sendJinglePacket(packet);
@ -635,7 +684,7 @@ public class JingleConnection implements Transferable {
private boolean receiveAccept(JinglePacket packet) { private boolean receiveAccept(JinglePacket packet) {
if (this.mJingleStatus != JINGLE_STATUS_INITIATED) { if (this.mJingleStatus != JINGLE_STATUS_INITIATED) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": received out of order session-accept"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received out of order session-accept");
return false; return false;
} }
this.mJingleStatus = JINGLE_STATUS_ACCEPTED; this.mJingleStatus = JINGLE_STATUS_ACCEPTED;
@ -654,7 +703,7 @@ public class JingleConnection implements Transferable {
this.ibbBlockSize = bs; this.ibbBlockSize = bs;
} }
} catch (Exception e) { } catch (Exception e) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": unable to parse block size in session-accept"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in session-accept");
} }
} }
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize); this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
@ -689,7 +738,7 @@ public class JingleConnection implements Transferable {
onProxyActivated.failed(); onProxyActivated.failed();
return true; return true;
} else if (content.socks5transport().hasChild("candidate-error")) { } else if (content.socks5transport().hasChild("candidate-error")) {
Log.d(Config.LOGTAG, "received candidate error"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received candidate error");
this.receivedCandidate = true; this.receivedCandidate = true;
if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) { if (mJingleStatus == JINGLE_STATUS_ACCEPTED && this.sentCandidate) {
this.connect(); this.connect();
@ -727,12 +776,14 @@ public class JingleConnection implements Transferable {
final JingleSocks5Transport connection = chooseConnection(); final JingleSocks5Transport connection = chooseConnection();
this.transport = connection; this.transport = connection;
if (connection == null) { if (connection == null) {
Log.d(Config.LOGTAG, "could not find suitable candidate"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not find suitable candidate");
this.disconnectSocks5Connections(); this.disconnectSocks5Connections();
if (initiating()) { if (initiating()) {
this.sendFallbackToIbb(); this.sendFallbackToIbb();
} }
} else { } else {
final JingleCandidate candidate = connection.getCandidate();
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": elected candidate " + candidate.getHost() + ":" + candidate.getPort());
this.mJingleStatus = JINGLE_STATUS_TRANSMITTING; this.mJingleStatus = JINGLE_STATUS_TRANSMITTING;
if (connection.needsActivation()) { if (connection.needsActivation()) {
if (connection.getCandidate().isOurs()) { if (connection.getCandidate().isOurs()) {
@ -754,10 +805,12 @@ public class JingleConnection implements Transferable {
.setContent(this.getCounterPart().toString()); .setContent(this.getCounterPart().toString());
mXmppConnectionService.sendIqPacket(account, activation, (account, response) -> { mXmppConnectionService.sendIqPacket(account, activation, (account, response) -> {
if (response.getType() != IqPacket.TYPE.RESULT) { if (response.getType() != IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + response.toString());
sendProxyError();
onProxyActivated.failed(); onProxyActivated.failed();
} else { } else {
onProxyActivated.success();
sendProxyActivated(connection.getCandidate().getCid()); sendProxyActivated(connection.getCandidate().getCid());
onProxyActivated.success();
} }
}); });
} else { } else {
@ -841,7 +894,7 @@ public class JingleConnection implements Transferable {
private boolean receiveFallbackToIbb(JinglePacket packet) { private boolean receiveFallbackToIbb(JinglePacket packet) {
Log.d(Config.LOGTAG, "receiving fallback to ibb"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": receiving fallback to ibb");
final String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size"); final String receivedBlockSize = packet.getJingleContent().ibbTransport().getAttribute("block-size");
if (receivedBlockSize != null) { if (receivedBlockSize != null) {
try { try {
@ -850,7 +903,7 @@ public class JingleConnection implements Transferable {
this.ibbBlockSize = bs; this.ibbBlockSize = bs;
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.d(Config.LOGTAG,account.getJid().asBareJid()+": unable to parse block size in transport-replace"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in transport-replace");
} }
} }
this.transportId = packet.getJingleContent().getTransportId(); this.transportId = packet.getJingleContent().getTransportId();
@ -889,7 +942,7 @@ public class JingleConnection implements Transferable {
this.ibbBlockSize = bs; this.ibbBlockSize = bs;
} }
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Log.d(Config.LOGTAG, account.getJid().asBareJid()+": unable to parse block size in transport-accept"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to parse block size in transport-accept");
} }
} }
this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize); this.transport = new JingleInbandTransport(this, this.transportId, this.ibbBlockSize);
@ -1029,14 +1082,23 @@ public class JingleConnection implements Transferable {
} }
private void sendProxyActivated(String cid) { private void sendProxyActivated(String cid) {
JinglePacket packet = bootstrapPacket("transport-info"); final JinglePacket packet = bootstrapPacket("transport-info");
Content content = new Content(this.contentCreator, this.contentName); final Content content = new Content(this.contentCreator, this.contentName);
content.setTransportId(this.transportId); content.setTransportId(this.transportId);
content.socks5transport().addChild("activated").setAttribute("cid", cid); content.socks5transport().addChild("activated").setAttribute("cid", cid);
packet.setContent(content); packet.setContent(content);
this.sendJinglePacket(packet); this.sendJinglePacket(packet);
} }
private void sendProxyError() {
final JinglePacket packet = bootstrapPacket("transport-info");
final Content content = new Content(this.contentCreator, this.contentName);
content.setTransportId(this.transportId);
content.socks5transport().addChild("proxy-error");
packet.setContent(content);
this.sendJinglePacket(packet);
}
private void sendCandidateUsed(final String cid) { private void sendCandidateUsed(final String cid) {
JinglePacket packet = bootstrapPacket("transport-info"); JinglePacket packet = bootstrapPacket("transport-info");
Content content = new Content(this.contentCreator, this.contentName); Content content = new Content(this.contentCreator, this.contentName);
@ -1051,7 +1113,7 @@ public class JingleConnection implements Transferable {
} }
private void sendCandidateError() { private void sendCandidateError() {
Log.d(Config.LOGTAG, "sending candidate error"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": sending candidate error");
JinglePacket packet = bootstrapPacket("transport-info"); JinglePacket packet = bootstrapPacket("transport-info");
Content content = new Content(this.contentCreator, this.contentName); Content content = new Content(this.contentCreator, this.contentName);
content.setTransportId(this.transportId); content.setTransportId(this.transportId);
@ -1087,6 +1149,7 @@ public class JingleConnection implements Transferable {
} }
private void mergeCandidates(List<JingleCandidate> candidates) { private void mergeCandidates(List<JingleCandidate> candidates) {
Collections.sort(candidates, (a, b) -> Integer.compare(b.getPriority(), a.getPriority()));
for (JingleCandidate c : candidates) { for (JingleCandidate c : candidates) {
mergeCandidate(c); mergeCandidate(c);
} }

View File

@ -81,8 +81,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
this.connections.remove(connection); this.connections.remove(connection);
} }
public void getPrimaryCandidate(Account account, public void getPrimaryCandidate(final Account account, final boolean initiator, final OnPrimaryCandidateFound listener) {
final OnPrimaryCandidateFound listener) {
if (Config.DISABLE_PROXY_LOOKUP) { if (Config.DISABLE_PROXY_LOOKUP) {
listener.onPrimaryCandidateFound(false, null); listener.onPrimaryCandidateFound(false, null);
return; return;
@ -107,7 +106,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
candidate.setPort(Integer.parseInt(port)); candidate.setPort(Integer.parseInt(port));
candidate.setType(JingleCandidate.TYPE_PROXY); candidate.setType(JingleCandidate.TYPE_PROXY);
candidate.setJid(proxy); candidate.setJid(proxy);
candidate.setPriority(655360 + 65535); candidate.setPriority(655360 + (initiator ? 10 : 20));
primaryCandidates.put(account.getJid().asBareJid(),candidate); primaryCandidates.put(account.getJid().asBareJid(),candidate);
listener.onPrimaryCandidateFound(true,candidate); listener.onPrimaryCandidateFound(true,candidate);
} catch (final NumberFormatException e) { } catch (final NumberFormatException e) {

View File

@ -6,13 +6,17 @@ import android.util.Log;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.AbstractConnectionManager;
@ -22,177 +26,268 @@ import eu.siacs.conversations.utils.WakeLockHelper;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
public class JingleSocks5Transport extends JingleTransport { public class JingleSocks5Transport extends JingleTransport {
private JingleCandidate candidate; private final JingleCandidate candidate;
private JingleConnection connection; private final JingleConnection connection;
private String destination; private final String destination;
private OutputStream outputStream; private OutputStream outputStream;
private InputStream inputStream; private InputStream inputStream;
private boolean isEstablished = false; private boolean isEstablished = false;
private boolean activated = false; private boolean activated = false;
private Socket socket; private ServerSocket serverSocket;
private Socket socket;
JingleSocks5Transport(JingleConnection jingleConnection, JingleCandidate candidate) { JingleSocks5Transport(JingleConnection jingleConnection, JingleCandidate candidate) {
this.candidate = candidate; final MessageDigest messageDigest;
this.connection = jingleConnection; try {
try { messageDigest = MessageDigest.getInstance("SHA-1");
MessageDigest mDigest = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) {
StringBuilder destBuilder = new StringBuilder(); throw new AssertionError(e);
if (jingleConnection.getFtVersion() == Content.Version.FT_3) { }
Log.d(Config.LOGTAG, this.connection.getAccount().getJid().asBareJid() + ": using session Id instead of transport Id for proxy destination"); this.candidate = candidate;
destBuilder.append(jingleConnection.getSessionId()); this.connection = jingleConnection;
} else { final StringBuilder destBuilder = new StringBuilder();
destBuilder.append(jingleConnection.getTransportId()); if (jingleConnection.getFtVersion() == Content.Version.FT_3) {
} Log.d(Config.LOGTAG, this.connection.getAccount().getJid().asBareJid() + ": using session Id instead of transport Id for proxy destination");
if (candidate.isOurs()) { destBuilder.append(jingleConnection.getSessionId());
destBuilder.append(jingleConnection.getAccount().getJid()); } else {
destBuilder.append(jingleConnection.getCounterPart()); destBuilder.append(jingleConnection.getTransportId());
} else { }
destBuilder.append(jingleConnection.getCounterPart()); if (candidate.isOurs()) {
destBuilder.append(jingleConnection.getAccount().getJid()); destBuilder.append(jingleConnection.getAccount().getJid());
} destBuilder.append(jingleConnection.getCounterPart());
mDigest.reset(); } else {
this.destination = CryptoHelper.bytesToHex(mDigest destBuilder.append(jingleConnection.getCounterPart());
.digest(destBuilder.toString().getBytes())); destBuilder.append(jingleConnection.getAccount().getJid());
} catch (NoSuchAlgorithmException e) { }
messageDigest.reset();
this.destination = CryptoHelper.bytesToHex(messageDigest.digest(destBuilder.toString().getBytes()));
if (candidate.isOurs() && candidate.getType() == JingleCandidate.TYPE_DIRECT) {
createServerSocket();
}
}
} private void createServerSocket() {
} try {
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(InetAddress.getByName(candidate.getHost()), candidate.getPort()));
new Thread(() -> {
try {
final Socket socket = serverSocket.accept();
new Thread(() -> {
try {
acceptIncomingSocketConnection(socket);
} catch (IOException e) {
Log.d(Config.LOGTAG, "unable to read from socket", e);
public void connect(final OnTransportConnected callback) { }
new Thread(() -> { }).start();
try { } catch (IOException e) {
final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect(); if (!serverSocket.isClosed()) {
if (useTor) { Log.d(Config.LOGTAG, "unable to accept socket", e);
socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(), candidate.getPort()); }
} else { }
socket = new Socket(); }).start();
SocketAddress address = new InetSocketAddress(candidate.getHost(), candidate.getPort()); } catch (IOException e) {
socket.connect(address, Config.SOCKET_TIMEOUT * 1000); Log.d(Config.LOGTAG, "unable to bind server socket ", e);
} }
inputStream = socket.getInputStream(); }
outputStream = socket.getOutputStream();
SocksSocketFactory.createSocksConnection(socket, destination, 0);
isEstablished = true;
callback.established();
} catch (IOException e) {
callback.failed();
}
}).start();
} private void acceptIncomingSocketConnection(Socket socket) throws IOException {
Log.d(Config.LOGTAG, "accepted connection from " + socket.getInetAddress().getHostAddress());
final byte[] authBegin = new byte[2];
final InputStream inputStream = socket.getInputStream();
final OutputStream outputStream = socket.getOutputStream();
inputStream.read(authBegin);
if (authBegin[0] != 0x5) {
socket.close();
}
final short methodCount = authBegin[1];
final byte[] methods = new byte[methodCount];
inputStream.read(methods);
if (SocksSocketFactory.contains((byte) 0x00, methods)) {
outputStream.write(new byte[]{0x05, 0x00});
} else {
outputStream.write(new byte[]{0x05, (byte) 0xff});
}
byte[] connectCommand = new byte[4];
inputStream.read(connectCommand);
if (connectCommand[0] == 0x05 && connectCommand[1] == 0x01 && connectCommand[3] == 0x03) {
int destinationCount = inputStream.read();
final byte[] destination = new byte[destinationCount];
inputStream.read(destination);
final int port = inputStream.read();
final String receivedDestination = new String(destination);
final ByteBuffer response = ByteBuffer.allocate(7 + destination.length);
final byte[] responseHeader;
final boolean success;
if (receivedDestination.equals(this.destination) && this.socket == null) {
responseHeader = new byte[]{0x05, 0x00, 0x00, 0x03};
success = true;
} else {
Log.d(Config.LOGTAG,connection.getAccount().getJid().asBareJid()+": destination mismatch. received "+receivedDestination+" (expected "+this.destination+")");
responseHeader = new byte[]{0x05, 0x04, 0x00, 0x03};
success = false;
}
response.put(responseHeader);
response.put((byte) destination.length);
response.put(destination);
response.putShort((short) port);
outputStream.write(response.array());
outputStream.flush();
if (success) {
Log.d(Config.LOGTAG,connection.getAccount().getJid().asBareJid()+": successfully processed connection to candidate "+candidate.getHost()+":"+candidate.getPort());
this.socket = socket;
this.inputStream = inputStream;
this.outputStream = outputStream;
this.isEstablished = true;
FileBackend.close(serverSocket);
} else {
this.socket.close();
}
} else {
socket.close();
}
}
public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { public void connect(final OnTransportConnected callback) {
new Thread(() -> { new Thread(() -> {
InputStream fileInputStream = null; try {
final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_" + connection.getSessionId()); final boolean useTor = connection.getAccount().isOnion() || connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
try { if (useTor) {
wakeLock.acquire(); socket = SocksSocketFactory.createSocketOverTor(candidate.getHost(), candidate.getPort());
MessageDigest digest = MessageDigest.getInstance("SHA-1"); } else {
digest.reset(); socket = new Socket();
fileInputStream = connection.getFileInputStream(); SocketAddress address = new InetSocketAddress(candidate.getHost(), candidate.getPort());
if (fileInputStream == null) { socket.connect(address, 5000);
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": could not create input stream"); }
callback.onFileTransferAborted(); inputStream = socket.getInputStream();
return; outputStream = socket.getOutputStream();
} socket.setSoTimeout(5000);
final InputStream innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream); SocksSocketFactory.createSocksConnection(socket, destination, 0);
long size = file.getExpectedSize(); socket.setSoTimeout(0);
long transmitted = 0; isEstablished = true;
int count; callback.established();
byte[] buffer = new byte[8192]; } catch (IOException e) {
while ((count = innerInputStream.read(buffer)) > 0) { callback.failed();
outputStream.write(buffer, 0, count); }
digest.update(buffer, 0, count); }).start();
transmitted += count;
connection.updateProgress((int) ((((double) transmitted) / size) * 100));
}
outputStream.flush();
file.setSha1Sum(digest.digest());
if (callback != null) {
callback.onFileTransmitted(file);
}
} catch (Exception e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": " + e.getMessage());
callback.onFileTransferAborted();
} finally {
FileBackend.close(fileInputStream);
WakeLockHelper.release(wakeLock);
}
}).start();
} }
public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) { public void send(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
new Thread(() -> { new Thread(() -> {
OutputStream fileOutputStream = null; InputStream fileInputStream = null;
final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_" + connection.getSessionId()); final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_send_" + connection.getSessionId());
try { long transmitted = 0;
wakeLock.acquire(); try {
MessageDigest digest = MessageDigest.getInstance("SHA-1"); wakeLock.acquire();
digest.reset(); MessageDigest digest = MessageDigest.getInstance("SHA-1");
//inputStream.skip(45); digest.reset();
socket.setSoTimeout(30000); fileInputStream = connection.getFileInputStream();
fileOutputStream = connection.getFileOutputStream(); if (fileInputStream == null) {
if (fileOutputStream == null) { Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": could not create input stream");
callback.onFileTransferAborted(); callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": could not create output stream"); return;
return; }
} final InputStream innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream);
double size = file.getExpectedSize(); long size = file.getExpectedSize();
long remainingSize = file.getExpectedSize(); int count;
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
int count; while ((count = innerInputStream.read(buffer)) > 0) {
while (remainingSize > 0) { outputStream.write(buffer, 0, count);
count = inputStream.read(buffer); digest.update(buffer, 0, count);
if (count == -1) { transmitted += count;
callback.onFileTransferAborted(); connection.updateProgress((int) ((((double) transmitted) / size) * 100));
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": file ended prematurely with " + remainingSize + " bytes remaining"); }
return; outputStream.flush();
} else { file.setSha1Sum(digest.digest());
fileOutputStream.write(buffer, 0, count); if (callback != null) {
digest.update(buffer, 0, count); callback.onFileTransmitted(file);
remainingSize -= count; }
} } catch (Exception e) {
connection.updateProgress((int) (((size - remainingSize) / size) * 100)); final Account account = connection.getAccount();
} Log.d(Config.LOGTAG, account.getJid().asBareJid()+": failed sending file after "+transmitted+"/"+file.getExpectedSize()+" ("+ socket.getInetAddress()+":"+socket.getPort()+")", e);
fileOutputStream.flush(); callback.onFileTransferAborted();
fileOutputStream.close(); } finally {
file.setSha1Sum(digest.digest()); FileBackend.close(fileInputStream);
callback.onFileTransmitted(file); WakeLockHelper.release(wakeLock);
} catch (Exception e) { }
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": " + e.getMessage()); }).start();
callback.onFileTransferAborted();
} finally {
WakeLockHelper.release(wakeLock);
FileBackend.close(fileOutputStream);
FileBackend.close(inputStream);
}
}).start();
}
public boolean isProxy() { }
return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
}
public boolean needsActivation() { public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
return (this.isProxy() && !this.activated); new Thread(() -> {
} OutputStream fileOutputStream = null;
final PowerManager.WakeLock wakeLock = connection.getConnectionManager().createWakeLock("jingle_receive_" + connection.getSessionId());
try {
wakeLock.acquire();
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
//inputStream.skip(45);
socket.setSoTimeout(30000);
fileOutputStream = connection.getFileOutputStream();
if (fileOutputStream == null) {
callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": could not create output stream");
return;
}
double size = file.getExpectedSize();
long remainingSize = file.getExpectedSize();
byte[] buffer = new byte[8192];
int count;
while (remainingSize > 0) {
count = inputStream.read(buffer);
if (count == -1) {
callback.onFileTransferAborted();
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": file ended prematurely with " + remainingSize + " bytes remaining");
return;
} else {
fileOutputStream.write(buffer, 0, count);
digest.update(buffer, 0, count);
remainingSize -= count;
}
connection.updateProgress((int) (((size - remainingSize) / size) * 100));
}
fileOutputStream.flush();
fileOutputStream.close();
file.setSha1Sum(digest.digest());
callback.onFileTransmitted(file);
} catch (Exception e) {
Log.d(Config.LOGTAG, connection.getAccount().getJid().asBareJid() + ": " + e.getMessage());
callback.onFileTransferAborted();
} finally {
WakeLockHelper.release(wakeLock);
FileBackend.close(fileOutputStream);
FileBackend.close(inputStream);
}
}).start();
}
public void disconnect() { public boolean isProxy() {
FileBackend.close(inputStream); return this.candidate.getType() == JingleCandidate.TYPE_PROXY;
FileBackend.close(outputStream); }
FileBackend.close(socket);
}
public boolean isEstablished() { public boolean needsActivation() {
return this.isEstablished; return (this.isProxy() && !this.activated);
} }
public JingleCandidate getCandidate() { public void disconnect() {
return this.candidate; FileBackend.close(inputStream);
} FileBackend.close(outputStream);
FileBackend.close(socket);
FileBackend.close(serverSocket);
}
public void setActivated(boolean activated) { public boolean isEstablished() {
this.activated = activated; return this.isEstablished;
} }
public JingleCandidate getCandidate() {
return this.candidate;
}
public void setActivated(boolean activated) {
this.activated = activated;
}
} }

View File

@ -27,7 +27,6 @@
<string name="just_now">الآن</string> <string name="just_now">الآن</string>
<string name="minute_ago">منذ 1 دقيقة</string> <string name="minute_ago">منذ 1 دقيقة</string>
<string name="minutes_ago">دقائق %d منذ</string> <string name="minutes_ago">دقائق %d منذ</string>
<string name="unread_conversations">محادثات غير مقروءة</string>
<string name="sending">ارسال</string> <string name="sending">ارسال</string>
<string name="message_decrypting">حل شيفرة الرسالة. الرجاء الإنتظار ...</string> <string name="message_decrypting">حل شيفرة الرسالة. الرجاء الإنتظار ...</string>
<string name="pgp_message">رسالة مشفرة عبر OpenPGP</string> <string name="pgp_message">رسالة مشفرة عبر OpenPGP</string>

View File

@ -28,7 +28,6 @@
<string name="just_now">току-що</string> <string name="just_now">току-що</string>
<string name="minute_ago">преди 1 минута</string> <string name="minute_ago">преди 1 минута</string>
<string name="minutes_ago">преди %d минути</string> <string name="minutes_ago">преди %d минути</string>
<string name="unread_conversations">непрочетени разговори</string>
<string name="sending">изпращане…</string> <string name="sending">изпращане…</string>
<string name="message_decrypting">Дешифроване на съобщението. Моля, изчакайте…</string> <string name="message_decrypting">Дешифроване на съобщението. Моля, изчакайте…</string>
<string name="pgp_message">Съобщение, шифр. чрез OpenPGP</string> <string name="pgp_message">Съобщение, шифр. чрез OpenPGP</string>

View File

@ -27,7 +27,6 @@
<string name="just_now">Ara</string> <string name="just_now">Ara</string>
<string name="minute_ago">fa 1 min</string> <string name="minute_ago">fa 1 min</string>
<string name="minutes_ago">fa %d mins</string> <string name="minutes_ago">fa %d mins</string>
<string name="unread_conversations">Converses sense llegir o no llegides</string>
<string name="sending">enviant…</string> <string name="sending">enviant…</string>
<string name="message_decrypting">Desxifrant el missatge. Espereu…</string> <string name="message_decrypting">Desxifrant el missatge. Espereu…</string>
<string name="pgp_message">Missatge xifrat amb OpenPGP</string> <string name="pgp_message">Missatge xifrat amb OpenPGP</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">právě teď</string> <string name="just_now">právě teď</string>
<string name="minute_ago">před minutou</string> <string name="minute_ago">před minutou</string>
<string name="minutes_ago">před %d minutami</string> <string name="minutes_ago">před %d minutami</string>
<string name="unread_conversations">nepřečtené konverzace</string>
<string name="sending">odesílám…</string> <string name="sending">odesílám…</string>
<string name="message_decrypting">Dešifrování zprávy. Chvíli strpení...</string> <string name="message_decrypting">Dešifrování zprávy. Chvíli strpení...</string>
<string name="pgp_message">OpenPGP šifrovaná zpráva</string> <string name="pgp_message">OpenPGP šifrovaná zpráva</string>

View File

@ -30,7 +30,7 @@
<string name="just_now">gerade</string> <string name="just_now">gerade</string>
<string name="minute_ago">vor einer Minute</string> <string name="minute_ago">vor einer Minute</string>
<string name="minutes_ago">vor %d Minuten</string> <string name="minutes_ago">vor %d Minuten</string>
<string name="unread_conversations">ungelesene Unterhaltungen</string> <string name="x_unread_conversations">%d ungelesene Unterhaltungen</string>
<string name="sending">senden…</string> <string name="sending">senden…</string>
<string name="message_decrypting">Nachricht wird entschlüsselt. Bitte warten …</string> <string name="message_decrypting">Nachricht wird entschlüsselt. Bitte warten …</string>
<string name="pgp_message">OpenPGP-verschlüsselte Nachricht</string> <string name="pgp_message">OpenPGP-verschlüsselte Nachricht</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">μόλις τώρα</string> <string name="just_now">μόλις τώρα</string>
<string name="minute_ago">πριν από 1 λεπτό</string> <string name="minute_ago">πριν από 1 λεπτό</string>
<string name="minutes_ago">πριν από %d λεπτά</string> <string name="minutes_ago">πριν από %d λεπτά</string>
<string name="unread_conversations">μη αναγνωσμένες Συζητήσεις</string>
<string name="sending">αποστολή...</string> <string name="sending">αποστολή...</string>
<string name="message_decrypting">Αποκρυπτογράφηση μηνύματος. Παρακαλώ περιμένετε...</string> <string name="message_decrypting">Αποκρυπτογράφηση μηνύματος. Παρακαλώ περιμένετε...</string>
<string name="pgp_message">OpenPGP κρυπτογραφημένο μήνυμα</string> <string name="pgp_message">OpenPGP κρυπτογραφημένο μήνυμα</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">ahora</string> <string name="just_now">ahora</string>
<string name="minute_ago">hace 1 min</string> <string name="minute_ago">hace 1 min</string>
<string name="minutes_ago">hace %d min</string> <string name="minutes_ago">hace %d min</string>
<string name="unread_conversations">conversaciones por leer</string>
<string name="sending">enviando…</string> <string name="sending">enviando…</string>
<string name="message_decrypting">Descifrando mensaje. Por favor, espera...</string> <string name="message_decrypting">Descifrando mensaje. Por favor, espera...</string>
<string name="pgp_message">Mensaje cifrado con OpenPGP</string> <string name="pgp_message">Mensaje cifrado con OpenPGP</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">orain</string> <string name="just_now">orain</string>
<string name="minute_ago">min 1 lehenago</string> <string name="minute_ago">min 1 lehenago</string>
<string name="minutes_ago">%d min lehenago</string> <string name="minutes_ago">%d min lehenago</string>
<string name="unread_conversations">irakurri gabeko elkarrizketak</string>
<string name="sending">bidaltzen…</string> <string name="sending">bidaltzen…</string>
<string name="message_decrypting">Mezua desenkriptatzen. Mesedez itxaron…</string> <string name="message_decrypting">Mezua desenkriptatzen. Mesedez itxaron…</string>
<string name="pgp_message">OpenPGPz enkriptatutako mezua</string> <string name="pgp_message">OpenPGPz enkriptatutako mezua</string>

View File

@ -25,7 +25,6 @@
<string name="just_now">هم اکنون</string> <string name="just_now">هم اکنون</string>
<string name="minute_ago">1 دقیقه قبل</string> <string name="minute_ago">1 دقیقه قبل</string>
<string name="minutes_ago">%d دقیقه قبل</string> <string name="minutes_ago">%d دقیقه قبل</string>
<string name="unread_conversations">گفتگو های خوانده نشده</string>
<string name="sending">در حال ارسال...</string> <string name="sending">در حال ارسال...</string>
<string name="message_decrypting">در حال رمزگشایی پیام. لطفا صبور باشید...</string> <string name="message_decrypting">در حال رمزگشایی پیام. لطفا صبور باشید...</string>
<string name="pgp_message">پیام رمز شده به وسیله OpenPGP</string> <string name="pgp_message">پیام رمز شده به وسیله OpenPGP</string>

View File

@ -28,7 +28,6 @@
<string name="just_now">À l\'instant</string> <string name="just_now">À l\'instant</string>
<string name="minute_ago">Il y a 1 minute</string> <string name="minute_ago">Il y a 1 minute</string>
<string name="minutes_ago">Il y a %d minutes</string> <string name="minutes_ago">Il y a %d minutes</string>
<string name="unread_conversations">Conversations non lues</string>
<string name="sending">Envoi…</string> <string name="sending">Envoi…</string>
<string name="message_decrypting">Déchiffrement du message. Veuillez patienter...</string> <string name="message_decrypting">Déchiffrement du message. Veuillez patienter...</string>
<string name="pgp_message">Message chiffré avec OpenPGP</string> <string name="pgp_message">Message chiffré avec OpenPGP</string>

View File

@ -30,7 +30,7 @@
<string name="just_now">agora</string> <string name="just_now">agora</string>
<string name="minute_ago">Hai 1 min</string> <string name="minute_ago">Hai 1 min</string>
<string name="minutes_ago">hai %d minutos</string> <string name="minutes_ago">hai %d minutos</string>
<string name="unread_conversations">conversas sen ler</string> <string name="x_unread_conversations">%d conversas non lidas</string>
<string name="sending">enviando…</string> <string name="sending">enviando…</string>
<string name="message_decrypting">Descifrando a mensaxe. Por favor agarde...</string> <string name="message_decrypting">Descifrando a mensaxe. Por favor agarde...</string>
<string name="pgp_message">Mensaxe cifrado con OpenPGP</string> <string name="pgp_message">Mensaxe cifrado con OpenPGP</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">Éppen most</string> <string name="just_now">Éppen most</string>
<string name="minute_ago">1 perce</string> <string name="minute_ago">1 perce</string>
<string name="minutes_ago">%d perce</string> <string name="minutes_ago">%d perce</string>
<string name="unread_conversations">olvasatlan beszélgetés</string>
<string name="sending">küldés...</string> <string name="sending">küldés...</string>
<string name="message_decrypting">Üzenet dekódolása. Kérem várjon...</string> <string name="message_decrypting">Üzenet dekódolása. Kérem várjon...</string>
<string name="pgp_message">OpenPGP kódolású üzenet</string> <string name="pgp_message">OpenPGP kódolású üzenet</string>
@ -871,4 +870,5 @@
<string name="not_a_backup_file">A kiválasztott fájl nem a Conversations biztonsági mentése</string> <string name="not_a_backup_file">A kiválasztott fájl nem a Conversations biztonsági mentése</string>
<string name="account_already_setup">Ez a fiók már be lett állítva</string> <string name="account_already_setup">Ez a fiók már be lett állítva</string>
<string name="please_enter_password">Kérem, adja meg a fiókhoz tartozó jelszót</string> <string name="please_enter_password">Kérem, adja meg a fiókhoz tartozó jelszót</string>
<string name="unable_to_perform_this_action">Nem sikerült ezt a cselekvést elvégezni</string>
</resources> </resources>

View File

@ -21,7 +21,6 @@
<string name="just_now">sekarang</string> <string name="just_now">sekarang</string>
<string name="minute_ago">1 min lalu</string> <string name="minute_ago">1 min lalu</string>
<string name="minutes_ago">%d min lalu</string> <string name="minutes_ago">%d min lalu</string>
<string name="unread_conversations">Percakapan belum dibaca</string>
<string name="sending">mengirim...</string> <string name="sending">mengirim...</string>
<string name="message_decrypting">Mendekripsi pesan. Mohon tunggu…</string> <string name="message_decrypting">Mendekripsi pesan. Mohon tunggu…</string>
<string name="pgp_message">Pesan terenkripsi OpenPGP</string> <string name="pgp_message">Pesan terenkripsi OpenPGP</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">adesso</string> <string name="just_now">adesso</string>
<string name="minute_ago">1 min fa</string> <string name="minute_ago">1 min fa</string>
<string name="minutes_ago">%d min fa</string> <string name="minutes_ago">%d min fa</string>
<string name="unread_conversations">Conversazioni non lette</string>
<string name="sending">invio…</string> <string name="sending">invio…</string>
<string name="message_decrypting">Decifrazione messaggio. Attendere prego...</string> <string name="message_decrypting">Decifrazione messaggio. Attendere prego...</string>
<string name="pgp_message">Messaggio cifrato con OpenPGP</string> <string name="pgp_message">Messaggio cifrato con OpenPGP</string>

View File

@ -20,7 +20,6 @@
<string name="just_now">ממש עכשיו</string> <string name="just_now">ממש עכשיו</string>
<string name="minute_ago">לפני דקה</string> <string name="minute_ago">לפני דקה</string>
<string name="minutes_ago">לפני %d דקות</string> <string name="minutes_ago">לפני %d דקות</string>
<string name="unread_conversations">שיחות שלא נקראו</string>
<string name="sending">שולח...</string> <string name="sending">שולח...</string>
<string name="message_decrypting">כעת מפענח צופן הודעה. אנא המתן…</string> <string name="message_decrypting">כעת מפענח צופן הודעה. אנא המתן…</string>
<string name="pgp_message">הודעה מוצפנת OpenPGP</string> <string name="pgp_message">הודעה מוצפנת OpenPGP</string>

View File

@ -28,7 +28,6 @@
<string name="just_now">ちょうど今</string> <string name="just_now">ちょうど今</string>
<string name="minute_ago">1 分前</string> <string name="minute_ago">1 分前</string>
<string name="minutes_ago">%d 分前</string> <string name="minutes_ago">%d 分前</string>
<string name="unread_conversations">未読の会話</string>
<string name="sending">送信中…</string> <string name="sending">送信中…</string>
<string name="message_decrypting">メッセージを復号化しています。しばらくお待ちください…</string> <string name="message_decrypting">メッセージを復号化しています。しばらくお待ちください…</string>
<string name="pgp_message">OpenPGP 暗号化メッセージ</string> <string name="pgp_message">OpenPGP 暗号化メッセージ</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">방금 </string> <string name="just_now">방금 </string>
<string name="minute_ago">1분 전 </string> <string name="minute_ago">1분 전 </string>
<string name="minutes_ago">%d 분 전 </string> <string name="minutes_ago">%d 분 전 </string>
<string name="unread_conversations">읽지 않은 대화 </string>
<string name="sending">보내는중... </string> <string name="sending">보내는중... </string>
<string name="message_decrypting">메세지 복호화중입니다. 기다리세요...</string> <string name="message_decrypting">메세지 복호화중입니다. 기다리세요...</string>
<string name="pgp_message">OpenPGP로 암호화된 메세지</string> <string name="pgp_message">OpenPGP로 암호화된 메세지</string>

View File

@ -24,7 +24,6 @@
<string name="just_now">akkurat nå</string> <string name="just_now">akkurat nå</string>
<string name="minute_ago">1 minutt siden</string> <string name="minute_ago">1 minutt siden</string>
<string name="minutes_ago">%d minutter siden</string> <string name="minutes_ago">%d minutter siden</string>
<string name="unread_conversations">uleste samtaler</string>
<string name="sending">sender...</string> <string name="sending">sender...</string>
<string name="message_decrypting">Dekrypterer melding mens du venter.</string> <string name="message_decrypting">Dekrypterer melding mens du venter.</string>
<string name="pgp_message">OpenPGP-kryptert melding</string> <string name="pgp_message">OpenPGP-kryptert melding</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">zojuist</string> <string name="just_now">zojuist</string>
<string name="minute_ago">1 min. geleden</string> <string name="minute_ago">1 min. geleden</string>
<string name="minutes_ago">%d min. geleden</string> <string name="minutes_ago">%d min. geleden</string>
<string name="unread_conversations">ongelezen gesprekken</string>
<string name="sending">versturen…</string> <string name="sending">versturen…</string>
<string name="message_decrypting">Bericht aan het ontsleutelen. Even geduld…</string> <string name="message_decrypting">Bericht aan het ontsleutelen. Even geduld…</string>
<string name="pgp_message">OpenPGP-versleuteld bericht</string> <string name="pgp_message">OpenPGP-versleuteld bericht</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">przed chwilą</string> <string name="just_now">przed chwilą</string>
<string name="minute_ago">minutę temu</string> <string name="minute_ago">minutę temu</string>
<string name="minutes_ago">%d minut temu</string> <string name="minutes_ago">%d minut temu</string>
<string name="unread_conversations">nieprzeczytanych konwersacji</string>
<string name="sending">wysyłanie...</string> <string name="sending">wysyłanie...</string>
<string name="message_decrypting">Odszyfrowywanie wiadomości. To zajmie tylko chwilę...</string> <string name="message_decrypting">Odszyfrowywanie wiadomości. To zajmie tylko chwilę...</string>
<string name="pgp_message">Wiadomość zaszyfrowana OpenPGP</string> <string name="pgp_message">Wiadomość zaszyfrowana OpenPGP</string>

View File

@ -17,6 +17,8 @@
<string name="action_unblock_contact">Desbloquear contato</string> <string name="action_unblock_contact">Desbloquear contato</string>
<string name="action_block_domain">Bloquear domínio</string> <string name="action_block_domain">Bloquear domínio</string>
<string name="action_unblock_domain">Desbloquear domínio</string> <string name="action_unblock_domain">Desbloquear domínio</string>
<string name="action_block_participant">Bloquear participante</string>
<string name="action_unblock_participant">Desbloquear participante</string>
<string name="title_activity_manage_accounts">Gerenciar contas</string> <string name="title_activity_manage_accounts">Gerenciar contas</string>
<string name="title_activity_settings">Configurações</string> <string name="title_activity_settings">Configurações</string>
<string name="title_activity_sharewith">Compartilhar com a conversa</string> <string name="title_activity_sharewith">Compartilhar com a conversa</string>
@ -28,7 +30,7 @@
<string name="just_now">agora</string> <string name="just_now">agora</string>
<string name="minute_ago">1 minuto atrás</string> <string name="minute_ago">1 minuto atrás</string>
<string name="minutes_ago">%d minutos atrás</string> <string name="minutes_ago">%d minutos atrás</string>
<string name="unread_conversations">conversas não lidas</string> <string name="x_unread_conversations">%d conversas não lidas</string>
<string name="sending">enviando...</string> <string name="sending">enviando...</string>
<string name="message_decrypting">Descriptografando a mensagem. Por favor, aguarde...</string> <string name="message_decrypting">Descriptografando a mensagem. Por favor, aguarde...</string>
<string name="pgp_message">Mensagem criptografada via OpenPGP</string> <string name="pgp_message">Mensagem criptografada via OpenPGP</string>
@ -861,4 +863,12 @@
<string name="this_looks_like_a_domain">Isso se parece com um endereço de domínio</string> <string name="this_looks_like_a_domain">Isso se parece com um endereço de domínio</string>
<string name="add_anway">Adicionar mesmo assim</string> <string name="add_anway">Adicionar mesmo assim</string>
<string name="this_looks_like_channel">Isso se parece com um endereço de canal</string> <string name="this_looks_like_channel">Isso se parece com um endereço de canal</string>
<string name="share_backup_files">Compartilhar arquivos de backup</string>
<string name="conversations_backup">Backup do Conversations</string>
<string name="event">Evento</string>
<string name="open_backup">Abrir backup</string>
<string name="not_a_backup_file">O arquivo que você selecionou não é um backup do Conversations</string>
<string name="account_already_setup">Esta conta já foi configurada</string>
<string name="please_enter_password">Por favor, digite a senha para esta conta</string>
<string name="unable_to_perform_this_action">Não foi possível executar essa ação</string>
</resources> </resources>

View File

@ -23,7 +23,6 @@
<string name="just_now">agora há pouco</string> <string name="just_now">agora há pouco</string>
<string name="minute_ago">1 minuto atrás</string> <string name="minute_ago">1 minuto atrás</string>
<string name="minutes_ago">%d minutos atrás</string> <string name="minutes_ago">%d minutos atrás</string>
<string name="unread_conversations">Conversas não lidas</string>
<string name="sending">enviando...</string> <string name="sending">enviando...</string>
<string name="message_decrypting">Decifrando a mensagem. Por favor aguarde...</string> <string name="message_decrypting">Decifrando a mensagem. Por favor aguarde...</string>
<string name="pgp_message">Mensagem cifrada OpenPGP</string> <string name="pgp_message">Mensagem cifrada OpenPGP</string>

View File

@ -30,7 +30,6 @@
<string name="just_now">în acest moment</string> <string name="just_now">în acest moment</string>
<string name="minute_ago">acum un minut</string> <string name="minute_ago">acum un minut</string>
<string name="minutes_ago">acum %d minute</string> <string name="minutes_ago">acum %d minute</string>
<string name="unread_conversations">conversații necitite</string>
<string name="sending">trimitere...</string> <string name="sending">trimitere...</string>
<string name="message_decrypting">Decriptez mesaj. Te rog așteaptă...</string> <string name="message_decrypting">Decriptez mesaj. Te rog așteaptă...</string>
<string name="pgp_message">Mesaj criptat cu OpenPGP</string> <string name="pgp_message">Mesaj criptat cu OpenPGP</string>

View File

@ -27,7 +27,6 @@
<string name="just_now">только что</string> <string name="just_now">только что</string>
<string name="minute_ago">1 минуту назад</string> <string name="minute_ago">1 минуту назад</string>
<string name="minutes_ago">%d мин. назад</string> <string name="minutes_ago">%d мин. назад</string>
<string name="unread_conversations">сообщен. не прочитано</string>
<string name="sending">отправка…</string> <string name="sending">отправка…</string>
<string name="message_decrypting">Расшифровка сообщения. Подождите…</string> <string name="message_decrypting">Расшифровка сообщения. Подождите…</string>
<string name="pgp_message">OpenPGP зашифр. сообщение</string> <string name="pgp_message">OpenPGP зашифр. сообщение</string>

View File

@ -20,7 +20,6 @@
<string name="just_now">práve teraz</string> <string name="just_now">práve teraz</string>
<string name="minute_ago">pred 1 minútou</string> <string name="minute_ago">pred 1 minútou</string>
<string name="minutes_ago">pred %d minútami</string> <string name="minutes_ago">pred %d minútami</string>
<string name="unread_conversations">neprečítané konverzácie</string>
<string name="sending">posielam...</string> <string name="sending">posielam...</string>
<string name="nick_in_use">Prezývka už existuje</string> <string name="nick_in_use">Prezývka už existuje</string>
<string name="admin">Administrátor</string> <string name="admin">Administrátor</string>

View File

@ -25,7 +25,6 @@
<string name="just_now">управо сад</string> <string name="just_now">управо сад</string>
<string name="minute_ago">пре минут</string> <string name="minute_ago">пре минут</string>
<string name="minutes_ago">пре %d минута</string> <string name="minutes_ago">пре %d минута</string>
<string name="unread_conversations">непрочитане преписке</string>
<string name="sending">шаљем…</string> <string name="sending">шаљем…</string>
<string name="message_decrypting">Дешифрујем поруку, сачекајте…</string> <string name="message_decrypting">Дешифрујем поруку, сачекајте…</string>
<string name="pgp_message">ОпенПГП шифрована порука</string> <string name="pgp_message">ОпенПГП шифрована порука</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">just nu</string> <string name="just_now">just nu</string>
<string name="minute_ago">1 min sedan</string> <string name="minute_ago">1 min sedan</string>
<string name="minutes_ago">%d min sedan</string> <string name="minutes_ago">%d min sedan</string>
<string name="unread_conversations">olästa konversationer</string>
<string name="sending">skickar…</string> <string name="sending">skickar…</string>
<string name="message_decrypting">Avkrypterar meddelande. Vänta…</string> <string name="message_decrypting">Avkrypterar meddelande. Vänta…</string>
<string name="pgp_message">OpenPGP-krypterat meddelande</string> <string name="pgp_message">OpenPGP-krypterat meddelande</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">şimdi</string> <string name="just_now">şimdi</string>
<string name="minute_ago">1 dakika önce</string> <string name="minute_ago">1 dakika önce</string>
<string name="minutes_ago">%d dakika önc</string> <string name="minutes_ago">%d dakika önc</string>
<string name="unread_conversations">okunmamış konuşmalar</string>
<string name="sending">gönderiyor…</string> <string name="sending">gönderiyor…</string>
<string name="message_decrypting">İleti deşifre ediliyor. Lütfen bekleyin…</string> <string name="message_decrypting">İleti deşifre ediliyor. Lütfen bekleyin…</string>
<string name="pgp_message">OpenPGP şifreli ileti</string> <string name="pgp_message">OpenPGP şifreli ileti</string>

View File

@ -28,7 +28,6 @@
<string name="just_now">щойно</string> <string name="just_now">щойно</string>
<string name="minute_ago">1 хвилину тому</string> <string name="minute_ago">1 хвилину тому</string>
<string name="minutes_ago">%d хвилин тому</string> <string name="minutes_ago">%d хвилин тому</string>
<string name="unread_conversations">не переглянуті розмови </string>
<string name="sending">відправляю…</string> <string name="sending">відправляю…</string>
<string name="message_decrypting">Розшифровую повідомлення. Зачекайте, будь ласка…</string> <string name="message_decrypting">Розшифровую повідомлення. Зачекайте, будь ласка…</string>
<string name="pgp_message">Повідомлення, зашифроване OpenPGP</string> <string name="pgp_message">Повідомлення, зашифроване OpenPGP</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">mới đây</string> <string name="just_now">mới đây</string>
<string name="minute_ago">1 phút trước</string> <string name="minute_ago">1 phút trước</string>
<string name="minutes_ago">%d phút trước</string> <string name="minutes_ago">%d phút trước</string>
<string name="unread_conversations">Các hội thoại chưa đọc</string>
<string name="sending">đang gửi...</string> <string name="sending">đang gửi...</string>
<string name="message_decrypting">Đang giải mã tin nhắn. Xin chờ...</string> <string name="message_decrypting">Đang giải mã tin nhắn. Xin chờ...</string>
<string name="pgp_message">Tin nhắn mã hoá bằng OpenPGP</string> <string name="pgp_message">Tin nhắn mã hoá bằng OpenPGP</string>

View File

@ -28,7 +28,6 @@
<string name="just_now">刚刚</string> <string name="just_now">刚刚</string>
<string name="minute_ago">1分钟前</string> <string name="minute_ago">1分钟前</string>
<string name="minutes_ago">%d分钟前</string> <string name="minutes_ago">%d分钟前</string>
<string name="unread_conversations">未读会话</string>
<string name="sending">正在发送…</string> <string name="sending">正在发送…</string>
<string name="message_decrypting">解密信息中. 请稍候…</string> <string name="message_decrypting">解密信息中. 请稍候…</string>
<string name="pgp_message">OpenPGP 加密的信息</string> <string name="pgp_message">OpenPGP 加密的信息</string>

View File

@ -21,7 +21,6 @@
<string name="just_now">剛剛</string> <string name="just_now">剛剛</string>
<string name="minute_ago">1 分鐘前</string> <string name="minute_ago">1 分鐘前</string>
<string name="minutes_ago">%d分鐘前</string> <string name="minutes_ago">%d分鐘前</string>
<string name="unread_conversations">未讀會話</string>
<string name="sending">正在發送…</string> <string name="sending">正在發送…</string>
<string name="message_decrypting">訊息解密中,請稍候…</string> <string name="message_decrypting">訊息解密中,請稍候…</string>
<string name="pgp_message">OpenPGP 加密的信息</string> <string name="pgp_message">OpenPGP 加密的信息</string>

View File

@ -30,7 +30,7 @@
<string name="just_now">just now</string> <string name="just_now">just now</string>
<string name="minute_ago">1 min ago</string> <string name="minute_ago">1 min ago</string>
<string name="minutes_ago">%d mins ago</string> <string name="minutes_ago">%d mins ago</string>
<string name="unread_conversations">unread Conversations</string> <string name="x_unread_conversations">%d unread conversations</string>
<string name="sending">sending…</string> <string name="sending">sending…</string>
<string name="message_decrypting">Decrypting message. Please wait…</string> <string name="message_decrypting">Decrypting message. Please wait…</string>
<string name="pgp_message">OpenPGP encrypted message</string> <string name="pgp_message">OpenPGP encrypted message</string>

View File

@ -19,4 +19,8 @@
<string name="no_microphone_permission">O Quicksy necessita de acesso ao microfone</string> <string name="no_microphone_permission">O Quicksy necessita de acesso ao microfone</string>
<string name="foreground_service_channel_description">Essa categoria de notificação é utilizada para exibir uma notificação permanente indicando que o Quicksy está em execução.</string> <string name="foreground_service_channel_description">Essa categoria de notificação é utilizada para exibir uma notificação permanente indicando que o Quicksy está em execução.</string>
<string name="set_profile_picture">Imagem de perfil do Quicksy</string> <string name="set_profile_picture">Imagem de perfil do Quicksy</string>
</resources> <string name="not_available_in_your_country">Quicksy agora está disponível no seu país.</string>
<string name="unable_to_verify_server_identity">Não foi possível verificar a identidade do servidor.</string>
<string name="unknown_security_error">Erro de segurança desconhecido.</string>
<string name="timeout_while_connecting_to_server">Tempo esgotado ao tentar conectar ao servidor.</string>
</resources>