From f73adfc8f5c8b82bc7bf9b0d98b2e61f4097b2fe Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 20 Feb 2013 18:10:33 -0800 Subject: [PATCH] Change MMS network request pattern. Currently we're flipping the radio in "MMS" mode, and connecting through any proxies specified in the APN. This always work, or at least doesn't seem to work on Sprint, since the configured mms proxy rejects proxy requests. Instead we try the following in this order: 1) Connect over normal data connection directly to MMSC. 2) Connect over MMS radio connection to MMSC. 3) Connect over MMS radio connection with any configured proxy to MMSC. Hopefully this doesn't fuck up shit on other unknown networks. --- res/values/strings.xml | 5 + .../securesms/mms/MmsCommunication.java | 36 ++- .../securesms/mms/MmsDownloadHelper.java | 32 ++- .../securesms/mms/MmsSendHelper.java | 15 +- .../securesms/service/MmsDownloader.java | 164 +++++++----- .../securesms/service/MmsSender.java | 237 +++++++++++------- 6 files changed, 315 insertions(+), 174 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index 48a2d8eadb..eacd477392 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -103,6 +103,11 @@ No scanned key found! + + + No connectivity available for MMS download, try again later... + Error storing MMS! + Error connecting to MMS provider... Passphrases Don\'t Match! diff --git a/src/org/thoughtcrime/securesms/mms/MmsCommunication.java b/src/org/thoughtcrime/securesms/mms/MmsCommunication.java index da150c6352..4174ff34e0 100644 --- a/src/org/thoughtcrime/securesms/mms/MmsCommunication.java +++ b/src/org/thoughtcrime/securesms/mms/MmsCommunication.java @@ -63,7 +63,8 @@ public class MmsCommunication { throw new ApnUnavailableException("No locally configured parameters available"); } - protected static MmsConnectionParameters getMmsConnectionParameters(Context context, String apn) + protected static MmsConnectionParameters getMmsConnectionParameters(Context context, String apn, + boolean proxyIfPossible) throws ApnUnavailableException { Cursor cursor = null; @@ -76,8 +77,13 @@ public class MmsCommunication { do { String mmsc = cursor.getString(cursor.getColumnIndexOrThrow("mmsc")); - String proxy = cursor.getString(cursor.getColumnIndexOrThrow("mmsproxy")); - String port = cursor.getString(cursor.getColumnIndexOrThrow("mmsport")); + String proxy = null; + String port = null; + + if (proxyIfPossible) { + proxy = cursor.getString(cursor.getColumnIndexOrThrow("mmsproxy")); + port = cursor.getString(cursor.getColumnIndexOrThrow("mmsport")); + } if (mmsc != null && !mmsc.equals("")) return new MmsConnectionParameters(mmsc, proxy, port); @@ -97,15 +103,29 @@ public class MmsCommunication { } } - protected static void checkRouteToHost(Context context, MmsConnectionParameters parameters, String url) throws IOException { + protected static void checkRouteToHost(Context context, MmsConnectionParameters parameters, + String url, boolean usingMmsRadio) + throws IOException + { if (parameters == null || !parameters.hasProxy()) - checkRouteToHost(context, Uri.parse(url).getHost()); + checkRouteToHost(context, Uri.parse(url).getHost(), usingMmsRadio); else - checkRouteToHost(context, parameters.getProxy()); + checkRouteToHost(context, parameters.getProxy(), usingMmsRadio); } - private static void checkRouteToHost(Context context, String host) throws IOException { - InetAddress inetAddress = InetAddress.getByName(host); + private static void checkRouteToHost(Context context, String host, boolean usingMmsRadio) + throws IOException + { + InetAddress inetAddress = InetAddress.getByName(host); + + if (!usingMmsRadio) { + if (inetAddress.isSiteLocalAddress()) { + throw new IOException("RFC1918 address in non-MMS radio situation!"); + } + + return; + } + byte[] ipAddressBytes = inetAddress.getAddress(); int ipAddress = Conversions.byteArrayToIntLittleEndian(ipAddressBytes, 0); ConnectivityManager manager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); diff --git a/src/org/thoughtcrime/securesms/mms/MmsDownloadHelper.java b/src/org/thoughtcrime/securesms/mms/MmsDownloadHelper.java index 0e380a911b..c32227cbb5 100644 --- a/src/org/thoughtcrime/securesms/mms/MmsDownloadHelper.java +++ b/src/org/thoughtcrime/securesms/mms/MmsDownloadHelper.java @@ -26,6 +26,9 @@ import org.apache.http.StatusLine; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; +import ws.com.google.android.mms.pdu.PduParser; +import ws.com.google.android.mms.pdu.RetrieveConf; + import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -46,15 +49,6 @@ public class MmsDownloadHelper extends MmsCommunication { request.setParams(client.getParams()); request.addHeader("Accept", "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"); -// java.util.logging.Logger.getLogger("org.apache.http.wire").setLevel(java.util.logging.Level.FINEST); -// java.util.logging.Logger.getLogger("org.apache.http.headers").setLevel(java.util.logging.Level.FINEST); -// -// System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.SimpleLog"); -// System.setProperty("org.apache.commons.logging.simplelog.showdatetime", "true"); -// System.setProperty("org.apache.commons.logging.simplelog.log.httpclient.wire", "debug"); -// System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http", "debug"); -// System.setProperty("org.apache.commons.logging.simplelog.log.org.apache.http.headers", "debug"); - HttpResponse response = client.execute(target, request); StatusLine status = response.getStatusLine(); @@ -71,17 +65,29 @@ public class MmsDownloadHelper extends MmsCommunication { } } - public static byte[] retrieveMms(Context context, String url, String apn) throws IOException { + public static RetrieveConf retrieveMms(Context context, String url, String apn, + boolean usingMmsRadio, boolean proxyIfPossible) + throws IOException + { MmsConnectionParameters connectionParameters; try { - connectionParameters = getMmsConnectionParameters(context, apn); + connectionParameters = getMmsConnectionParameters(context, apn, proxyIfPossible); } catch (ApnUnavailableException aue) { Log.w("MmsDownloadHelper", aue); connectionParameters = new MmsConnectionParameters(null, null, null); } - checkRouteToHost(context, connectionParameters, url); - return makeRequest(context, connectionParameters, url); + checkRouteToHost(context, connectionParameters, url, usingMmsRadio); + + byte[] pdu = makeRequest(context, connectionParameters, url); + + RetrieveConf retrieved = (RetrieveConf)new PduParser(pdu).parse(); + + if (retrieved == null) { + throw new IOException("Bad retrieved PDU"); + } + + return retrieved; } } diff --git a/src/org/thoughtcrime/securesms/mms/MmsSendHelper.java b/src/org/thoughtcrime/securesms/mms/MmsSendHelper.java index 8197629365..225b61f671 100644 --- a/src/org/thoughtcrime/securesms/mms/MmsSendHelper.java +++ b/src/org/thoughtcrime/securesms/mms/MmsSendHelper.java @@ -27,6 +27,9 @@ import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ByteArrayEntity; +import ws.com.google.android.mms.pdu.PduParser; +import ws.com.google.android.mms.pdu.SendConf; + import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; @@ -66,12 +69,16 @@ public class MmsSendHelper extends MmsCommunication { } } - public static byte[] sendMms(Context context, byte[] mms, String apn) throws IOException { + public static SendConf sendMms(Context context, byte[] mms, String apn, + boolean usingMmsRadio, boolean useProxyIfAvailable) + throws IOException + { Log.w("MmsSender", "Sending MMS of length: " + mms.length); try { - MmsConnectionParameters parameters = getMmsConnectionParameters(context, apn); - checkRouteToHost(context, parameters, parameters.getMmsc()); - return makePost(context, parameters, mms); + MmsConnectionParameters parameters = getMmsConnectionParameters(context, apn, useProxyIfAvailable); + checkRouteToHost(context, parameters, parameters.getMmsc(), usingMmsRadio); + byte[] response = makePost(context, parameters, mms); + return (SendConf) new PduParser(response).parse(); } catch (ApnUnavailableException aue) { Log.w("MmsSender", aue); throw new IOException("Failed to get MMSC information..."); diff --git a/src/org/thoughtcrime/securesms/service/MmsDownloader.java b/src/org/thoughtcrime/securesms/service/MmsDownloader.java index ef9981ed78..668831c420 100644 --- a/src/org/thoughtcrime/securesms/service/MmsDownloader.java +++ b/src/org/thoughtcrime/securesms/service/MmsDownloader.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.Intent; import android.util.Log; +import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.crypto.DecryptingQueue; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -28,7 +29,6 @@ import org.thoughtcrime.securesms.mms.MmsDownloadHelper; import org.thoughtcrime.securesms.protocol.WirePrefix; import ws.com.google.android.mms.MmsException; -import ws.com.google.android.mms.pdu.PduParser; import ws.com.google.android.mms.pdu.RetrieveConf; import java.io.IOException; @@ -44,73 +44,105 @@ public class MmsDownloader extends MmscProcessor { this.toastHandler = toastHandler; } - private void handleDownloadMms(DownloadItem item) { - if (!isConnectivityPossible()) { - DatabaseFactory.getMmsDatabase(context).markDownloadState(item.getMessageId(), MmsDatabase.Types.DOWNLOAD_NO_CONNECTIVITY); - toastHandler.makeToast("No connectivity available for MMS download, try again later..."); - Log.w("MmsDownloadService", "Unable to download MMS, please try again later."); - } else { - DatabaseFactory.getMmsDatabase(context).markDownloadState(item.getMessageId(), MmsDatabase.Types.DOWNLOAD_CONNECTING); - pendingMessages.add(item); - issueConnectivityRequest(); + public void process(MasterSecret masterSecret, Intent intent) { + if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_ACTION)) { + DownloadItem item = new DownloadItem(masterSecret, false, false, + intent.getLongExtra("message_id", -1), + intent.getLongExtra("thread_id", -1), + intent.getStringExtra("content_location"), + intent.getByteArrayExtra("transaction_id")); + + handleDownloadMmsAction(item); + } else if (intent.getAction().equals(SendReceiveService.DOWNLOAD_MMS_CONNECTIVITY_ACTION)) { + handleConnectivityChange(); } } - private void handleDownloadMmsContinued(DownloadItem item) { - Log.w("MmsDownloadService", "Handling MMS download continuation..."); + private void handleDownloadMmsAction(DownloadItem item) { + if (!isConnectivityPossible()) { + Log.w("MmsDownloader", "No MMS connectivity available!"); + DatabaseFactory.getMmsDatabase(context).markDownloadState(item.getMessageId(), MmsDatabase.Types.DOWNLOAD_NO_CONNECTIVITY); + toastHandler.makeToast(context.getString(R.string.MmsDownloader_no_connectivity_available_for_mms_download_try_again_later)); + return; + } + + DatabaseFactory.getMmsDatabase(context).markDownloadState(item.getMessageId(), MmsDatabase.Types.DOWNLOAD_CONNECTING); + + if (item.useMmsRadioMode()) downloadMmsWithRadioChange(item); + else downloadMms(item); + } + + private void downloadMmsWithRadioChange(DownloadItem item) { + Log.w("MmsDownloader", "Handling MMS download with radio change..."); + pendingMessages.add(item); + issueConnectivityRequest(); + } + + private void downloadMms(DownloadItem item) { + Log.w("MmsDownloadService", "Handling actual MMS download..."); MmsDatabase mmsDatabase; - if (item.getMasterSecret() == null) + if (item.getMasterSecret() == null) { mmsDatabase = DatabaseFactory.getMmsDatabase(context); - else + } else { mmsDatabase = DatabaseFactory.getEncryptingMmsDatabase(context, item.getMasterSecret()); - + } try { - byte[] pdu = MmsDownloadHelper.retrieveMms(context, item.getContentLocation(), - getApnInformation()); - RetrieveConf retrieved = (RetrieveConf)new PduParser(pdu).parse(); - - if (retrieved == null) - throw new IOException("Bad retrieved PDU"); + RetrieveConf retrieved = MmsDownloadHelper.retrieveMms(context, item.getContentLocation(), + getApnInformation(), + item.useMmsRadioMode(), + item.proxyRequestIfPossible()); for (int i=0;i pendingMessages = new LinkedList(); - private final Handler toastHandler; + private final LinkedList pendingMessages = new LinkedList(); + private final ToastHandler toastHandler; - public MmsSender(Context context, Handler toastHandler) { + public MmsSender(Context context, ToastHandler toastHandler) { super(context); this.toastHandler = toastHandler; } @@ -66,49 +67,137 @@ public class MmsSender extends MmscProcessor { MmsDatabase database = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret); try { - SendReq[] sendRequests; + List sendRequests = getOutgoingMessages(masterSecret, messageId); - if (messageId == -1) { - sendRequests = database.getOutgoingMessages(); - } else { - sendRequests = new SendReq[1]; - sendRequests[0] = database.getSendRequest(messageId); + for (SendReq sendRequest : sendRequests) { + handleSendMmsAction(new SendItem(masterSecret, sendRequest, messageId != -1, false, false)); } - if (sendRequests != null && sendRequests.length > 0) - handleSendMms(sendRequests, messageId != -1); - } catch (MmsException me) { Log.w("MmsSender", me); if (messageId != -1) database.markAsSentFailed(messageId); } } else if (intent.getAction().equals(SendReceiveService.SEND_MMS_CONNECTIVITY_ACTION)) { - handleConnectivityChange(masterSecret); + handleConnectivityChange(); } } - protected void handleConnectivityChange(MasterSecret masterSecret) { - if (!isConnected()) - return; - - if (!pendingMessages.isEmpty()) handleSendMmsContinued(masterSecret, pendingMessages.remove()); - else finishConnectivity(); - } - - private void handleSendMms(SendReq[] sendRequests, boolean targeted) { + private void handleSendMmsAction(SendItem item) { if (!isConnectivityPossible()) { - if (targeted) { + if (item.targeted) { toastHandler .obtainMessage(0, context.getString(R.string.MmsSender_currently_unable_to_send_your_mms_message)) .sendToTarget(); } -// for (int i=0;i getOutgoingMessages(MasterSecret masterSecret, long messageId) + throws MmsException + { + MmsDatabase database = DatabaseFactory.getEncryptingMmsDatabase(context, masterSecret); + List sendRequests; + + if (messageId == -1) { + sendRequests = Arrays.asList(database.getOutgoingMessages()); + } else { + sendRequests = new ArrayList(1); + sendRequests.add(database.getSendRequest(messageId)); + } + + return sendRequests; + } + + protected void handleConnectivityChange() { + if (!isConnected()) { + if (!isConnectivityPossible() && !pendingMessages.isEmpty()) { + DatabaseFactory.getMmsDatabase(context).markAsSentFailed(pendingMessages.remove().request.getDatabaseMessageId()); + toastHandler.makeToast(context.getString(R.string.MmsSender_currently_unable_to_send_your_mms_message)); + Log.w("MmsSender", "Unable to send MMS."); + finishConnectivity(); + } + + return; + } + + for (SendItem item : pendingMessages) { + sendMmsMessage(item); + } + + pendingMessages.clear(); + finishConnectivity(); } private boolean isInconsistentResponse(SendReq send, SendConf response) { @@ -145,69 +234,15 @@ public class MmsSender extends MmscProcessor { return pdu; } - private void sendMms(MmsDatabase db, SendReq pdu, String number, long messageId, boolean secure) { - try { - if (number != null && number.trim().length() != 0) - pdu.setFrom(new EncodedStringValue(number)); - - byte[] response = MmsSendHelper.sendMms(context, new PduComposer(context, pdu).make(), getApnInformation()); - SendConf conf = (SendConf) new PduParser(response).parse(); - - for (int i=0;i