initial tor support

This commit is contained in:
Daniel Gultsch 2015-11-28 20:11:38 +01:00
parent 06cadab7cc
commit f0b1761ec3
15 changed files with 346 additions and 130 deletions

View file

@ -2,6 +2,10 @@ package eu.siacs.conversations;
import android.graphics.Bitmap;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
public final class Config {

View file

@ -2,6 +2,8 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.SystemClock;
import eu.siacs.conversations.crypto.PgpDecryptionService;
@ -13,6 +15,7 @@ import org.json.JSONObject;
import java.security.PublicKey;
import java.security.interfaces.DSAPublicKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@ -39,6 +42,8 @@ public class Account extends AbstractEntity {
public static final String KEYS = "keys";
public static final String AVATAR = "avatar";
public static final String DISPLAY_NAME = "display_name";
public static final String HOSTNAME = "hostname";
public static final String PORT = "port";
public static final String PINNED_MECHANISM_KEY = "pinned_mechanism";
@ -67,7 +72,20 @@ public class Account extends AbstractEntity {
}
}
public static enum State {
public ArrayList<Parcelable> getHostnamePortBundles() {
ArrayList<Parcelable> values = new ArrayList<>();
Bundle hostPort = new Bundle();
if (hostname != null && !hostname.isEmpty()) {
hostPort.putString("name", hostname);
} else {
hostPort.putString("name", getServer().toString());
}
hostPort.putInt("port", port);
values.add(hostPort);
return values;
}
public enum State {
DISABLED,
OFFLINE,
CONNECTING,
@ -147,6 +165,8 @@ public class Account extends AbstractEntity {
protected JSONObject keys = new JSONObject();
protected String avatar;
protected String displayName = null;
protected String hostname = null;
protected int port = 5222;
protected boolean online = false;
private OtrService mOtrService = null;
private AxolotlService axolotlService = null;
@ -164,12 +184,12 @@ public class Account extends AbstractEntity {
public Account(final Jid jid, final String password) {
this(java.util.UUID.randomUUID().toString(), jid,
password, 0, null, "", null, null);
password, 0, null, "", null, null, null, 5222);
}
public Account(final String uuid, final Jid jid,
final String password, final int options, final String rosterVersion, final String keys,
final String avatar, String displayName) {
final String avatar, String displayName, String hostname, int port) {
this.uuid = uuid;
this.jid = jid;
if (jid.isBareJid()) {
@ -185,6 +205,8 @@ public class Account extends AbstractEntity {
}
this.avatar = avatar;
this.displayName = displayName;
this.hostname = hostname;
this.port = port;
}
public static Account fromCursor(final Cursor cursor) {
@ -201,7 +223,9 @@ public class Account extends AbstractEntity {
cursor.getString(cursor.getColumnIndex(ROSTERVERSION)),
cursor.getString(cursor.getColumnIndex(KEYS)),
cursor.getString(cursor.getColumnIndex(AVATAR)),
cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)));
cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)),
cursor.getString(cursor.getColumnIndex(HOSTNAME)),
cursor.getInt(cursor.getColumnIndex(PORT)));
}
public boolean isOptionSet(final int option) {
@ -236,6 +260,22 @@ public class Account extends AbstractEntity {
this.password = password;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public String getHostname() {
return this.hostname == null ? "" : this.hostname;
}
public void setPort(int port) {
this.port = port;
}
public int getPort() {
return this.port;
}
public State getStatus() {
if (isOptionSet(OPTION_DISABLED)) {
return State.DISABLED;
@ -314,6 +354,8 @@ public class Account extends AbstractEntity {
values.put(ROSTERVERSION, rosterVersion);
values.put(AVATAR, avatar);
values.put(DISPLAY_NAME, displayName);
values.put(HOSTNAME, hostname);
values.put(PORT, port);
return values;
}

View file

@ -1,7 +1,13 @@
package eu.siacs.conversations.http;
import android.os.Build;
import org.apache.http.conn.ssl.StrictHostnameVerifier;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
@ -87,4 +93,12 @@ public class HttpConnectionManager extends AbstractConnectionManager {
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
}
}
public Proxy getProxy() throws IOException {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(InetAddress.getLocalHost(), 9050));
} else {
return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(InetAddress.getLocalHost(), 8118));
}
}
}

View file

