From e38aa30a847cd100084e28749c98a6e1df76e490 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 11 Dec 2020 11:05:08 +0100 Subject: [PATCH 01/21] minor code clean up --- src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java b/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java index 00f42bcea..13b33570b 100644 --- a/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java @@ -29,7 +29,7 @@ public class SSLSocketHelper { final Collection 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); From d1490673bb64efc45aea932280152602a11a38cc Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 11 Dec 2020 11:29:23 +0100 Subject: [PATCH 02/21] work around race condition after opening easy invite dialog --- .../siacs/conversations/services/MessageArchiveService.java | 3 +++ .../conversations/ui/ConversationsOverviewFragment.java | 6 +++++- src/main/res/values/strings.xml | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index 89ddec12b..47d4a240b 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -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(); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java index ae81874e1..3c3c53f63 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java @@ -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 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 selectedAccount = new AtomicReference<>(accounts.get(0)); diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index a9670533f..5aba03dfd 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -953,4 +953,5 @@ Invite to Conversations Unable to parse invite Server does not support generating invites + No active accounts support this feature From 090b3b18d0565d1378a3fbfeb583fcca3692a21f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 11 Dec 2020 14:25:56 +0100 Subject: [PATCH 03/21] =?UTF-8?q?don=E2=80=99t=20check=20for=20inRoster=20?= =?UTF-8?q?when=20doing=20jingle=20with=20oneself.=20fixes=20#3947?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/eu/siacs/conversations/ui/XmppActivity.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index fcdccb9c4..e143cee3d 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -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()); } } From 39229c34f6119d2233bdb4dc013cd8677329eef8 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 18 Dec 2020 21:18:09 +0100 Subject: [PATCH 04/21] cancel touch event after opening context menu in search view --- src/main/java/eu/siacs/conversations/ui/SearchActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/SearchActivity.java b/src/main/java/eu/siacs/conversations/ui/SearchActivity.java index d73249f4b..1b2430f6d 100644 --- a/src/main/java/eu/siacs/conversations/ui/SearchActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SearchActivity.java @@ -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); From b4db2e528436c5e5f08be181cc2e9168179adfd9 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 22 Dec 2020 14:30:54 +0100 Subject: [PATCH 05/21] make ongoing call check null safe. fixes #3951 --- .../ui/ConversationFragment.java | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 8bbd804a9..3e9b2600e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -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 = activity.xmppConnectionService.getJingleConnectionManager().getOngoingRtpConnection(conversation.getContact()); + final XmppConnectionService service = activity.xmppConnectionService; + final Optional 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()) { From f23016c967c87df28543b781b15780f1f877802e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 22 Dec 2020 17:50:26 +0100 Subject: [PATCH 06/21] offline presences aborts session proposals. fixes #3943 --- .../services/XmppConnectionService.java | 45 ++++++++++--------- .../xmpp/jingle/JingleConnectionManager.java | 31 ++++++++++--- 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 0f2423f63..3d172f458 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -206,23 +206,26 @@ public class XmppConnectionService extends Service { } }; public DatabaseBackend databaseBackend; - private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor("ContactMerger"); + private final ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor("ContactMerger"); private long mLastActivity = 0; - private FileBackend fileBackend = new FileBackend(this); + private final FileBackend fileBackend = new FileBackend(this); private MemorizingTrustManager mMemorizingTrustManager; - private NotificationService mNotificationService = new NotificationService(this); - private ChannelDiscoveryService mChannelDiscoveryService = new ChannelDiscoveryService(this); - private ShortcutService mShortcutService = new ShortcutService(this); - private AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false); - private AtomicBoolean mForceForegroundService = new AtomicBoolean(false); - private AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false); - private AtomicReference ongoingCall = new AtomicReference<>(); - private OnMessagePacketReceived mMessageParser = new MessageParser(this); - private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); - private IqParser mIqParser = new IqParser(this); - private MessageGenerator mMessageGenerator = new MessageGenerator(this); + private final NotificationService mNotificationService = new NotificationService(this); + private final ChannelDiscoveryService mChannelDiscoveryService = new ChannelDiscoveryService(this); + private final ShortcutService mShortcutService = new ShortcutService(this); + private final AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false); + private final AtomicBoolean mForceForegroundService = new AtomicBoolean(false); + private final AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false); + private final AtomicReference ongoingCall = new AtomicReference<>(); + private final OnMessagePacketReceived mMessageParser = new MessageParser(this); + private final OnPresencePacketReceived mPresenceParser = new PresenceParser(this); + private final IqParser mIqParser = new IqParser(this); + private final MessageGenerator mMessageGenerator = new MessageGenerator(this); public OnContactStatusChanged onContactStatusChanged = (contact, online) -> { - Conversation conversation = find(getConversations(), contact); + if (!online) { + getJingleConnectionManager().failProposedSessions(contact.getAccount(), contact.getJid().asBareJid()); + } + final Conversation conversation = find(getConversations(), contact); if (conversation != null) { if (online) { if (contact.getPresences().size() == 1) { @@ -231,14 +234,14 @@ public class XmppConnectionService extends Service { } } }; - private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this); + private final PresenceGenerator mPresenceGenerator = new PresenceGenerator(this); private List accounts; - private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(this); - private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(this); - private AvatarService mAvatarService = new AvatarService(this); - private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); - private PushManagementService mPushManagementService = new PushManagementService(this); - private QuickConversationsService mQuickConversationsService = new QuickConversationsService(this); + private final JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(this); + private final HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(this); + private final AvatarService mAvatarService = new AvatarService(this); + private final MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); + private final PushManagementService mPushManagementService = new PushManagementService(this); + private final QuickConversationsService mQuickConversationsService = new QuickConversationsService(this); private final ConversationsFileObserver fileObserver = new ConversationsFileObserver( Environment.getExternalStorageDirectory().getAbsolutePath() ) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 178ac659c..778b254ea 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -361,7 +361,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { } } } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved out of order jingle message"+message); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved out of order jingle message" + message); } } @@ -681,11 +681,30 @@ public class JingleConnectionManager extends AbstractConnectionManager { Log.d(Config.LOGTAG, "session proposal already at discovered. not going to fall back"); return; } - this.rtpSessionProposals.put(sessionProposal, target); - final RtpEndUserState endUserState = target.toEndUserState(); - toneManager.transition(endUserState, sessionProposal.media); - mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, sessionProposal.with, sessionProposal.sessionId, endUserState); - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": flagging session " + sessionId + " as " + target); + updateProposedSessionDiscovered(sessionProposal, target); + } + } + + public void updateProposedSessionDiscovered(RtpSessionProposal sessionProposal, final DeviceDiscoveryState target) { + this.rtpSessionProposals.put(sessionProposal, target); + final RtpEndUserState endUserState = target.toEndUserState(); + toneManager.transition(endUserState, sessionProposal.media); + mXmppConnectionService.notifyJingleRtpConnectionUpdate(sessionProposal.account, sessionProposal.with, sessionProposal.sessionId, endUserState); + Log.d(Config.LOGTAG, sessionProposal.account.getJid().asBareJid() + ": flagging session " + sessionProposal.sessionId + " as " + target); + } + + public void failProposedSessions(final Account account, Jid from) { + synchronized (this.rtpSessionProposals) { + for (Map.Entry entry : rtpSessionProposals.entrySet()) { + final RtpSessionProposal rtpSessionProposal = entry.getKey(); + final DeviceDiscoveryState state = entry.getValue(); + if (state != DeviceDiscoveryState.DISCOVERED) { + continue; + } + if (rtpSessionProposal.with.equals(from) && rtpSessionProposal.account.getJid().equals(account.getJid())) { + updateProposedSessionDiscovered(rtpSessionProposal, JingleConnectionManager.DeviceDiscoveryState.FAILED); + } + } } } From 692ee6c9fbd03f38aa8ab969077b77cc77357854 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 30 Dec 2020 15:57:42 +0100 Subject: [PATCH 07/21] SCRAM remove cache. made digest and hmac non static DIGEST and HMAC were static variables. Those are initialized by what ever concrete implementation gets executed first. (Perform SCRAM-SHA1 first and those variables got initialized with SHA1 variants) For subsequent SHA256 executions those variables contained wrong values. --- .../crypto/sasl/ScramMechanism.java | 72 ++++++++----------- .../conversations/crypto/sasl/ScramSha1.java | 13 +++- .../crypto/sasl/ScramSha256.java | 13 +++- 3 files changed, 48 insertions(+), 50 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java index 4d40d2b74..dbc73c78f 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java @@ -3,13 +3,11 @@ package eu.siacs.conversations.crypto.sasl; import android.annotation.TargetApi; import android.os.Build; import android.util.Base64; -import android.util.LruCache; 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; @@ -24,30 +22,22 @@ abstract class ScramMechanism extends SaslMechanism { 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 CACHE; - static HMac HMAC; - static Digest DIGEST; - static { - CACHE = new LruCache(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 KeyPair getKeyPair(final String password, final String salt, final int iterations) { + try { + 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); + } catch (final InvalidKeyException | NumberFormatException e) { + return null; + } } private final String clientNonce; @@ -63,20 +53,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 +76,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,14 +162,7 @@ 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() - ); + final KeyPair keys = getKeyPair(CryptoHelper.saslPrep(account.getPassword()), salt, iterationCount); if (keys == null) { throw new AuthenticationException("Invalid keys generated"); } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java index 13593778d..5558d6a43 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java @@ -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,9 +10,15 @@ 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()); + + @Override + protected HMac getHMAC() { + return new HMac(new SHA1Digest()); + } + + @Override + protected Digest getDigest() { + return new SHA1Digest(); } public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) { diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java index 1b7a969d9..866c1ea79 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java @@ -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,9 +10,15 @@ 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()); + + @Override + protected HMac getHMAC() { + return new HMac(new SHA256Digest()); + } + + @Override + protected Digest getDigest() { + return new SHA256Digest(); } public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) { From 2a57c92f63afbe6c8627b84ba959614c2667de96 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 30 Dec 2020 22:01:08 +0100 Subject: [PATCH 08/21] rewrote scram cache implementation --- .../crypto/sasl/ScramMechanism.java | 52 ++++++++++++++++--- 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java index dbc73c78f..5af911cc7 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java @@ -4,6 +4,10 @@ import android.annotation.TargetApi; import android.os.Build; import android.util.Base64; +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; @@ -11,6 +15,7 @@ import org.bouncycastle.crypto.params.KeyParameter; 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; @@ -27,17 +32,46 @@ abstract class ScramMechanism extends SaslMechanism { protected abstract Digest getDigest(); - private KeyPair getKeyPair(final String password, final String salt, final int iterations) { - try { + private static final Cache 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); - } catch (final InvalidKeyException | NumberFormatException e) { - return null; - } + }); } private final String clientNonce; @@ -162,8 +196,10 @@ abstract class ScramMechanism extends SaslMechanism { final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ',' + clientFinalMessageWithoutProof).getBytes(); - final KeyPair keys = getKeyPair(CryptoHelper.saslPrep(account.getPassword()), salt, iterationCount); - 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; From 0e54d8a2cfb65e5cc8811d0ffe76706de1a25d3a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 31 Dec 2020 09:32:05 +0100 Subject: [PATCH 09/21] implement SCRAM-SHA512 --- .../conversations/crypto/sasl/Anonymous.java | 32 ++-- .../conversations/crypto/sasl/DigestMd5.java | 143 +++++++++--------- .../conversations/crypto/sasl/External.java | 33 ++-- .../conversations/crypto/sasl/Plain.java | 41 ++--- .../crypto/sasl/SaslMechanism.java | 97 ++++++------ .../crypto/sasl/ScramMechanism.java | 3 - .../conversations/crypto/sasl/ScramSha1.java | 40 ++--- .../crypto/sasl/ScramSha256.java | 40 ++--- .../crypto/sasl/ScramSha512.java | 39 +++++ .../conversations/crypto/sasl/Tokenizer.java | 120 +++++++-------- .../conversations/xmpp/XmppConnection.java | 60 ++++---- 11 files changed, 352 insertions(+), 296 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/Anonymous.java b/src/main/java/eu/siacs/conversations/crypto/sasl/Anonymous.java index 4672bc1ff..a9abb2bf8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/Anonymous.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/Anonymous.java @@ -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 ""; + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java b/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java index 3bf5fe342..74d4463d5 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java @@ -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; + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java index 294f382d7..6e0ed4390 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java @@ -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); + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java b/src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java index 7b774b681..d5cc037e1 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/Plain.java @@ -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); + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java index 5833309ce..86fd6524e 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/SaslMechanism.java @@ -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 ""; + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java index 5af911cc7..807056bf8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramMechanism.java @@ -1,7 +1,5 @@ package eu.siacs.conversations.crypto.sasl; -import android.annotation.TargetApi; -import android.os.Build; import android.util.Base64; import com.google.common.base.Objects; @@ -21,7 +19,6 @@ 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,,"; diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java index 5558d6a43..c58dd147c 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha1.java @@ -11,27 +11,29 @@ import eu.siacs.conversations.xml.TagWriter; public class ScramSha1 extends ScramMechanism { - @Override - protected HMac getHMAC() { - return new HMac(new SHA1Digest()); - } + public static final String MECHANISM = "SCRAM-SHA-1"; - @Override - protected Digest getDigest() { - return new SHA1Digest(); - } + @Override + protected HMac getHMAC() { + return new HMac(new SHA1Digest()); + } - public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) { - super(tagWriter, account, rng); - } + @Override + protected Digest getDigest() { + return new SHA1Digest(); + } - @Override - public int getPriority() { - return 20; - } + public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) { + super(tagWriter, account, rng); + } - @Override - public String getMechanism() { - return "SCRAM-SHA-1"; - } + @Override + public int getPriority() { + return 20; + } + + @Override + public String getMechanism() { + return MECHANISM; + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java index 866c1ea79..d5dc42b07 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha256.java @@ -11,27 +11,29 @@ import eu.siacs.conversations.xml.TagWriter; public class ScramSha256 extends ScramMechanism { - @Override - protected HMac getHMAC() { - return new HMac(new SHA256Digest()); - } + public static final String MECHANISM = "SCRAM-SHA-256"; - @Override - protected Digest getDigest() { - return new SHA256Digest(); - } + @Override + protected HMac getHMAC() { + return new HMac(new SHA256Digest()); + } - public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) { - super(tagWriter, account, rng); - } + @Override + protected Digest getDigest() { + return new SHA256Digest(); + } - @Override - public int getPriority() { - return 25; - } + public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) { + super(tagWriter, account, rng); + } - @Override - public String getMechanism() { - return "SCRAM-SHA-256"; - } + @Override + public int getPriority() { + return 25; + } + + @Override + public String getMechanism() { + return MECHANISM; + } } diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java new file mode 100644 index 000000000..dbd30945c --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/ScramSha512.java @@ -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; + } +} diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/Tokenizer.java b/src/main/java/eu/siacs/conversations/crypto/sasl/Tokenizer.java index e37e0fa71..f9ba24f09 100644 --- a/src/main/java/eu/siacs/conversations/crypto/sasl/Tokenizer.java +++ b/src/main/java/eu/siacs/conversations/crypto/sasl/Tokenizer.java @@ -10,69 +10,69 @@ import java.util.NoSuchElementException; * A tokenizer for GS2 header strings */ public final class Tokenizer implements Iterator, Iterable { - private final List parts; - private int index; + private final List 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 iterator() { - return parts.iterator(); - } + /** + * Returns an {@link java.util.Iterator} for the elements in this object. + * + * @return An {@code Iterator} instance. + */ + @Override + public Iterator iterator() { + return parts.iterator(); + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 3f699e64e..64db553fe 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -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; @@ -870,20 +871,21 @@ public class XmppConnection implements Runnable { } private void authenticate() throws IOException { - final List mechanisms = extractMechanisms(streamFeatures - .findChild("mechanisms")); + final List 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) { @@ -1265,27 +1267,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 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 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); + } + } }); } From 0569febf67b650889316e78b14938f08560f4c9a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 31 Dec 2020 10:27:06 +0100 Subject: [PATCH 10/21] minor code clean up in XmppConnection class --- .../conversations/xmpp/XmppConnection.java | 91 +++++++++---------- 1 file changed, 42 insertions(+), 49 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 64db553fe..2054dfa53 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -107,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 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 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; @@ -160,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; @@ -772,7 +769,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() + "'"); @@ -781,7 +778,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() + "'"); @@ -835,7 +832,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); @@ -1326,7 +1323,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 items = new HashSet(); + final HashSet items = new HashSet<>(); final List elements = packet.query().getChildren(); for (final Element element : elements) { if (element.getName().equals("item")) { @@ -1354,23 +1351,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; @@ -1623,8 +1616,8 @@ public class XmppConnection implements Runnable { } public List getMucServersWithholdAccount() { - List servers = getMucServers(); - servers.remove(account.getDomain()); + final List servers = getMucServers(); + servers.remove(account.getDomain().toEscapedString()); return servers; } @@ -1795,7 +1788,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) { @@ -1803,7 +1796,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) { From adb5a2b2c2c31353b5ba0645cd1a82a2965bff4c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 31 Dec 2020 10:41:14 +0100 Subject: [PATCH 11/21] pulled translations from transifex --- src/conversations/res/values-it/strings.xml | 6 +++++- src/conversations/res/values-pt-rBR/strings.xml | 6 +++++- src/main/res/values-it/strings.xml | 3 +++ src/main/res/values-pt-rBR/strings.xml | 4 ++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/conversations/res/values-it/strings.xml b/src/conversations/res/values-it/strings.xml index eb7b7fe52..45800a7b8 100644 --- a/src/conversations/res/values-it/strings.xml +++ b/src/conversations/res/values-it/strings.xml @@ -11,4 +11,8 @@ In ogni caso per facilitare puoi creare facilmente un account su conversations.i 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. Il tuo invito al server Codice di approvvigionamento formattato male - \ No newline at end of file + Tocca il pulsante condividi per inviare al contatto un invito per %1$s. + Se il contatto è vicino, può anche scansionare il codice sottostante per accettare il tuo invito. + Unisciti a %1$s e chatta con me: %2$s + Condividi invito con... + \ No newline at end of file diff --git a/src/conversations/res/values-pt-rBR/strings.xml b/src/conversations/res/values-pt-rBR/strings.xml index f48bf1026..1d51c86b6 100644 --- a/src/conversations/res/values-pt-rBR/strings.xml +++ b/src/conversations/res/values-pt-rBR/strings.xml @@ -9,4 +9,8 @@ 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. Seu convite do servidor Código de provisionamento formatado de maneira imprópria - \ No newline at end of file + Toque no botão compartilhar para enviar, para seu contato, um convite para %1$s. + Se seu contato estiver por perto, ele também pode escanear o código abaixo para aceitar seu convite. + Junte-se a %1$s e converse comigo: %2$s + Compartilhe o convite com... + \ No newline at end of file diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 600cc0d19..00aa0dccb 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -948,4 +948,7 @@ Recapiti falliti Altre opzioni Nessuna applicazione trovata + Invita su Conversations + Impossibile analizzare l\'invito + Il server non supporta la generazione di inviti diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 8c0f8518a..69007c761 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -947,4 +947,8 @@ Entregas não efetuadas Mais opções + Não foi encontrado nenhum aplicativo + Convidar para o Conversations + Não foi possível processar o convite + O servidor não suporta a criação de convites From 6d13ee52f019d41134d69e24d8913e0f138d5cc4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 31 Dec 2020 10:49:29 +0100 Subject: [PATCH 12/21] version bump to 2.9.3 + changelog --- CHANGELOG.md | 6 ++++++ build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/403.txt | 3 +++ 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/403.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index e58b08305..65baaedd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### 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 diff --git a/build.gradle b/build.gradle index 87ce7a1bb..f4de663a7 100644 --- a/build.gradle +++ b/build.gradle @@ -95,8 +95,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 29 - versionCode 402 - versionName "2.9.2" + versionCode 403 + versionName "2.9.3" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/403.txt b/fastlane/metadata/android/en-US/changelogs/403.txt new file mode 100644 index 000000000..99d62ca48 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/403.txt @@ -0,0 +1,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 From 8eb685a7eb1593c453a1812264c3115f90e27321 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 2 Jan 2021 09:09:23 +0100 Subject: [PATCH 13/21] pulled translations from transifex --- src/main/res/values-cs/strings.xml | 12 ++++++++++++ src/main/res/values-de/strings.xml | 1 + src/main/res/values-gl/strings.xml | 1 + src/main/res/values-ro-rRO/strings.xml | 1 + 4 files changed, 15 insertions(+) diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 4b0ce3f36..dd2a09c53 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -4,7 +4,9 @@ Nová konverzace Nastavení účtů Nastavení účtu + Zavřít konverzaci Detaily kontaktu + Detaily skupinového chatu Přidat účet Upravit jméno Přidat do adresáře @@ -17,6 +19,7 @@ Nastavení Sdílet s konverzací Začít konverzaci + Vybrat kontakt Seznam blokovaných právě teď před minutou @@ -30,14 +33,17 @@ Moderátor Účastník Návštěvník + Přejete si odstranit %s ze seznamu kontaktů? Předešlé rozhovory nebudou odstraněny. Chcete zablokovat příjem zpráv od %s? Chcete odblokovat příjem zpráv od %s? Zablokovat všechny kontakty z %s? Odblokovat všechny kontakty z %s? Kontakty zablokovány + Přejete si odstranit %s ze záložek? Předešlé rozhovory pod záložkou nebudou odstraněny. Registrovat nový účet na serveru Změnit heslo na serveru Sdílet s... + Začít konverzaci Pozvat kontakt Kontakty Zrušit @@ -144,9 +150,14 @@ nedostupný Chybí oznámení o veřejném klíči právě spatřen + naposledy spatřen před minutou naposledy spatřen před %d minutami + naposledy spatřen před hodinou naposledy spatřen před %d hodinami + naposledy spatřen včera naposledy spatřen před %d dny + Šifrovaná zpráva. Nainstalujte OpenKeychain pro její dešifrování. + Nalezeny nové OpenPGP šifrované zprávy OpenPGP ID klíče OMEMO otisk v\\OMEMO otisk @@ -160,6 +171,7 @@ Záložky Hledat Vložit kontakt + Smazat kontakt Zobrazit detaily kontaktu Zablokovat kontakt Odblokovat kontakt diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 5f0174d12..0b0429f21 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -951,4 +951,5 @@ Einladung zu Conversations Einladung kann nicht gelesen werden Server unterstützt keine Generierung von Einladungen + Keine aktiven Konten unterstützen diese Funktion diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index a9d0c8648..09528305c 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -951,4 +951,5 @@ Convida a Conversations Non se puido enviar o convite O servidor non soporta a creación de convites + Ningunha conta activa soporta esta función diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index c8649d9d7..017e59935 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -961,4 +961,5 @@ Invitați la Conversations Nu s-a putut procesa invitația Serverul nu suportă generarea de invitații + Nici un cont activ nu suporta această caracteristică From 2bec5459c5a653aba80e6150309a8ca2fa6e8b52 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 3 Jan 2021 16:05:17 +0100 Subject: [PATCH 14/21] properly null check ufrag and pwd before whitespace checking. fixes #3956 --- .../siacs/conversations/persistance/FileBackend.java | 2 +- .../conversations/xmpp/jingle/SessionDescription.java | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 977c9fc27..a072e9d9a 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -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; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java index af7213220..52762407f 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/SessionDescription.java @@ -143,14 +143,16 @@ public class SessionDescription { final ArrayListMultimap 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) { From 6c2f0d29d8da5979c0c2f3d090660d0e704cd37d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 3 Jan 2021 16:10:51 +0100 Subject: [PATCH 15/21] use svg logo in doap file --- doap.rdf => conversations.doap | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) rename doap.rdf => conversations.doap (97%) diff --git a/doap.rdf b/conversations.doap similarity index 97% rename from doap.rdf rename to conversations.doap index 7dfd8f065..6dccb05f2 100644 --- a/doap.rdf +++ b/conversations.doap @@ -1,7 +1,10 @@ - + Conversations 2014-01-14 @@ -22,13 +25,12 @@ en - + Java Android - From 69dca53bf36366f47e9d500d2fe9edec5f2103c4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 3 Jan 2021 16:17:33 +0100 Subject: [PATCH 16/21] use libwebrtc-m87 --- .travis.yml | 2 +- build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 239637ba4..48a836ed1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/build.gradle b/build.gradle index f4de663a7..de1c4efed 100644 --- a/build.gradle +++ b/build.gradle @@ -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') } ext { @@ -93,7 +93,7 @@ android { compileSdkVersion 29 defaultConfig { - minSdkVersion 16 + minSdkVersion 21 targetSdkVersion 29 versionCode 403 versionName "2.9.3" From 17c697eed9563b50b48055919fc3c47a86ceb3c0 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Sat, 26 Dec 2020 20:32:04 +0100 Subject: [PATCH 17/21] add 'id' attribute to outgoing ICE-UDP candidates this attribute is mandatory as per the XEP. --- .../conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java index 99cf4d743..e43556d17 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java @@ -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); From 372ddbfb49cdea71c47ba40089b84eba550bb5ac Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 6 Jan 2021 09:03:42 +0100 Subject: [PATCH 18/21] Revert "offline presences aborts session proposals. fixes #3943" This reverts commit f23016c967c87df28543b781b15780f1f877802e. --- .../services/XmppConnectionService.java | 45 +++++++++---------- .../xmpp/jingle/JingleConnectionManager.java | 31 +++---------- 2 files changed, 27 insertions(+), 49 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 3d172f458..0f2423f63 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -206,26 +206,23 @@ public class XmppConnectionService extends Service { } }; public DatabaseBackend databaseBackend; - private final ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor("ContactMerger"); + private ReplacingSerialSingleThreadExecutor mContactMergerExecutor = new ReplacingSerialSingleThreadExecutor("ContactMerger"); private long mLastActivity = 0; - private final FileBackend fileBackend = new FileBackend(this); + private FileBackend fileBackend = new FileBackend(this); private MemorizingTrustManager mMemorizingTrustManager; - private final NotificationService mNotificationService = new NotificationService(this); - private final ChannelDiscoveryService mChannelDiscoveryService = new ChannelDiscoveryService(this); - private final ShortcutService mShortcutService = new ShortcutService(this); - private final AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false); - private final AtomicBoolean mForceForegroundService = new AtomicBoolean(false); - private final AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false); - private final AtomicReference ongoingCall = new AtomicReference<>(); - private final OnMessagePacketReceived mMessageParser = new MessageParser(this); - private final OnPresencePacketReceived mPresenceParser = new PresenceParser(this); - private final IqParser mIqParser = new IqParser(this); - private final MessageGenerator mMessageGenerator = new MessageGenerator(this); + private NotificationService mNotificationService = new NotificationService(this); + private ChannelDiscoveryService mChannelDiscoveryService = new ChannelDiscoveryService(this); + private ShortcutService mShortcutService = new ShortcutService(this); + private AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false); + private AtomicBoolean mForceForegroundService = new AtomicBoolean(false); + private AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false); + private AtomicReference ongoingCall = new AtomicReference<>(); + private OnMessagePacketReceived mMessageParser = new MessageParser(this); + private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); + private IqParser mIqParser = new IqParser(this); + private MessageGenerator mMessageGenerator = new MessageGenerator(this); public OnContactStatusChanged onContactStatusChanged = (contact, online) -> { - if (!online) { - getJingleConnectionManager().failProposedSessions(contact.getAccount(), contact.getJid().asBareJid()); - } - final Conversation conversation = find(getConversations(), contact); + Conversation conversation = find(getConversations(), contact); if (conversation != null) { if (online) { if (contact.getPresences().size() == 1) { @@ -234,14 +231,14 @@ public class XmppConnectionService extends Service { } } }; - private final PresenceGenerator mPresenceGenerator = new PresenceGenerator(this); + private PresenceGenerator mPresenceGenerator = new PresenceGenerator(this); private List accounts; - private final JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(this); - private final HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(this); - private final AvatarService mAvatarService = new AvatarService(this); - private final MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); - private final PushManagementService mPushManagementService = new PushManagementService(this); - private final QuickConversationsService mQuickConversationsService = new QuickConversationsService(this); + private JingleConnectionManager mJingleConnectionManager = new JingleConnectionManager(this); + private HttpConnectionManager mHttpConnectionManager = new HttpConnectionManager(this); + private AvatarService mAvatarService = new AvatarService(this); + private MessageArchiveService mMessageArchiveService = new MessageArchiveService(this); + private PushManagementService mPushManagementService = new PushManagementService(this); + private QuickConversationsService mQuickConversationsService = new QuickConversationsService(this); private final ConversationsFileObserver fileObserver = new ConversationsFileObserver( Environment.getExternalStorageDirectory().getAbsolutePath() ) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 778b254ea..178ac659c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -361,7 +361,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { } } } else { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved out of order jingle message" + message); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved out of order jingle message"+message); } } @@ -681,30 +681,11 @@ public class JingleConnectionManager extends AbstractConnectionManager { Log.d(Config.LOGTAG, "session proposal already at discovered. not going to fall back"); return; } - updateProposedSessionDiscovered(sessionProposal, target); - } - } - - public void updateProposedSessionDiscovered(RtpSessionProposal sessionProposal, final DeviceDiscoveryState target) { - this.rtpSessionProposals.put(sessionProposal, target); - final RtpEndUserState endUserState = target.toEndUserState(); - toneManager.transition(endUserState, sessionProposal.media); - mXmppConnectionService.notifyJingleRtpConnectionUpdate(sessionProposal.account, sessionProposal.with, sessionProposal.sessionId, endUserState); - Log.d(Config.LOGTAG, sessionProposal.account.getJid().asBareJid() + ": flagging session " + sessionProposal.sessionId + " as " + target); - } - - public void failProposedSessions(final Account account, Jid from) { - synchronized (this.rtpSessionProposals) { - for (Map.Entry entry : rtpSessionProposals.entrySet()) { - final RtpSessionProposal rtpSessionProposal = entry.getKey(); - final DeviceDiscoveryState state = entry.getValue(); - if (state != DeviceDiscoveryState.DISCOVERED) { - continue; - } - if (rtpSessionProposal.with.equals(from) && rtpSessionProposal.account.getJid().equals(account.getJid())) { - updateProposedSessionDiscovered(rtpSessionProposal, JingleConnectionManager.DeviceDiscoveryState.FAILED); - } - } + this.rtpSessionProposals.put(sessionProposal, target); + final RtpEndUserState endUserState = target.toEndUserState(); + toneManager.transition(endUserState, sessionProposal.media); + mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, sessionProposal.with, sessionProposal.sessionId, endUserState); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": flagging session " + sessionId + " as " + target); } } From d0a2f1f45f848076d9ea834f52218aad3427c618 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 6 Jan 2021 11:46:09 +0100 Subject: [PATCH 19/21] just xmpp things --- .../java/eu/siacs/conversations/entities/MucOptions.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index 1072eeeb9..67657159a 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -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() { From b0584137b419b099e510139b091c1c99ee932667 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 7 Jan 2021 09:12:11 +0100 Subject: [PATCH 20/21] pulled translations from transifex --- src/conversations/res/values-pl/strings.xml | 6 +++++- src/main/res/values-pl/strings.xml | 4 ++++ src/main/res/values-pt-rBR/strings.xml | 1 + src/main/res/values-tr-rTR/strings.xml | 1 + 4 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/conversations/res/values-pl/strings.xml b/src/conversations/res/values-pl/strings.xml index 8f8df4837..4e8b5d619 100644 --- a/src/conversations/res/values-pl/strings.xml +++ b/src/conversations/res/values-pl/strings.xml @@ -9,4 +9,8 @@ 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. Zaproszenie twojego serwera Niepoprawnie sformatowany kod zaopatrywania - \ No newline at end of file + Użyj przycisku udostępniania aby wysłać swojemu kontaktowi zaproszenie do %1$s. + Jeśli twój kontakt jest blisko może przeskanować kod poniżej aby zaakceptować twoje zaproszenie. + Dołącz do %1$s aby porozmawiać ze mną: %2$s + Udostępnij zaproszenie... + \ No newline at end of file diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 51ad53d0e..d411f0d6e 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -970,4 +970,8 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Nie dostarczone wiadomości Więcej ustawień Nie znaleziono żadnej aplikacji + Zaproś do Conversations + Nie można przetworzyć zaproszenia + Serwer nie wspiera tworzenia zaproszeń + Nie ma aktywnych kont wspierających tę funkcję diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index 69007c761..d3724425a 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -951,4 +951,5 @@ Convidar para o Conversations Não foi possível processar o convite O servidor não suporta a criação de convites + Nenhuma conta ativa suporta esse recurso diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml index 83eb48990..1868d2dc5 100644 --- a/src/main/res/values-tr-rTR/strings.xml +++ b/src/main/res/values-tr-rTR/strings.xml @@ -974,4 +974,5 @@ Conversations\'a davet et Davet iletilemedi Sunucu, davet oluşturulmasını desteklemiyor + Bu özelliği destekleyen aktif bir hesap yok From 07cc5c13cac39dae427c5a57d7459ae71723d380 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 11 Jan 2021 11:30:11 +0100 Subject: [PATCH 21/21] version bump to 2.9.4 + changelog --- CHANGELOG.md | 3 +++ build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/404.txt | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/404.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 65baaedd8..9b597c4f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # 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 diff --git a/build.gradle b/build.gradle index de1c4efed..b3b95a841 100644 --- a/build.gradle +++ b/build.gradle @@ -95,8 +95,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 403 - versionName "2.9.3" + versionCode 404 + versionName "2.9.4" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/404.txt b/fastlane/metadata/android/en-US/changelogs/404.txt new file mode 100644 index 000000000..d4f2e7b6d --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/404.txt @@ -0,0 +1 @@ +* minor stability improvements for A/V calls