@ -10,7 +10,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import java.util.Arrays;
@ -39,10 +42,12 @@ public class HttpDownloadConnection implements Transferable {
private int mStatus = Transferable.STATUS_UNKNOWN;
private boolean acceptedAutomatically = false;
private int mProgress = 0;
private boolean mUseTor = false;
public HttpDownloadConnection(HttpConnectionManager manager) {
this.mHttpConnectionManager = manager;
this.mXmppConnectionService = manager.getXmppConnectionService();
this.mUseTor = mXmppConnectionService.useTorToConnect();
}
@Override
@ -191,8 +196,15 @@ public class HttpDownloadConnection implements Transferable {
try {
Log.d(Config.LOGTAG, "retrieve file size. interactive:" + String.valueOf(interactive));
changeStatus(STATUS_CHECKING);
HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
HttpURLConnection connection;
if (mUseTor) {
connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy());
} else {
connection = (HttpURLConnection) mUrl.openConnection();
}
connection.setRequestMethod("HEAD");
Log.d(Config.LOGTAG,"url: "+connection.getURL().toString());
Log.d(Config.LOGTAG,"connection: "+connection.toString());
connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getIdentityName());
if (connection instanceof HttpsURLConnection) {
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
@ -245,7 +257,12 @@ public class HttpDownloadConnection implements Transferable {
PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_download_"+message.getUuid());
try {
wakeLock.acquire();
HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
HttpURLConnection connection;
if (mUseTor) {
connection = (HttpURLConnection) mUrl.openConnection(mHttpConnectionManager.getProxy());
} else {
connection = (HttpURLConnection) mUrl.openConnection();
}
if (connection instanceof HttpsURLConnection) {
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
}

View file

@ -12,7 +12,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
@ -46,6 +49,7 @@ public class HttpUploadConnection implements Transferable {
private String mime;
private URL mGetUrl;
private URL mPutUrl;
private boolean mUseTor = false;
private byte[] key = null;
@ -56,6 +60,7 @@ public class HttpUploadConnection implements Transferable {
public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
this.mHttpConnectionManager = httpConnectionManager;
this.mXmppConnectionService = httpConnectionManager.getXmppConnectionService();
this.mUseTor = mXmppConnectionService.useTorToConnect();
}
@Override
@ -158,7 +163,11 @@ public class HttpUploadConnection implements Transferable {
try {
wakeLock.acquire();
Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
if (mUseTor) {
connection = (HttpURLConnection) mPutUrl.openConnection(mHttpConnectionManager.getProxy());
} else {
connection = (HttpURLConnection) mPutUrl.openConnection();
}
if (connection instanceof HttpsURLConnection) {
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true);
}

View file

@ -43,7 +43,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history";
private static final int DATABASE_VERSION = 19;
private static final int DATABASE_VERSION = 20;
private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@ -124,7 +124,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Account.DISPLAY_NAME + " TEXT, "
+ Account.ROSTERVERSION + " TEXT," + Account.OPTIONS
+ " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS
+ " TEXT)");
+ " TEXT, " + Account.HOSTNAME + " TEXT, " + Account.PORT + " NUMBER DEFAULT 5222)");
db.execSQL("create table " + Conversation.TABLENAME + " ("
+ Conversation.UUID + " TEXT PRIMARY KEY, " + Conversation.NAME
+ " TEXT, " + Conversation.CONTACT + " TEXT, "
@ -307,6 +307,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
if (oldVersion < 19 && newVersion >= 19) {
db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.DISPLAY_NAME + " TEXT");
}
if (oldVersion < 20 && newVersion >= 20) {
db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.HOSTNAME + " TEXT");
db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN " + Account.PORT + " NUMBER DEFAULT 5222");
}
/* Any migrations that alter the Account table need to happen BEFORE this migration, as it
* depends on account de-serialization.
*/

View file

@ -2106,8 +2106,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void createContact(Contact contact) {
SharedPreferences sharedPref = getPreferences();
boolean autoGrant = sharedPref.getBoolean("grant_new_contacts", true);
boolean autoGrant = getPreferences().getBoolean("grant_new_contacts", true);
if (autoGrant) {
contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
contact.setOption(Contact.Options.ASKING);
@ -2534,10 +2533,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
.getDefaultSharedPreferences(getApplicationContext());
}
public boolean forceEncryption() {
return getPreferences().getBoolean("force_encryption", false);
}
public boolean confirmMessages() {
return getPreferences().getBoolean("confirm_messages", true);
}
@ -2554,6 +2549,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return getPreferences().getBoolean("indicate_received", false);
}
public boolean useTorToConnect() {
return getPreferences().getBoolean("use_tor", false);
}
public int unreadCount() {
int count = 0;
for (Conversation conversation : getConversations()) {

View file

@ -82,10 +82,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
private ImageButton mRegenerateAxolotlKeyButton;
private LinearLayout keys;
private LinearLayout keysCard;
private LinearLayout mNamePort;
private EditText mHostname;
private EditText mPort;
private AlertDialog mCaptchaDialog = null;
private Jid jidToEdit;
private boolean mInitMode = false;
private boolean mUseTor = false;
private Account mAccount;
private String messageFingerprint;
@ -136,6 +140,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
final String password = mPassword.getText().toString();
final String passwordConfirm = mPasswordConfirm.getText().toString();
final String hostname = mHostname.getText().toString();
final String port = mPort.getText().toString();
if (registerNewAccount) {
if (!password.equals(passwordConfirm)) {
mPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
@ -149,6 +155,25 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
mPasswordConfirm.setError(null);
mAccount.setPassword(password);
mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
if (hostname.contains(" ")) {
mHostname.setError(getString(R.string.not_valid_hostname));
mHostname.requestFocus();
return;
}
mAccount.setHostname(hostname);
try {
int numericPort = Integer.parseInt(port);
if (numericPort < 0 || numericPort > 65535) {
mPort.setError(getString(R.string.not_a_valid_port));
mPort.requestFocus();
return;
}
mAccount.setPort(numericPort);
} catch (NumberFormatException e) {
mPort.setError(getString(R.string.not_a_valid_port));
mPort.requestFocus();
return;
}
xmppConnectionService.updateAccount(mAccount);
} else {
if (xmppConnectionService.findAccountByJid(jid) != null) {
@ -319,7 +344,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
unmodified = this.mAccount.getJid().toBareJid().toString();
}
return !unmodified.equals(this.mAccountJid.getText().toString()) ||
!this.mAccount.getPassword().equals(this.mPassword.getText().toString());
!this.mAccount.getPassword().equals(this.mPassword.getText().toString()) ||
!this.mAccount.getHostname().equals(this.mHostname.getText().toString()) ||
!String.valueOf(this.mAccount.getPort()).equals(this.mPort.getText().toString());
}
@Override
@ -368,6 +395,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key);
this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card);
this.keys = (LinearLayout) findViewById(R.id.other_device_keys);
this.mNamePort = (LinearLayout) findViewById(R.id.name_port);
this.mHostname = (EditText) findViewById(R.id.hostname);
this.mHostname.addTextChangedListener(mTextWatcher);
this.mPort = (EditText) findViewById(R.id.port);
this.mPort.setText("5222");
this.mPort.addTextChangedListener(mTextWatcher);
this.mSaveButton = (Button) findViewById(R.id.save_button);
this.mCancelButton = (Button) findViewById(R.id.cancel_button);
this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
@ -448,6 +481,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
}
}
this.mUseTor = getPreferences().getBoolean("use_tor", false);
this.mNamePort.setVisibility(mUseTor ? View.VISIBLE : View.GONE);
}
@Override
@ -529,6 +564,12 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString());
}
this.mPassword.setText(this.mAccount.getPassword());
this.mHostname.setText("");
this.mHostname.getEditableText().append(this.mAccount.getHostname());
this.mPort.setText("");
this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort()));
this.mNamePort.setVisibility(mUseTor ? View.VISIBLE : View.GONE);
}
if (!mInitMode) {
this.mAvatar.setVisibility(View.VISIBLE);

View file

@ -160,6 +160,8 @@ public class SettingsActivity extends XmppActivity implements
} else if (name.equals("dont_trust_system_cas")) {
xmppConnectionService.updateMemorizingTrustmanager();
reconnectAccounts();
} else if (name.equals("use_tor")) {
reconnectAccounts();
}
}

View file

@ -27,6 +27,7 @@ import java.net.ConnectException;
import java.net.IDN;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.UnknownHostException;
import java.net.URL;
@ -233,16 +234,23 @@ public class XmppConnection implements Runnable {
tagReader = new XmlReader(wakeLock);
tagWriter = new TagWriter();
this.changeStatus(Account.State.CONNECTING);
final boolean useTor = mXmppConnectionService.useTorToConnect();
final Proxy TOR_PROXY = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(InetAddress.getLocalHost(), 9050));
if (DNSHelper.isIp(account.getServer().toString())) {
socket = new Socket();
socket = useTor ? new Socket(TOR_PROXY) : new Socket();
try {
socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
} catch (IOException e) {
throw new UnknownHostException();
}
} else {
final ArrayList<Parcelable> values;
if (useTor) {
values = account.getHostnamePortBundles();
} else {
final Bundle result = DNSHelper.getSRVRecord(account.getServer(),mXmppConnectionService);
final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
values = result.getParcelableArrayList("values");
}
int i = 0;
boolean socketError = true;
while (socketError && values.size() > i) {
@ -269,11 +277,11 @@ public class XmppConnection implements Runnable {
+ ": using values from dns "
+ srvRecordServer + ":" + srvRecordPort);
}
socket = new Socket();
socket = useTor ? new Socket(TOR_PROXY) : new Socket();
socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
socketError = false;
} catch (final Throwable e) {
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage());
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")");
i++;
}
}

View file

@ -7,7 +7,9 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
@ -59,7 +61,9 @@ public class JingleSocks5Transport extends JingleTransport {
@Override
public void run() {
try {
socket = new Socket();
final boolean useTor = connection.getConnectionManager().getXmppConnectionService().useTorToConnect();
final Proxy TOR_PROXY = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(InetAddress.getLocalHost(), 9050));
socket = useTor ? new Socket(TOR_PROXY) : new Socket();
SocketAddress address = new InetSocketAddress(candidate.getHost(),candidate.getPort());
socket.connect(address,Config.SOCKET_TIMEOUT * 1000);
inputStream = socket.getInputStream();

View file

@ -78,6 +78,58 @@
android:textColorHint="@color/black54"
android:textSize="?attr/TextSizeBody" />
<LinearLayout
android:id="@+id/name_port"
android:layout_marginTop="8dp"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:weightSum="1">
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.8">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/account_settings_hostname"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"
android:id="@+id/textView"/>
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textColor="@color/black87"
android:textColorHint="@color/black54"
android:textSize="?attr/TextSizeBody"
android:id="@+id/hostname"
android:inputType="textNoSuggestions"
android:hint="@string/hostname_or_onion"/>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.2"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/account_settings_port"
android:textColor="@color/black87"
android:textSize="?attr/TextSizeBody"/>
<EditText
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="number"
android:maxLength="5"
android:textColor="@color/black87"
android:textColorHint="@color/black54"
android:textSize="?attr/TextSizeBody"
android:id="@+id/port"/>
</LinearLayout>
</LinearLayout>
<CheckBox
android:id="@+id/account_register_new"
android:layout_width="wrap_content"

View file

@ -31,4 +31,9 @@
<item android:id="@+id/action_clear_devices"
android:title="@string/clear_other_devices"
android:showAsAction="never"/>
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:showAsAction="never"
android:title="@string/action_settings"/>
</menu>

View file

@ -540,4 +540,12 @@
<string name="error_fetching_omemo_key">Error fetching OMEMO key!</string>
<string name="verified_omemo_key_with_certificate">Verified OMEMO key with certificate!</string>
<string name="device_does_not_support_certificates">Your device does not support the selection of client certificates!</string>
<string name="pref_connection_options">Connection options</string>
<string name="pref_use_tor">Connect via Tor</string>
<string name="pref_use_tor_summary">Tunnel all connections through the TOR network. Requires Orbot</string>
<string name="account_settings_hostname">Hostname</string>
<string name="account_settings_port">Port</string>
<string name="hostname_or_onion">Server- or .onion-Address</string>
<string name="not_a_valid_port">This is not a valid port number</string>
<string name="not_valid_hostname">This is not a valid hostname</string>
</resources>

View file

@ -147,6 +147,13 @@
android:summary="@string/pref_remove_trusted_certificates_summary"
android:title="@string/pref_remove_trusted_certificates_title"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_connection_options">
<CheckBoxPreference
android:defaultValue="false"
android:key="use_tor"
android:title="@string/pref_use_tor"
android:summary="@string/pref_use_tor_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_input_options">
<CheckBoxPreference
android:defaultValue="false"