diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml index 2759c3c97..d6c88a688 100644 --- a/res/values-ca/strings.xml +++ b/res/values-ca/strings.xml @@ -389,10 +389,6 @@ Si us plau, envia\'ns els errors, contribueix a millorar-lo a Tipus de seguretat Tipus d\'autenticació Cap - SSL/TLS (si és disponible) - SSL/TLS (sempre) - STARTTLS (si és disponible) - STARTTLS (sempre) Quan esborro missatges No els esborris del servidor diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml index c28891705..7e83382d1 100644 --- a/res/values-cs/strings.xml +++ b/res/values-cs/strings.xml @@ -393,10 +393,6 @@ Posílejte prosím chybová hlášení, přispívejte novými funkcemi a ptejte Typ zabezpečení Typ ověření Žádné - SSL/TLS (je-li dostupné) - SSL/TLS (vždy) - STARTTLS (je-li dostupné) - STARTTLS (vždy) Když smažu zprávu Nemazat ji na serveru diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml index c39b56048..bece6f525 100644 --- a/res/values-da/strings.xml +++ b/res/values-da/strings.xml @@ -389,10 +389,6 @@ Vær venlig at sende fejlrapporter, anmodning om nye funktioner, og spørgsmål Sikkerhed Autentifikations type Ingen - SSL/TLS (hvis tilgængelig) - SSL/TLS (altid) - STARTTLS (hvis tilgængelig) - STARTTLS (altid) Når jeg sletter en mail Undlad at slette på server diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 7df1011f0..3e685f573 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -387,10 +387,6 @@ Um Fehler zu melden, neue Funktionen vorzuschlagen oder Fragen zu stellen, besuc Sicherheitstyp Authentifizierungstyp Keine Verschlüsselung - SSL/TLS (falls verfügbar) - SSL/TLS (immer) - STARTTLS (falls verfügbar) - STARTTLS (immer) Beim Löschen von Nachrichten Nie von Server löschen diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml index 9852d9964..7dde8f171 100644 --- a/res/values-el/strings.xml +++ b/res/values-el/strings.xml @@ -388,10 +388,6 @@ Ασφάλεια Πιστοποίηση Καμιά - SSL/TLS (αν υπάρχει) - SSL/TLS (πάντοτε) - STARTTLS (αν υπάρχει) - STARTTLS (πάντοτε) Κατά τη διαγραφή μηνύματος Να μη διαγράφεται στο server diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml index b20ddf6e8..abb27998d 100644 --- a/res/values-es/strings.xml +++ b/res/values-es/strings.xml @@ -388,10 +388,6 @@ Por favor, envía los errores detectados, contribuye con nuevas funcionalidades Tipo de Seguridad Tipo de autentificación Ninguna - SSL/TLS (si disponible) - SSL/TLS (siempre) - STARTTLS (si disponible) - STARTTLS (siempre) Borrado de mensajes No borrar del servidor diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml index 7cdc8bdf4..59e6817e8 100755 --- a/res/values-fi/strings.xml +++ b/res/values-fi/strings.xml @@ -389,10 +389,6 @@ Virheraportit, osallistuminen projektiin ja kysymykset: Mene osoitteeseen Suojauksen tyyppi Todennuksen tyyppi Ei mitään - SSL/TLS (jos käytettävissä) - SSL/TLS (aina) - STARTTLS (jos käytettävissä) - STARTTLS (aina) Kun viesti poistetaan Älä poista palvelimelta diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index 01a545959..e925198f4 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -414,10 +414,6 @@ de plus Type de sécurité Type d\'authentification Aucun - SSL/TLS (si disponible) - SSL/TLS (toujours) - STARTTLS (si disponible) - STARTTLS (toujours) Supprimer les messages du serveur\u00A0: Jamais diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml index a891ec599..81ea6942a 100644 --- a/res/values-gl/strings.xml +++ b/res/values-gl/strings.xml @@ -388,10 +388,6 @@ Por favor, envía os erros detectados, contribúe con novas funcionalidas e preg Tipo de Seguridade Tipo de autentificación Ningunha - SSL/TLS (se dispoñible) - SSL/TLS (sempre) - STARTTLS (se dispoñible) - STARTTLS (sempre) Borrado de mesaxes Non borrar do servidor diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml index 2c915e277..1d9f8b661 100644 --- a/res/values-hu/strings.xml +++ b/res/values-hu/strings.xml @@ -389,10 +389,6 @@ Hibajelentéseivel hozzájárul az újabb verziók tökéletesítéséhez, kérd Kapcsolat biztonsága Hitelesítés típus Nincs - SSL/TLS (ha elérhető) - SSL/TLS (mindig) - STARTTLS (ha elérhető) - STARTTLS (mindig) Üzenet törlésekor Ne törlődjön a szerverről diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml index 4718e0cf0..790138f30 100644 --- a/res/values-it/strings.xml +++ b/res/values-it/strings.xml @@ -388,10 +388,6 @@ Invia le tue segnalazioni, suggerisci nuove funzionalità e chiedi informazioni Tipo di protezione Tipo di autenticazione Nessuna - SSL/TLS (se disponibile) - SSL/TLS (sempre) - STARTTLS (se disponibile) - STARTTLS (sempre) Quando si elimina un messaggio Non eliminare dal server diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml index c203bd31d..1d81f1e01 100644 --- a/res/values-iw/strings.xml +++ b/res/values-iw/strings.xml @@ -390,10 +390,6 @@ סוג אבטחה סוג אימות כלום - SSL/TLS (אם זמין) - SSL/TLS (תמיד) - STARTTLS (אם זמין) - STARTTLS (תמיד) כאשר אני מוחק הודעה אל תמחוק בשרת diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml index 26001a089..4c0d10ff9 100644 --- a/res/values-ja/strings.xml +++ b/res/values-ja/strings.xml @@ -389,10 +389,6 @@ K-9 は大多数のメールクライアントと同様に、ほとんどのフ 保護された接続 認証タイプ 使用しない - 可能なら SSL/TLS を使用する - SSL/TLS を使用する - 可能なら STARTTLS を使用する - STARTTLS を使用する メール削除時の動作 サーバでは削除しない diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index be8001f93..8ce2a8600 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -387,10 +387,6 @@ K-9 메일은 대부분의 무료 hotmail 계정을 지원하지 않으며, 다 보안 연결 인증 방식 없음 - SSL/TLS (유효할 경우) - SSL/TLS (모든 인증서 허용) - STARTTLS (유효할 경우) - STARTTLS (모든 인증서 허용) 메시지 삭제 시 서버에는 메일을 삭제하지 않음 diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml index f6650d610..39a5e9281 100644 --- a/res/values-lt/strings.xml +++ b/res/values-lt/strings.xml @@ -388,10 +388,6 @@ Praneškite apie klaidas, pridėkite naujų galimybių ir užduokite klausimus m Saugumas Tapatumo nustatymas Joks - SSL/TLS (jei įmanoma) - SSL/TLS (visada) - STARTTLS (jei įmanoma) - STARTTLS (visada) Kai pašalinu laišką Nešalinti serveryje diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml index e20001522..3e2e94c3a 100644 --- a/res/values-nl/strings.xml +++ b/res/values-nl/strings.xml @@ -388,10 +388,6 @@ Graag foutrapporten, bijdrage nieuwe functies en vragen stellen op Beveiligings type Authenticatie type Geen - SSL/TLS (indien beschikbaar) - SSL/TLS (altijd) - STARTTLS (indien beschikbaar) - STARTTLS (altijd) Wanneer ik een bericht verwijder Verwijder niet van server diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml index 196d73e94..882d2f5a5 100644 --- a/res/values-pl/strings.xml +++ b/res/values-pl/strings.xml @@ -399,10 +399,6 @@ Wszelkie zgłoszenia usterek, zapytania oraz nowe pomysły prosimy przesyłać z Zabezpieczenia Rodzaj uwierzytelnienia Brak - SSL/TLS (jeśli dostępne) - SSL/TLS (zawsze) - STARTTLS (jeśli dostępne) - STARTTLS (zawsze) Gdy skasuję wiadomość Nie usuwaj z serwera diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml index 0f268d1c1..cddd5feb4 100644 --- a/res/values-pt-rBR/strings.xml +++ b/res/values-pt-rBR/strings.xml @@ -388,10 +388,6 @@ Por favor, nos envie relatórios de bugs, contribua para novas melhorias e faça Tipo de segurança Tipo de autenticação Nenhum - SSL/TLS (se disponível) - SSL/TLS (sempre) - STARTTLS (se disponível) - STARTTLS (sempre) Quando eu excluir uma mensagem Não excluí-la do servidor diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 371472f61..714ba95b4 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -389,10 +389,6 @@ K-9 Mail — почтовый клиент для Android. Безопасность Аутентификация Нет - SSL/TLS (если доступно) - SSL/TLS (всегда) - STARTTLS (если доступно) - STARTTLS (всегда) Удалённое, на сервере Оставить diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml index 6641d617f..c57951a7b 100644 --- a/res/values-sk/strings.xml +++ b/res/values-sk/strings.xml @@ -388,10 +388,6 @@ Prosím, nahlasujte prípadné chyby, prispievajte novými funkciami a pýtajte Zabezpečenie Overenie Žiadne - SSL/TLS (ak je k dispozícii) - SSL/TLS (vždy) - STARTTLS (ak je k dispozícii) - STARTTLS (vždy) Akcia po vymazaní správy Ponechať na serveri diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml index 47cc1ba23..8c57b59d3 100644 --- a/res/values-sv/strings.xml +++ b/res/values-sv/strings.xml @@ -389,10 +389,6 @@ Vänligen skicka felrapporter, hjälp till med nya funktioner och ställ frågor Säkerhetstyp Autentiseringstyp Ingen - SSL/TLS (om tillgängligt) - SSL/TLS (alltid) - STARTTLS (om tillgängligt) - STARTTLS (alltid) När jag raderar ett brev Radera inte på servern diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml index 152c515af..7f889465f 100644 --- a/res/values-tr/strings.xml +++ b/res/values-tr/strings.xml @@ -388,10 +388,6 @@ Lütfen hata raporlarınızı, istediğiniz yeni özellikleri ve sorularınızı Güvenlik tipi Kimlik doğrulama tipi Hiçbiri - SSL/TLS (Varsa) - SSL/TLS (daima) - STARTTLS (Varsa) - STARTTLS (Daima) Bir mesaj sildiğim zaman Sunucudan silme diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml index 4b8886260..2e640af3d 100644 --- a/res/values-uk/strings.xml +++ b/res/values-uk/strings.xml @@ -388,10 +388,6 @@ K-9 Mail це поштовий клієнт з відкритим вихідни Тип системи захисту Метод автентифікації Немає - SSL/TLS (якщо доступно) - SSL/TLS (завжди) - STARTTLS (якщо доступно) - STARTTLS (завжди) Коли повідомлення видалено Не видаляти на сервері diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml index 0b9b941e5..602db93bc 100644 --- a/res/values-zh-rCN/strings.xml +++ b/res/values-zh-rCN/strings.xml @@ -387,10 +387,6 @@ K-9改进的功能包括: 加密方法 身份验证方法 - SSL/TLS(如果可能的话) - SSL/TLS(总是) - STARTTLS(如果可能的话) - STARTTLS(总是) 当我删除邮件时 不要从服务器上删除 diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml index 3eb6532b3..ce35a86ad 100644 --- a/res/values-zh-rTW/strings.xml +++ b/res/values-zh-rTW/strings.xml @@ -361,10 +361,6 @@ 加密類型 身份驗證類型 - SSL/TLS(如果可用) - SSL/TLS(預設) - STARTTLS(如果可用) - STARTTLS(預設) 當我刪除郵件時 不要從伺服器上刪除 diff --git a/res/values/strings.xml b/res/values/strings.xml index fcb3298ac..a4810143b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -385,6 +385,10 @@ Please submit bug reports, contribute new features and ask questions at IMAP Exchange (WebDAV) + Normal password + Password, transmitted insecurely + Encrypted password + Incoming server settings Username Password @@ -395,10 +399,8 @@ Please submit bug reports, contribute new features and ask questions at Security Authentication None - SSL/TLS (if available) - SSL/TLS (always) - STARTTLS (if available) - STARTTLS (always) + SSL/TLS + STARTTLS When I delete a message Do not delete on server diff --git a/res/xml/providers.xml b/res/xml/providers.xml index 9ecbda571..ef28ec7c4 100644 --- a/res/xml/providers.xml +++ b/res/xml/providers.xml @@ -36,16 +36,12 @@ Valid incoming uri schemes are: imap IMAP with no transport security. - imap+tls IMAP with optional TLS transport security. - If TLS is not available the connection is made as "imap" imap+tls+ IMAP with required TLS transport security. If TLS is not available the conneciton fails. imap+ssl+ IMAP with required SSL transport security. If SSL is not available the connection fails. pop3 POP3 with no transport security. - pop3+tls POP3 with optional TLS transport security. - If TLS is not available the connection is made as "pop3" pop3+tls+ POP3 with required TLS transport security. If TLS is not available the conneciton fails. pop3+ssl+ POP3 with required SSL transport security. @@ -53,8 +49,6 @@ Valid outgoing uri schemes are: smtp SMTP with no transport security. - smtp+tls SMTP with optional TLS transport security. - If TLS is not available the connection is made as "smtp" smtp+tls+ SMTP with required TLS transport security. If TLS is not available the conneciton fails. smtp+ssl+ SMTP with required SSL transport security. @@ -127,8 +121,8 @@ - - + + @@ -154,10 +148,9 @@ - - - + + @@ -296,12 +289,12 @@ - - + + - - + + @@ -323,28 +316,18 @@ - - + + - - + + - - - - - - - - - - + + @@ -538,11 +521,11 @@ - - + + - - + + diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java index 74d3fd29b..9a841a5c7 100644 --- a/src/com/fsck/k9/K9.java +++ b/src/com/fsck/k9/K9.java @@ -177,8 +177,9 @@ public class K9 extends Application { /** * Can create messages containing stack traces that can be forwarded * to the development team. + * + * Feature is enabled when DEBUG == true */ - public static boolean ENABLE_ERROR_FOLDER = true; public static String ERROR_FOLDER_NAME = "K9mail-errors"; /** diff --git a/src/com/fsck/k9/activity/setup/AccountSetupAccountType.java b/src/com/fsck/k9/activity/setup/AccountSetupAccountType.java index f4a7f34cb..3e038166f 100644 --- a/src/com/fsck/k9/activity/setup/AccountSetupAccountType.java +++ b/src/com/fsck/k9/activity/setup/AccountSetupAccountType.java @@ -55,6 +55,11 @@ public class AccountSetupAccountType extends K9Activity implements OnClickListen URI uri = new URI(mAccount.getStoreUri()); uri = new URI("pop3", uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null); mAccount.setStoreUri(uri.toString()); + + uri = new URI(mAccount.getTransportUri()); + uri = new URI("smtp", uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null); + mAccount.setTransportUri(uri.toString()); + AccountSetupIncoming.actionIncomingSettings(this, mAccount, mMakeDefault); finish(); } catch (Exception use) { @@ -68,6 +73,11 @@ public class AccountSetupAccountType extends K9Activity implements OnClickListen URI uri = new URI(mAccount.getStoreUri()); uri = new URI("imap", uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null); mAccount.setStoreUri(uri.toString()); + + uri = new URI(mAccount.getTransportUri()); + uri = new URI("smtp", uri.getUserInfo(), uri.getHost(), uri.getPort(), null, null, null); + mAccount.setTransportUri(uri.toString()); + AccountSetupIncoming.actionIncomingSettings(this, mAccount, mMakeDefault); finish(); } catch (Exception use) { diff --git a/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java b/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java index e58fc8846..b275e005d 100644 --- a/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java +++ b/src/com/fsck/k9/activity/setup/AccountSetupIncoming.java @@ -15,9 +15,11 @@ import android.widget.*; import android.widget.CompoundButton.OnCheckedChangeListener; import com.fsck.k9.*; +import com.fsck.k9.Account.FolderMode; import com.fsck.k9.activity.K9Activity; import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection; import com.fsck.k9.helper.Utility; +import com.fsck.k9.mail.AuthType; import com.fsck.k9.mail.ConnectionSecurity; import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.Store; @@ -26,6 +28,7 @@ import com.fsck.k9.mail.store.Pop3Store; import com.fsck.k9.mail.store.WebDavStore; import com.fsck.k9.mail.store.ImapStore.ImapStoreSettings; import com.fsck.k9.mail.store.WebDavStore.WebDavStoreSettings; +import com.fsck.k9.service.MailService; import java.io.UnsupportedEncodingException; import java.net.URI; @@ -38,32 +41,13 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener private static final String EXTRA_ACCOUNT = "account"; private static final String EXTRA_MAKE_DEFAULT = "makeDefault"; - private static final int[] POP3_PORTS = { - 110, 995, 995, 110, 110 - }; + private static final String POP3_PORT = "110"; + private static final String POP3_SSL_PORT = "995"; + private static final String IMAP_PORT = "143"; + private static final String IMAP_SSL_PORT = "993"; + private static final String WEBDAV_PORT = "80"; + private static final String WEBDAV_SSL_PORT = "443"; - private static final int[] IMAP_PORTS = { - 143, 993, 993, 143, 143 - }; - - private static final int[] WEBDAV_PORTS = { - 80, 443, 443, 443, 443 - }; - - private static final ConnectionSecurity[] CONNECTION_SECURITY_TYPES = { - ConnectionSecurity.NONE, - ConnectionSecurity.SSL_TLS_OPTIONAL, - ConnectionSecurity.SSL_TLS_REQUIRED, - ConnectionSecurity.STARTTLS_OPTIONAL, - ConnectionSecurity.STARTTLS_REQUIRED - }; - - private static final String[] AUTH_TYPES = { - "PLAIN", "CRAM_MD5" - }; - - - private int[] mAccountPorts; private String mStoreType; private EditText mUsernameView; private EditText mPasswordView; @@ -83,6 +67,10 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener private CheckBox mCompressionWifi; private CheckBox mCompressionOther; private CheckBox mSubscribedFoldersOnly; + private ArrayAdapter mAuthTypeAdapter; + private String mDefaultPort = ""; + private String mDefaultSslPort = ""; + private ConnectionSecurity[] mConnectionSecurityChoices = ConnectionSecurity.values(); public static void actionIncomingSettings(Activity context, Account account, boolean makeDefault) { Intent i = new Intent(context, AccountSetupIncoming.class); @@ -139,32 +127,8 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener } }); - SpinnerOption securityTypes[] = { - new SpinnerOption(0, getString(R.string.account_setup_incoming_security_none_label)), - new SpinnerOption(1, - getString(R.string.account_setup_incoming_security_ssl_optional_label)), - new SpinnerOption(2, getString(R.string.account_setup_incoming_security_ssl_label)), - new SpinnerOption(3, - getString(R.string.account_setup_incoming_security_tls_optional_label)), - new SpinnerOption(4, getString(R.string.account_setup_incoming_security_tls_label)), - }; - - // This needs to be kept in sync with the list at the top of the file. - // that makes me somewhat unhappy - SpinnerOption authTypeSpinnerOptions[] = { - new SpinnerOption(0, AUTH_TYPES[0]), - new SpinnerOption(1, AUTH_TYPES[1]) - }; - - ArrayAdapter securityTypesAdapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, securityTypes); - securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mSecurityTypeView.setAdapter(securityTypesAdapter); - - ArrayAdapter authTypesAdapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, authTypeSpinnerOptions); - authTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mAuthTypeView.setAdapter(authTypesAdapter); + mAuthTypeAdapter = AuthType.getArrayAdapter(this); + mAuthTypeView.setAdapter(mAuthTypeAdapter); /* * Calls validateFields() which enables or disables the Next button @@ -217,18 +181,17 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener mPasswordView.setText(settings.password); } - if (settings.authenticationType != null) { - for (int i = 0; i < AUTH_TYPES.length; i++) { - if (AUTH_TYPES[i].equals(settings.authenticationType)) { - SpinnerOption.setSpinnerOptionValue(mAuthTypeView, i); - } - } - } + updateAuthPlainTextFromSecurityType(settings.connectionSecurity); + + // The first item is selected if settings.authenticationType is null or is not in mAuthTypeAdapter + int position = mAuthTypeAdapter.getPosition(settings.authenticationType); + mAuthTypeView.setSelection(position, false); mStoreType = settings.type; if (Pop3Store.STORE_TYPE.equals(settings.type)) { serverLabelView.setText(R.string.account_setup_incoming_pop_server_label); - mAccountPorts = POP3_PORTS; + mDefaultPort = POP3_PORT; + mDefaultSslPort = POP3_SSL_PORT; findViewById(R.id.imap_path_prefix_section).setVisibility(View.GONE); findViewById(R.id.webdav_advanced_header).setVisibility(View.GONE); findViewById(R.id.webdav_mailbox_alias_section).setVisibility(View.GONE); @@ -240,7 +203,8 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener mAccount.setDeletePolicy(Account.DELETE_POLICY_NEVER); } else if (ImapStore.STORE_TYPE.equals(settings.type)) { serverLabelView.setText(R.string.account_setup_incoming_imap_server_label); - mAccountPorts = IMAP_PORTS; + mDefaultPort = IMAP_PORT; + mDefaultSslPort = IMAP_SSL_PORT; ImapStoreSettings imapSettings = (ImapStoreSettings) settings; @@ -260,7 +224,11 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener } } else if (WebDavStore.STORE_TYPE.equals(settings.type)) { serverLabelView.setText(R.string.account_setup_incoming_webdav_server_label); - mAccountPorts = WEBDAV_PORTS; + mDefaultPort = WEBDAV_PORT; + mDefaultSslPort = WEBDAV_SSL_PORT; + mConnectionSecurityChoices = new ConnectionSecurity[] { + ConnectionSecurity.NONE, + ConnectionSecurity.SSL_TLS_REQUIRED }; // Hide the unnecessary fields findViewById(R.id.imap_path_prefix_section).setVisibility(View.GONE); @@ -288,12 +256,13 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener throw new Exception("Unknown account type: " + mAccount.getStoreUri()); } + ArrayAdapter securityTypesAdapter = new ArrayAdapter(this, + android.R.layout.simple_spinner_item, mConnectionSecurityChoices); + securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mSecurityTypeView.setAdapter(securityTypesAdapter); + // Select currently configured security type - for (int i = 0; i < CONNECTION_SECURITY_TYPES.length; i++) { - if (CONNECTION_SECURITY_TYPES[i] == settings.connectionSecurity) { - SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i); - } - } + mSecurityTypeView.setSelection(settings.connectionSecurity.ordinal(), false); /* * Updates the port when the user changes the security type. This allows @@ -353,9 +322,35 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener } private void updatePortFromSecurityType() { - if (mAccountPorts != null) { - int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; - mPortView.setText(Integer.toString(mAccountPorts[securityType])); + ConnectionSecurity securityType = (ConnectionSecurity) mSecurityTypeView.getSelectedItem(); + mPortView.setText(getDefaultPort(securityType)); + updateAuthPlainTextFromSecurityType(securityType); + } + + private String getDefaultPort(ConnectionSecurity securityType) { + String port; + switch (securityType) { + case NONE: + case STARTTLS_REQUIRED: + port = mDefaultPort; + break; + case SSL_TLS_REQUIRED: + port = mDefaultSslPort; + break; + default: + Log.e(K9.LOG_TAG, "Unhandled ConnectionSecurity type encountered"); + port = ""; + } + return port; + } + + private void updateAuthPlainTextFromSecurityType(ConnectionSecurity securityType) { + switch (securityType) { + case NONE: + AuthType.PLAIN.useInsecureText(true, mAuthTypeAdapter); + break; + default: + AuthType.PLAIN.useInsecureText(false, mAuthTypeAdapter); } } @@ -363,6 +358,16 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { if (Intent.ACTION_EDIT.equals(getIntent().getAction())) { + boolean isPushCapable = false; + try { + Store store = mAccount.getRemoteStore(); + isPushCapable = store.isPushCapable(); + } catch (Exception e) { + Log.e(K9.LOG_TAG, "Could not get remote store", e); + } + if (isPushCapable && mAccount.getFolderPushMode() != FolderMode.NONE) { + MailService.actionRestartPushers(this, null); + } mAccount.save(Preferences.getPreferences(this)); finish(); } else { @@ -402,12 +407,11 @@ public class AccountSetupIncoming extends K9Activity implements OnClickListener protected void onNext() { try { - ConnectionSecurity connectionSecurity = CONNECTION_SECURITY_TYPES[ - (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value]; + ConnectionSecurity connectionSecurity = (ConnectionSecurity) mSecurityTypeView.getSelectedItem(); String username = mUsernameView.getText().toString(); String password = mPasswordView.getText().toString(); - String authType = ((SpinnerOption)mAuthTypeView.getSelectedItem()).label; + AuthType authType = (AuthType) mAuthTypeView.getSelectedItem(); String host = mServerView.getText().toString(); int port = Integer.parseInt(mPortView.getText().toString()); diff --git a/src/com/fsck/k9/activity/setup/AccountSetupOutgoing.java b/src/com/fsck/k9/activity/setup/AccountSetupOutgoing.java index 6d1af8bc0..7aadd43fd 100644 --- a/src/com/fsck/k9/activity/setup/AccountSetupOutgoing.java +++ b/src/com/fsck/k9/activity/setup/AccountSetupOutgoing.java @@ -17,13 +17,14 @@ import com.fsck.k9.*; import com.fsck.k9.activity.K9Activity; import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection; import com.fsck.k9.helper.Utility; +import com.fsck.k9.mail.AuthType; +import com.fsck.k9.mail.ConnectionSecurity; +import com.fsck.k9.mail.ServerSettings; +import com.fsck.k9.mail.Transport; import com.fsck.k9.mail.transport.SmtpTransport; -import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; -import java.net.URLDecoder; -import java.net.URLEncoder; public class AccountSetupOutgoing extends K9Activity implements OnClickListener, OnCheckedChangeListener { @@ -31,29 +32,8 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, private static final String EXTRA_MAKE_DEFAULT = "makeDefault"; - private static final int smtpPorts[] = { - 587, 465, 465, 587, 587 - }; - - private static final String smtpSchemes[] = { - "smtp", "smtp+ssl", "smtp+ssl+", "smtp+tls", "smtp+tls+" - }; - /* - private static final int webdavPorts[] = - { - 80, 443, 443, 443, 443 - }; - private static final String webdavSchemes[] = - { - "webdav", "webdav+ssl", "webdav+ssl+", "webdav+tls", "webdav+tls+" - }; - */ - private static final String authTypes[] = { - SmtpTransport.AUTH_AUTOMATIC, - SmtpTransport.AUTH_LOGIN, - SmtpTransport.AUTH_PLAIN, - SmtpTransport.AUTH_CRAM_MD5, - }; + private static final String SMTP_PORT = "587"; + private static final String SMTP_SSL_PORT = "465"; private EditText mUsernameView; private EditText mPasswordView; @@ -63,6 +43,7 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, private ViewGroup mRequireLoginSettingsView; private Spinner mSecurityTypeView; private Spinner mAuthTypeView; + private ArrayAdapter mAuthTypeAdapter; private Button mNextButton; private Account mAccount; private boolean mMakeDefault; @@ -117,30 +98,13 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, mNextButton.setOnClickListener(this); mRequireLoginView.setOnCheckedChangeListener(this); - SpinnerOption securityTypes[] = { - new SpinnerOption(0, getString(R.string.account_setup_incoming_security_none_label)), - new SpinnerOption(1, - getString(R.string.account_setup_incoming_security_ssl_optional_label)), - new SpinnerOption(2, getString(R.string.account_setup_incoming_security_ssl_label)), - new SpinnerOption(3, - getString(R.string.account_setup_incoming_security_tls_optional_label)), - new SpinnerOption(4, getString(R.string.account_setup_incoming_security_tls_label)), - }; - - SpinnerOption authTypeSpinnerOptions[] = new SpinnerOption[authTypes.length]; - for (int i = 0; i < authTypes.length; i++) { - authTypeSpinnerOptions[i] = new SpinnerOption(i, authTypes[i]); - } - - ArrayAdapter securityTypesAdapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, securityTypes); + ArrayAdapter securityTypesAdapter = new ArrayAdapter(this, + android.R.layout.simple_spinner_item, ConnectionSecurity.values()); securityTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mSecurityTypeView.setAdapter(securityTypesAdapter); - ArrayAdapter authTypesAdapter = new ArrayAdapter(this, - android.R.layout.simple_spinner_item, authTypeSpinnerOptions); - authTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - mAuthTypeView.setAdapter(authTypesAdapter); + mAuthTypeAdapter = AuthType.getArrayAdapter(this); + mAuthTypeView.setAdapter(mAuthTypeAdapter); /* * Calls validateFields() which enables or disables the Next button @@ -182,21 +146,9 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, } try { - URI uri = new URI(mAccount.getTransportUri()); - String username = null; - String password = null; - String authType = null; - if (uri.getUserInfo() != null) { - String[] userInfoParts = uri.getUserInfo().split(":"); - - username = URLDecoder.decode(userInfoParts[0], "UTF-8"); - if (userInfoParts.length > 1) { - password = URLDecoder.decode(userInfoParts[1], "UTF-8"); - } - if (userInfoParts.length > 2) { - authType = userInfoParts[2]; - } - } + ServerSettings settings = Transport.decodeTransportUri(mAccount.getTransportUri()); + String username = settings.username; + String password = settings.password; if (username != null) { mUsernameView.setText(username); @@ -207,20 +159,14 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, mPasswordView.setText(password); } - if (authType != null) { - for (int i = 0; i < authTypes.length; i++) { - if (authTypes[i].equals(authType)) { - SpinnerOption.setSpinnerOptionValue(mAuthTypeView, i); - } - } - } + updateAuthPlainTextFromSecurityType(settings.connectionSecurity); + + // The first item is selected if settings.authenticationType is null or is not in mAuthTypeAdapter + int position = mAuthTypeAdapter.getPosition(settings.authenticationType); + mAuthTypeView.setSelection(position, false); // Select currently configured security type - for (int i = 0; i < smtpSchemes.length; i++) { - if (smtpSchemes[i].equals(uri.getScheme())) { - SpinnerOption.setSpinnerOptionValue(mSecurityTypeView, i); - } - } + mSecurityTypeView.setSelection(settings.connectionSecurity.ordinal(), false); /* * Updates the port when the user changes the security type. This allows @@ -242,12 +188,12 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, public void onNothingSelected(AdapterView parent) { /* unused */ } }); - if (uri.getHost() != null) { - mServerView.setText(uri.getHost()); + if (settings.host != null) { + mServerView.setText(settings.host); } - if (uri.getPort() != -1) { - mPortView.setText(Integer.toString(uri.getPort())); + if (settings.port != -1) { + mPortView.setText(Integer.toString(settings.port)); } else { updatePortFromSecurityType(); } @@ -280,8 +226,36 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, } private void updatePortFromSecurityType() { - int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; - mPortView.setText(Integer.toString(smtpPorts[securityType])); + ConnectionSecurity securityType = (ConnectionSecurity) mSecurityTypeView.getSelectedItem(); + mPortView.setText(getDefaultSmtpPort(securityType)); + updateAuthPlainTextFromSecurityType(securityType); + } + + private String getDefaultSmtpPort(ConnectionSecurity securityType) { + String port; + switch (securityType) { + case NONE: + case STARTTLS_REQUIRED: + port = SMTP_PORT; + break; + case SSL_TLS_REQUIRED: + port = SMTP_SSL_PORT; + break; + default: + port = ""; + Log.e(K9.LOG_TAG, "Unhandled ConnectionSecurity type encountered"); + } + return port; + } + + private void updateAuthPlainTextFromSecurityType(ConnectionSecurity securityType) { + switch (securityType) { + case NONE: + AuthType.PLAIN.useInsecureText(true, mAuthTypeAdapter); + break; + default: + AuthType.PLAIN.useInsecureText(false, mAuthTypeAdapter); + } } @Override @@ -298,34 +272,25 @@ public class AccountSetupOutgoing extends K9Activity implements OnClickListener, } protected void onNext() { - int securityType = (Integer)((SpinnerOption)mSecurityTypeView.getSelectedItem()).value; - URI uri; - try { - String usernameEnc = URLEncoder.encode(mUsernameView.getText().toString(), "UTF-8"); - String passwordEnc = URLEncoder.encode(mPasswordView.getText().toString(), "UTF-8"); - - String userInfo = null; - String authType = ((SpinnerOption)mAuthTypeView.getSelectedItem()).label; - if (mRequireLoginView.isChecked()) { - userInfo = usernameEnc + ":" + passwordEnc + ":" + authType; - } - String newHost = mServerView.getText().toString(); - int newPort = Integer.parseInt(mPortView.getText().toString()); - uri = new URI(smtpSchemes[securityType], userInfo, newHost, newPort, null, null, null); - mAccount.deleteCertificate(newHost, newPort, CheckDirection.OUTGOING); - mAccount.setTransportUri(uri.toString()); - AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.OUTGOING); - } catch (UnsupportedEncodingException enc) { - // This really shouldn't happen since the encoding is hardcoded to UTF-8 - Log.e(K9.LOG_TAG, "Couldn't urlencode username or password.", enc); - } catch (Exception e) { - /* - * It's unrecoverable if we cannot create a URI from components that - * we validated to be safe. - */ - failure(e); + ConnectionSecurity securityType = (ConnectionSecurity) mSecurityTypeView.getSelectedItem(); + String uri; + String username = null; + String password = null; + AuthType authType = null; + if (mRequireLoginView.isChecked()) { + username = mUsernameView.getText().toString(); + password = mPasswordView.getText().toString(); + authType = (AuthType) mAuthTypeView.getSelectedItem(); } + String newHost = mServerView.getText().toString(); + int newPort = Integer.parseInt(mPortView.getText().toString()); + String type = SmtpTransport.TRANSPORT_TYPE; + ServerSettings server = new ServerSettings(type, newHost, newPort, securityType, authType, username, password); + uri = Transport.createTransportUri(server); + mAccount.deleteCertificate(newHost, newPort, CheckDirection.OUTGOING); + mAccount.setTransportUri(uri); + AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.OUTGOING); } public void onClick(View v) { diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 63d2afa63..2fa6d3fd5 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -27,6 +27,7 @@ import android.app.PendingIntent; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageInfo; import android.database.Cursor; import android.net.Uri; import android.os.Build; @@ -2627,7 +2628,7 @@ public class MessagingController implements Runnable { } } - private void notifyUserIfCertificateProblem(Context context, Exception e, + public static void notifyUserIfCertificateProblem(Context context, Exception e, Account account, boolean incoming) { if (!(e instanceof CertificateValidationException)) { return; @@ -2647,7 +2648,7 @@ public class MessagingController implements Runnable { final PendingIntent pi = PendingIntent.getActivity(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT); final String title = context.getString( - R.string.notification_certificate_error_title, account.getName()); + R.string.notification_certificate_error_title, account.getDescription()); final NotificationCompat.Builder builder = new NotificationBuilder(context); builder.setSmallIcon(R.drawable.ic_notify_new_mail); @@ -2683,9 +2684,6 @@ public class MessagingController implements Runnable { static long uidfill = 0; static AtomicBoolean loopCatch = new AtomicBoolean(); public void addErrorMessage(Account account, String subject, Throwable t) { - if (!loopCatch.compareAndSet(false, true)) { - return; - } try { if (t == null) { return; @@ -2693,6 +2691,17 @@ public class MessagingController implements Runnable { CharArrayWriter baos = new CharArrayWriter(t.getStackTrace().length * 10); PrintWriter ps = new PrintWriter(baos); + try { + Application context = K9.app; + PackageInfo packageInfo = context.getPackageManager().getPackageInfo( + context.getPackageName(), 0); + ps.format("K9-Mail version: %s\r\n", packageInfo.versionName); + } catch (Exception e) { + // ignore + } + ps.format("Device make: %s\r\n", Build.MANUFACTURER); + ps.format("Device model: %s\r\n", Build.MODEL); + ps.format("Android version: %s\r\n\r\n", Build.VERSION.RELEASE); t.printStackTrace(ps); ps.close(); @@ -2703,13 +2712,11 @@ public class MessagingController implements Runnable { addErrorMessage(account, subject, baos.toString()); } catch (Throwable it) { Log.e(K9.LOG_TAG, "Could not save error message to " + account.getErrorFolderName(), it); - } finally { - loopCatch.set(false); } } public void addErrorMessage(Account account, String subject, String body) { - if (!K9.ENABLE_ERROR_FOLDER) { + if (!K9.DEBUG) { return; } if (!loopCatch.compareAndSet(false, true)) { @@ -5037,7 +5044,7 @@ public class MessagingController implements Runnable { * @param ringAndVibrate * {@code true}, if ringtone/vibration are allowed. {@code false}, otherwise. */ - private void configureNotification(NotificationCompat.Builder builder, String ringtone, + private static void configureNotification(NotificationCompat.Builder builder, String ringtone, long[] vibrationPattern, Integer ledColor, int ledSpeed, boolean ringAndVibrate) { // if it's quiet time, then we shouldn't be ringing, buzzing or flashing diff --git a/src/com/fsck/k9/mail/AuthType.java b/src/com/fsck/k9/mail/AuthType.java new file mode 100644 index 000000000..52ffe5866 --- /dev/null +++ b/src/com/fsck/k9/mail/AuthType.java @@ -0,0 +1,83 @@ +package com.fsck.k9.mail; + +import android.content.Context; +import android.widget.ArrayAdapter; +import com.fsck.k9.K9; +import com.fsck.k9.R; + +public enum AuthType { + /* + * The names of these authentication types are saved as strings when + * settings are exported and are also saved as part of the Server URI stored + * in the account settings. + * + * PLAIN and CRAM_MD5 originally referred to specific SASL authentication + * mechanisms. Their meaning has since been broadened to mean authentication + * with unencrypted and encrypted passwords, respectively. Nonetheless, + * their original names have been retained for backward compatibility with + * user settings. + */ + + PLAIN(R.string.account_setup_auth_type_normal_password){ + + @Override + public void useInsecureText(boolean insecure, ArrayAdapter authTypesAdapter) { + if (insecure) { + mResourceId = R.string.account_setup_auth_type_insecure_password; + } else { + mResourceId = R.string.account_setup_auth_type_normal_password; + } + authTypesAdapter.notifyDataSetChanged(); + } + }, + + CRAM_MD5(R.string.account_setup_auth_type_encrypted_password), + + /* + * The following are obsolete authentication settings that were used with + * SMTP. They are no longer presented to the user as options, but they may + * still exist in a user's settings from a previous version or may be found + * when importing settings. + */ + AUTOMATIC(0), + + LOGIN(0); + + static public ArrayAdapter getArrayAdapter(Context context) { + AuthType[] authTypes = {PLAIN, CRAM_MD5}; + ArrayAdapter authTypesAdapter = new ArrayAdapter(context, + android.R.layout.simple_spinner_item, authTypes); + authTypesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + return authTypesAdapter; + } + + int mResourceId; + + private AuthType(int id) { + mResourceId = id; + } + + /** + * Used to select an appropriate localized text label for the + * {@code AuthType.PLAIN} option presented to users. + * + * @param insecure + *

+ * A value of {@code true} will use "Normal password". + *

+ * A value of {@code false} will use + * "Password, transmitted insecurely" + */ + public void useInsecureText(boolean insecure, ArrayAdapter authTypesAdapter) { + // Do nothing. Overridden in AuthType.PLAIN + } + + @Override + public String toString() { + if (mResourceId == 0) { + return name(); + } else { + return K9.app.getString(mResourceId); + } + } +} diff --git a/src/com/fsck/k9/mail/Authentication.java b/src/com/fsck/k9/mail/Authentication.java index 4b9ae90ae..2872b1b58 100644 --- a/src/com/fsck/k9/mail/Authentication.java +++ b/src/com/fsck/k9/mail/Authentication.java @@ -17,21 +17,21 @@ public class Authentication { * @param b64Nonce The nonce as base64-encoded string. * @return The CRAM-MD5 response. * - * @throws AuthenticationFailedException If something went wrong. + * @throws MessagingException If something went wrong. * * @see Authentication#computeCramMd5Bytes(String, String, byte[]) */ public static String computeCramMd5(String username, String password, String b64Nonce) - throws AuthenticationFailedException { + throws MessagingException { try { byte[] b64NonceBytes = b64Nonce.getBytes(US_ASCII); byte[] b64CRAM = computeCramMd5Bytes(username, password, b64NonceBytes); return new String(b64CRAM, US_ASCII); - } catch (AuthenticationFailedException e) { + } catch (MessagingException e) { throw e; } catch (Exception e) { - throw new AuthenticationFailedException("This shouldn't happen", e); + throw new MessagingException("This shouldn't happen", e); } } @@ -44,17 +44,17 @@ public class Authentication { * @param b64Nonce The nonce as base64-encoded byte array. * @return The CRAM-MD5 response as byte array. * - * @throws AuthenticationFailedException If something went wrong. + * @throws MessagingException If something went wrong. * * @see RFC 2195 */ public static byte[] computeCramMd5Bytes(String username, String password, byte[] b64Nonce) - throws AuthenticationFailedException { + throws MessagingException { try { byte[] nonce = Base64.decodeBase64(b64Nonce); - byte[] secretBytes = password.getBytes(US_ASCII); + byte[] secretBytes = password.getBytes(); MessageDigest md = MessageDigest.getInstance("MD5"); if (secretBytes.length > 64) { secretBytes = md.digest(secretBytes); @@ -74,12 +74,12 @@ public class Authentication { byte[] result = md.digest(firstPass); String plainCRAM = username + " " + new String(Hex.encodeHex(result)); - byte[] b64CRAM = Base64.encodeBase64(plainCRAM.getBytes(US_ASCII)); + byte[] b64CRAM = Base64.encodeBase64(plainCRAM.getBytes()); return b64CRAM; } catch (Exception e) { - throw new AuthenticationFailedException("Something went wrong during CRAM-MD5 computation", e); + throw new MessagingException("Something went wrong during CRAM-MD5 computation", e); } } } diff --git a/src/com/fsck/k9/mail/ConnectionSecurity.java b/src/com/fsck/k9/mail/ConnectionSecurity.java index 98741303e..510eda039 100644 --- a/src/com/fsck/k9/mail/ConnectionSecurity.java +++ b/src/com/fsck/k9/mail/ConnectionSecurity.java @@ -1,19 +1,21 @@ package com.fsck.k9.mail; -/** - * The currently available connection security types. - * - *

- * Right now this enum is only used by {@link ServerSettings} and converted to store- or - * transport-specific constants in the different {@link Store} and {@link Transport} - * implementations. In the future we probably want to change this and use - * {@code ConnectionSecurity} exclusively. - *

- */ +import com.fsck.k9.K9; +import com.fsck.k9.R; + public enum ConnectionSecurity { - NONE, - STARTTLS_OPTIONAL, - STARTTLS_REQUIRED, - SSL_TLS_OPTIONAL, - SSL_TLS_REQUIRED + NONE(R.string.account_setup_incoming_security_none_label), + STARTTLS_REQUIRED(R.string.account_setup_incoming_security_tls_label), + SSL_TLS_REQUIRED(R.string.account_setup_incoming_security_ssl_label); + + private final int mResourceId; + + private ConnectionSecurity(int id) { + mResourceId = id; + } + + @Override + public String toString() { + return K9.app.getString(mResourceId); + } } diff --git a/src/com/fsck/k9/mail/ServerSettings.java b/src/com/fsck/k9/mail/ServerSettings.java index 9ba43556e..50207863d 100644 --- a/src/com/fsck/k9/mail/ServerSettings.java +++ b/src/com/fsck/k9/mail/ServerSettings.java @@ -48,7 +48,7 @@ public class ServerSettings { * * {@code null} if not applicable for the store or transport. */ - public final String authenticationType; + public final AuthType authenticationType; /** * The username part of the credentials needed to authenticate to the server. @@ -91,7 +91,7 @@ public class ServerSettings { * see {@link ServerSettings#password} */ public ServerSettings(String type, String host, int port, - ConnectionSecurity connectionSecurity, String authenticationType, String username, + ConnectionSecurity connectionSecurity, AuthType authenticationType, String username, String password) { this.type = type; this.host = host; @@ -124,7 +124,7 @@ public class ServerSettings { * see {@link ServerSettings#extra} */ public ServerSettings(String type, String host, int port, - ConnectionSecurity connectionSecurity, String authenticationType, String username, + ConnectionSecurity connectionSecurity, AuthType authenticationType, String username, String password, Map extra) { this.type = type; this.host = host; diff --git a/src/com/fsck/k9/mail/filter/Hex.java b/src/com/fsck/k9/mail/filter/Hex.java index 92022203b..049af6e84 100644 --- a/src/com/fsck/k9/mail/filter/Hex.java +++ b/src/com/fsck/k9/mail/filter/Hex.java @@ -30,13 +30,13 @@ public class Hex { }; /** - * Converts an array of bytes into an array of characters representing the hexidecimal values of each byte in order. + * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order. * The returned array will be double the length of the passed array, as it takes two characters to represent any * given byte. * * @param data * a byte[] to convert to Hex characters - * @return A char[] containing hexidecimal characters + * @return A char[] containing lower-case hexadecimal characters */ public static char[] encodeHex(byte[] data) { diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 2363112eb..ca6c5966b 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -27,6 +27,7 @@ import java.nio.charset.CodingErrorAction; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.security.Security; +import java.security.cert.CertificateException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -65,10 +66,12 @@ import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.R; import com.fsck.k9.controller.MessageRetrievalListener; +import com.fsck.k9.controller.MessagingController; import com.fsck.k9.helper.StringUtils; import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.power.TracingPowerManager; import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock; +import com.fsck.k9.mail.AuthType; import com.fsck.k9.mail.Authentication; import com.fsck.k9.mail.AuthenticationFailedException; import com.fsck.k9.mail.Body; @@ -84,6 +87,7 @@ import com.fsck.k9.mail.PushReceiver; import com.fsck.k9.mail.Pusher; import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.Store; +import com.fsck.k9.mail.filter.Base64; import com.fsck.k9.mail.filter.EOLConvertingOutputStream; import com.fsck.k9.mail.filter.FixedLengthInputStream; import com.fsck.k9.mail.filter.PeekableInputStream; @@ -110,14 +114,6 @@ import com.jcraft.jzlib.ZOutputStream; public class ImapStore extends Store { public static final String STORE_TYPE = "IMAP"; - public static final int CONNECTION_SECURITY_NONE = 0; - public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1; - public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2; - public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3; - public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4; - - public enum AuthType { PLAIN, CRAM_MD5 } - private static final int IDLE_READ_TIMEOUT_INCREMENT = 5 * 60 * 1000; private static final int IDLE_FAILURE_COUNT_LIMIT = 10; private static int MAX_DELAY_TIME = 5 * 60 * 1000; // 5 minutes @@ -128,6 +124,10 @@ public class ImapStore extends Store { private Set mPermanentFlagsIndex = new HashSet(); private static final String CAPABILITY_IDLE = "IDLE"; + private static final String CAPABILITY_AUTH_CRAM_MD5 = "AUTH=CRAM-MD5"; + private static final String CAPABILITY_AUTH_PLAIN = "AUTH=PLAIN"; + private static final String CAPABILITY_LOGINDISABLED = "LOGINDISABLED"; + private static final String CAPABILITY_LITERAL_PLUS = "LITERAL+"; private static final String COMMAND_IDLE = "IDLE"; private static final String CAPABILITY_NAMESPACE = "NAMESPACE"; private static final String COMMAND_NAMESPACE = "NAMESPACE"; @@ -147,18 +147,16 @@ public class ImapStore extends Store { * *

Possible forms:

*
-     * imap://auth:user:password@server:port CONNECTION_SECURITY_NONE
-     * imap+tls://auth:user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
-     * imap+tls+://auth:user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
-     * imap+ssl+://auth:user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
-     * imap+ssl://auth:user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
+     * imap://auth:user:password@server:port ConnectionSecurity.NONE
+     * imap+tls+://auth:user:password@server:port ConnectionSecurity.STARTTLS_REQUIRED
+     * imap+ssl+://auth:user:password@server:port ConnectionSecurity.SSL_TLS_REQUIRED
      * 
*/ public static ImapStoreSettings decodeUri(String uri) { String host; int port; ConnectionSecurity connectionSecurity; - String authenticationType = null; + AuthType authenticationType = null; String username = null; String password = null; String pathPrefix = null; @@ -172,21 +170,27 @@ public class ImapStore extends Store { } String scheme = imapUri.getScheme(); + /* + * Currently available schemes are: + * imap + * imap+tls+ + * imap+ssl+ + * + * The following are obsolete schemes that may be found in pre-existing + * settings from earlier versions or that may be found when imported. We + * continue to recognize them and re-map them appropriately: + * imap+tls + * imap+ssl + */ if (scheme.equals("imap")) { connectionSecurity = ConnectionSecurity.NONE; port = 143; - } else if (scheme.equals("imap+tls")) { - connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; - port = 143; - } else if (scheme.equals("imap+tls+")) { + } else if (scheme.startsWith("imap+tls")) { connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; port = 143; - } else if (scheme.equals("imap+ssl+")) { + } else if (scheme.startsWith("imap+ssl")) { connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; port = 993; - } else if (scheme.equals("imap+ssl")) { - connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; - port = 993; } else { throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); } @@ -204,14 +208,14 @@ public class ImapStore extends Store { if (userinfo.endsWith(":")) { // Password is empty. This can only happen after an account was imported. - authenticationType = AuthType.valueOf(userInfoParts[0]).name(); + authenticationType = AuthType.valueOf(userInfoParts[0]); username = URLDecoder.decode(userInfoParts[1], "UTF-8"); } else if (userInfoParts.length == 2) { - authenticationType = AuthType.PLAIN.name(); + authenticationType = AuthType.PLAIN; username = URLDecoder.decode(userInfoParts[0], "UTF-8"); password = URLDecoder.decode(userInfoParts[1], "UTF-8"); } else { - authenticationType = AuthType.valueOf(userInfoParts[0]).name(); + authenticationType = AuthType.valueOf(userInfoParts[0]); username = URLDecoder.decode(userInfoParts[1], "UTF-8"); password = URLDecoder.decode(userInfoParts[2], "UTF-8"); } @@ -268,15 +272,9 @@ public class ImapStore extends Store { String scheme; switch (server.connectionSecurity) { - case SSL_TLS_OPTIONAL: - scheme = "imap+ssl"; - break; case SSL_TLS_REQUIRED: scheme = "imap+ssl+"; break; - case STARTTLS_OPTIONAL: - scheme = "imap+tls"; - break; case STARTTLS_REQUIRED: scheme = "imap+tls+"; break; @@ -286,15 +284,9 @@ public class ImapStore extends Store { break; } - AuthType authType; - try { - authType = AuthType.valueOf(server.authenticationType); - } catch (Exception e) { - throw new IllegalArgumentException("Invalid authentication type: " + - server.authenticationType); - } + AuthType authType = server.authenticationType; - String userInfo = authType.toString() + ":" + userEnc + ":" + passwordEnc; + String userInfo = authType.name() + ":" + userEnc + ":" + passwordEnc; try { Map extra = server.getExtra(); String path = null; @@ -329,7 +321,7 @@ public class ImapStore extends Store { public final String pathPrefix; protected ImapStoreSettings(String host, int port, ConnectionSecurity connectionSecurity, - String authenticationType, String username, String password, + AuthType authenticationType, String username, String password, boolean autodetectNamespace, String pathPrefix) { super(STORE_TYPE, host, port, connectionSecurity, authenticationType, username, password); @@ -357,7 +349,7 @@ public class ImapStore extends Store { private int mPort; private String mUsername; private String mPassword; - private int mConnectionSecurity; + private ConnectionSecurity mConnectionSecurity; private AuthType mAuthType; private volatile String mPathPrefix; private volatile String mCombinedPrefix = null; @@ -376,7 +368,7 @@ public class ImapStore extends Store { } @Override - public int getConnectionSecurity() { + public ConnectionSecurity getConnectionSecurity() { return mConnectionSecurity; } @@ -462,25 +454,9 @@ public class ImapStore extends Store { mHost = settings.host; mPort = settings.port; - switch (settings.connectionSecurity) { - case NONE: - mConnectionSecurity = CONNECTION_SECURITY_NONE; - break; - case STARTTLS_OPTIONAL: - mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL; - break; - case STARTTLS_REQUIRED: - mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED; - break; - case SSL_TLS_OPTIONAL: - mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; - break; - case SSL_TLS_REQUIRED: - mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; - break; - } + mConnectionSecurity = settings.connectionSecurity; - mAuthType = AuthType.valueOf(settings.authenticationType); + mAuthType = settings.authenticationType; mUsername = settings.username; mPassword = settings.password; @@ -2429,7 +2405,7 @@ public class ImapStore extends Store { } try { - int connectionSecurity = mSettings.getConnectionSecurity(); + ConnectionSecurity connectionSecurity = mSettings.getConnectionSecurity(); // Try all IPv4 and IPv6 addresses of the host InetAddress[] addresses = InetAddress.getAllByName(mSettings.getHost()); @@ -2443,15 +2419,13 @@ public class ImapStore extends Store { SocketAddress socketAddress = new InetSocketAddress(addresses[i], mSettings.getPort()); - if (connectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED || - connectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) { + if (connectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) { SSLContext sslContext = SSLContext.getInstance("TLS"); - boolean secure = connectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED; sslContext .init(null, new TrustManager[] { TrustManagerFactory.get( mSettings.getHost(), - mSettings.getPort(), secure) }, + mSettings.getPort()) }, new SecureRandom()); mSocket = TrustedSocketFactory.createSocket(sslContext); } else { @@ -2476,7 +2450,7 @@ public class ImapStore extends Store { mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), 1024)); mParser = new ImapResponseParser(mIn); - mOut = mSocket.getOutputStream(); + mOut = new BufferedOutputStream(mSocket.getOutputStream(), 1024); capabilities.clear(); ImapResponse nullResponse = mParser.readResponse(); @@ -2496,19 +2470,17 @@ public class ImapStore extends Store { } } - if (mSettings.getConnectionSecurity() == CONNECTION_SECURITY_TLS_OPTIONAL - || mSettings.getConnectionSecurity() == CONNECTION_SECURITY_TLS_REQUIRED) { + if (mSettings.getConnectionSecurity() == ConnectionSecurity.STARTTLS_REQUIRED) { if (hasCapability("STARTTLS")) { // STARTTLS executeSimpleCommand("STARTTLS"); SSLContext sslContext = SSLContext.getInstance("TLS"); - boolean secure = mSettings.getConnectionSecurity() == CONNECTION_SECURITY_TLS_REQUIRED; sslContext.init(null, new TrustManager[] { TrustManagerFactory.get( mSettings.getHost(), - mSettings.getPort(), secure) }, + mSettings.getPort()) }, new SecureRandom()); mSocket = TrustedSocketFactory.createSocket(sslContext, mSocket, mSettings.getHost(), mSettings.getPort(), true); @@ -2516,37 +2488,55 @@ public class ImapStore extends Store { mIn = new PeekableInputStream(new BufferedInputStream(mSocket .getInputStream(), 1024)); mParser = new ImapResponseParser(mIn); - mOut = mSocket.getOutputStream(); - } else if (mSettings.getConnectionSecurity() == CONNECTION_SECURITY_TLS_REQUIRED) { - throw new MessagingException("TLS not supported but required"); - } - } - - mOut = new BufferedOutputStream(mOut, 1024); - - try { - if (mSettings.getAuthType() == AuthType.CRAM_MD5) { - authCramMD5(); - // The authCramMD5 method called on the previous line does not allow for handling updated capabilities - // sent by the server. So, to make sure we update to the post-authentication capability list - // we fetch the capabilities here. + mOut = new BufferedOutputStream(mSocket.getOutputStream(), 1024); + // Per RFC 2595 (3.1): Once TLS has been started, reissue CAPABILITY command if (K9.DEBUG) - Log.i(K9.LOG_TAG, "Updating capabilities after CRAM-MD5 authentication for " + getLogId()); + Log.i(K9.LOG_TAG, "Updating capabilities after STARTTLS for " + getLogId()); + capabilities.clear(); List responses = receiveCapabilities(executeSimpleCommand(COMMAND_CAPABILITY)); if (responses.size() != 2) { throw new MessagingException("Invalid CAPABILITY response received"); } - - } else if (mSettings.getAuthType() == AuthType.PLAIN) { - receiveCapabilities(executeSimpleCommand(String.format("LOGIN %s %s", ImapStore.encodeString(mSettings.getUsername()), ImapStore.encodeString(mSettings.getPassword())), true)); + } else { + /* + * This exception triggers a "Certificate error" + * notification that takes the user to the incoming + * server settings for review. This might be needed if + * the account was configured with an obsolete + * "STARTTLS (if available)" setting. + */ + throw new CertificateValidationException( + "STARTTLS connection security not available", + new CertificateException()); } - authSuccess = true; - } catch (ImapException ie) { - throw new AuthenticationFailedException(ie.getAlertText(), ie); - - } catch (MessagingException me) { - throw new AuthenticationFailedException(null, me); } + + switch (mSettings.getAuthType()) { + case CRAM_MD5: + if (hasCapability(CAPABILITY_AUTH_CRAM_MD5)) { + authCramMD5(); + } else { + throw new MessagingException( + "Server doesn't support encrypted passwords using CRAM-MD5."); + } + break; + + case PLAIN: + if (hasCapability(CAPABILITY_AUTH_PLAIN)) { + saslAuthPlain(); + } else if (!hasCapability(CAPABILITY_LOGINDISABLED)) { + login(); + } else { + throw new MessagingException( + "Server doesn't support unencrypted passwords using AUTH=PLAIN and LOGIN is disabled."); + } + break; + + default: + throw new MessagingException( + "Unhandled authentication method found in the server settings (bug)."); + } + authSuccess = true; if (K9.DEBUG) { Log.d(K9.LOG_TAG, CAPABILITY_COMPRESS_DEFLATE + " = " + hasCapability(CAPABILITY_COMPRESS_DEFLATE)); } @@ -2664,48 +2654,123 @@ public class ImapStore extends Store { } } - protected void authCramMD5() throws AuthenticationFailedException, MessagingException { - try { - String tag = sendCommand("AUTHENTICATE CRAM-MD5", false); - byte[] buf = new byte[1024]; - int b64NonceLen = 0; - for (int i = 0; i < buf.length; i++) { - buf[i] = (byte)mIn.read(); - if (buf[i] == 0x0a) { - b64NonceLen = i; - break; - } + protected void login() throws IOException, MessagingException { + boolean hasLiteralPlus = hasCapability(CAPABILITY_LITERAL_PLUS); + String tag; + byte[] username = mSettings.getUsername().getBytes(); + byte[] password = mSettings.getPassword().getBytes(); + tag = sendCommand(String.format(Locale.US, "LOGIN {%d%s}", + username.length, hasLiteralPlus ? "+" : ""), true); + if (!hasLiteralPlus) { + readContinuationResponse(tag); } - if (b64NonceLen == 0) { - throw new AuthenticationFailedException("Error negotiating CRAM-MD5: nonce too long."); + mOut.write(username); + mOut.write(String.format(Locale.US, " {%d%s}\r\n", password.length, + hasLiteralPlus ? "+" : "").getBytes()); + if (!hasLiteralPlus) { + mOut.flush(); + readContinuationResponse(tag); } - byte[] b64NonceTrim = new byte[b64NonceLen - 2]; - System.arraycopy(buf, 1, b64NonceTrim, 0, b64NonceLen - 2); - - byte[] b64CRAM = Authentication.computeCramMd5Bytes(mSettings.getUsername(), - mSettings.getPassword(), b64NonceTrim); - - mOut.write(b64CRAM); - mOut.write(new byte[] { 0x0d, 0x0a }); + mOut.write(password); + mOut.write('\r'); + mOut.write('\n'); mOut.flush(); + try { + receiveCapabilities(readStatusResponse(tag, "LOGIN", null)); + } catch (MessagingException e) { + throw new AuthenticationFailedException(e.getMessage()); + } + } - int respLen = 0; - for (int i = 0; i < buf.length; i++) { - buf[i] = (byte)mIn.read(); - if (buf[i] == 0x0a) { - respLen = i; - break; + protected void authCramMD5() throws MessagingException, IOException { + String command = "AUTHENTICATE CRAM-MD5"; + String tag = sendCommand(command, false); + ImapResponse response = readContinuationResponse(tag); + if (response.size() != 1 || !(response.get(0) instanceof String)) { + throw new MessagingException("Invalid Cram-MD5 nonce received"); + } + byte[] b64Nonce = response.getString(0).getBytes(); + byte[] b64CRAM = Authentication.computeCramMd5Bytes( + mSettings.getUsername(), mSettings.getPassword(), b64Nonce); + + mOut.write(b64CRAM); + mOut.write('\r'); + mOut.write('\n'); + mOut.flush(); + try { + receiveCapabilities(readStatusResponse(tag, command, null)); + } catch (MessagingException e) { + throw new AuthenticationFailedException(e.getMessage()); + } + } + + protected void saslAuthPlain() throws IOException, MessagingException { + String command = "AUTHENTICATE PLAIN"; + String tag = sendCommand(command, false); + readContinuationResponse(tag); + mOut.write(Base64.encodeBase64(("\000" + mSettings.getUsername() + + "\000" + mSettings.getPassword()).getBytes())); + mOut.write('\r'); + mOut.write('\n'); + mOut.flush(); + try { + receiveCapabilities(readStatusResponse(tag, command, null)); + } catch (MessagingException e) { + throw new AuthenticationFailedException(e.getMessage()); + } + } + + protected ImapResponse readContinuationResponse(String tag) + throws IOException, MessagingException { + ImapResponse response; + do { + response = readResponse(); + if (response.mTag != null) { + if (response.mTag.equalsIgnoreCase(tag)) { + throw new MessagingException( + "Command continuation aborted: " + response); + } else { + Log.w(K9.LOG_TAG, "After sending tag " + tag + + ", got tag response from previous command " + + response + " for " + getLogId()); } } + } while (!response.mCommandContinuationRequested); + return response; + } - String toMatch = tag + " OK"; - String respStr = new String(buf, 0, respLen); - if (!respStr.startsWith(toMatch)) { - throw new AuthenticationFailedException("CRAM-MD5 error: " + respStr); + protected ArrayList readStatusResponse(String tag, + String commandToLog, UntaggedHandler untaggedHandler) + throws IOException, MessagingException { + ArrayList responses = new ArrayList(); + ImapResponse response; + do { + response = mParser.readResponse(); + if (K9.DEBUG && K9.DEBUG_PROTOCOL_IMAP) + Log.v(K9.LOG_TAG, getLogId() + "<<<" + response); + + if (response.mTag != null && !response.mTag.equalsIgnoreCase(tag)) { + Log.w(K9.LOG_TAG, "After sending tag " + tag + ", got tag response from previous command " + response + " for " + getLogId()); + Iterator iter = responses.iterator(); + while (iter.hasNext()) { + ImapResponse delResponse = iter.next(); + if (delResponse.mTag != null || delResponse.size() < 2 + || (!ImapResponseParser.equalsIgnoreCase(delResponse.get(1), "EXISTS") && !ImapResponseParser.equalsIgnoreCase(delResponse.get(1), "EXPUNGE"))) { + iter.remove(); + } + } + response.mTag = null; + continue; } - } catch (IOException ioe) { - throw new AuthenticationFailedException("CRAM-MD5 Auth Failed.", ioe); + if (untaggedHandler != null) { + untaggedHandler.handleAsyncUntaggedResponse(response); + } + responses.add(response); + } while (response.mTag == null); + if (response.size() < 1 || !ImapResponseParser.equalsIgnoreCase(response.get(0), "OK")) { + throw new ImapException("Command: " + commandToLog + "; response: " + response.toString(), response.getAlertText()); } + return responses; } protected void setReadTimeout(int millis) throws SocketException { @@ -2830,35 +2895,7 @@ public class ImapStore extends Store { //if (K9.DEBUG) // Log.v(K9.LOG_TAG, "Sent IMAP command " + commandToLog + " with tag " + tag + " for " + getLogId()); - ArrayList responses = new ArrayList(); - ImapResponse response; - do { - response = mParser.readResponse(); - if (K9.DEBUG && K9.DEBUG_PROTOCOL_IMAP) - Log.v(K9.LOG_TAG, getLogId() + "<<<" + response); - - if (response.mTag != null && !response.mTag.equalsIgnoreCase(tag)) { - Log.w(K9.LOG_TAG, "After sending tag " + tag + ", got tag response from previous command " + response + " for " + getLogId()); - Iterator iter = responses.iterator(); - while (iter.hasNext()) { - ImapResponse delResponse = iter.next(); - if (delResponse.mTag != null || delResponse.size() < 2 - || (!ImapResponseParser.equalsIgnoreCase(delResponse.get(1), "EXISTS") && !ImapResponseParser.equalsIgnoreCase(delResponse.get(1), "EXPUNGE"))) { - iter.remove(); - } - } - response.mTag = null; - continue; - } - if (untaggedHandler != null) { - untaggedHandler.handleAsyncUntaggedResponse(response); - } - responses.add(response); - } while (response.mTag == null); - if (response.size() < 1 || !ImapResponseParser.equalsIgnoreCase(response.get(0), "OK")) { - throw new ImapException("Command: " + commandToLog + "; response: " + response.toString(), response.getAlertText()); - } - return responses; + return readStatusResponse(tag, commandToLog, untaggedHandler); } } @@ -3102,6 +3139,7 @@ public class ImapStore extends Store { if (stop.get()) { Log.i(K9.LOG_TAG, "Got exception while idling, but stop is set for " + getLogId()); } else { + MessagingController.notifyUserIfCertificateProblem(K9.app, e, getAccount(), true); receiver.pushError("Push error for " + getName(), e); Log.e(K9.LOG_TAG, "Got exception while idling for " + getLogId(), e); int delayTimeInt = delayTime.get(); diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java index ee38e9a08..f50187a36 100644 --- a/src/com/fsck/k9/mail/store/Pop3Store.java +++ b/src/com/fsck/k9/mail/store/Pop3Store.java @@ -9,6 +9,8 @@ import com.fsck.k9.controller.MessageRetrievalListener; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.*; +import com.fsck.k9.mail.filter.Base64; +import com.fsck.k9.mail.filter.Hex; import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.net.ssl.TrustManagerFactory; import com.fsck.k9.net.ssl.TrustedSocketFactory; @@ -16,11 +18,16 @@ import com.fsck.k9.net.ssl.TrustedSocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; + import java.io.*; import java.net.*; import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.cert.CertificateException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.LinkedList; import java.util.HashMap; @@ -33,21 +40,11 @@ import java.util.Set; public class Pop3Store extends Store { public static final String STORE_TYPE = "POP3"; - public static final int CONNECTION_SECURITY_NONE = 0; - public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1; - public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2; - public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3; - public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4; - - private enum AuthType { - PLAIN, - CRAM_MD5 - } - private static final String STLS_COMMAND = "STLS"; private static final String USER_COMMAND = "USER"; private static final String PASS_COMMAND = "PASS"; private static final String CAPA_COMMAND = "CAPA"; + private static final String AUTH_COMMAND = "AUTH"; private static final String STAT_COMMAND = "STAT"; private static final String LIST_COMMAND = "LIST"; private static final String UIDL_COMMAND = "UIDL"; @@ -58,20 +55,19 @@ public class Pop3Store extends Store { private static final String STLS_CAPABILITY = "STLS"; private static final String UIDL_CAPABILITY = "UIDL"; - private static final String PIPELINING_CAPABILITY = "PIPELINING"; - private static final String USER_CAPABILITY = "USER"; private static final String TOP_CAPABILITY = "TOP"; + private static final String SASL_CAPABILITY = "SASL"; + private static final String AUTH_PLAIN_CAPABILITY = "PLAIN"; + private static final String AUTH_CRAM_MD5_CAPABILITY = "CRAM-MD5"; /** * Decodes a Pop3Store URI. * *

Possible forms:

*
-     * pop3://user:password@server:port CONNECTION_SECURITY_NONE
-     * pop3+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
-     * pop3+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
-     * pop3+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
-     * pop3+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
+     * pop3://user:password@server:port ConnectionSecurity.NONE
+     * pop3+tls+://user:password@server:port ConnectionSecurity.STARTTLS_REQUIRED
+     * pop3+ssl+://user:password@server:port ConnectionSecurity.SSL_TLS_REQUIRED
      * 
*/ public static ServerSettings decodeUri(String uri) { @@ -89,21 +85,27 @@ public class Pop3Store extends Store { } String scheme = pop3Uri.getScheme(); + /* + * Currently available schemes are: + * pop3 + * pop3+tls+ + * pop3+ssl+ + * + * The following are obsolete schemes that may be found in pre-existing + * settings from earlier versions or that may be found when imported. We + * continue to recognize them and re-map them appropriately: + * pop3+tls + * pop3+ssl + */ if (scheme.equals("pop3")) { connectionSecurity = ConnectionSecurity.NONE; port = 110; - } else if (scheme.equals("pop3+tls")) { - connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; - port = 110; - } else if (scheme.equals("pop3+tls+")) { + } else if (scheme.startsWith("pop3+tls")) { connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; port = 110; - } else if (scheme.equals("pop3+ssl+")) { + } else if (scheme.startsWith("pop3+ssl")) { connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; port = 995; - } else if (scheme.equals("pop3+ssl")) { - connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; - port = 995; } else { throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); } @@ -114,7 +116,7 @@ public class Pop3Store extends Store { port = pop3Uri.getPort(); } - String authType = AuthType.PLAIN.name(); + AuthType authType = AuthType.PLAIN; if (pop3Uri.getUserInfo() != null) { try { int userIndex = 0, passwordIndex = 1; @@ -125,7 +127,7 @@ public class Pop3Store extends Store { // after an account was imported (so authType and username are present). userIndex++; passwordIndex++; - authType = userInfoParts[0]; + authType = AuthType.valueOf(userInfoParts[0]); } username = URLDecoder.decode(userInfoParts[userIndex], "UTF-8"); if (userInfoParts.length > passwordIndex) { @@ -166,15 +168,9 @@ public class Pop3Store extends Store { String scheme; switch (server.connectionSecurity) { - case SSL_TLS_OPTIONAL: - scheme = "pop3+ssl"; - break; case SSL_TLS_REQUIRED: scheme = "pop3+ssl+"; break; - case STARTTLS_OPTIONAL: - scheme = "pop3+tls"; - break; case STARTTLS_REQUIRED: scheme = "pop3+tls+"; break; @@ -184,14 +180,7 @@ public class Pop3Store extends Store { break; } - try { - AuthType.valueOf(server.authenticationType); - } catch (Exception e) { - throw new IllegalArgumentException("Invalid authentication type (" + - server.authenticationType + ")"); - } - - String userInfo = server.authenticationType + ":" + userEnc + ":" + passwordEnc; + String userInfo = server.authenticationType.name() + ":" + userEnc + ":" + passwordEnc; try { return new URI(scheme, userInfo, server.host, server.port, null, null, null).toString(); @@ -206,7 +195,7 @@ public class Pop3Store extends Store { private String mUsername; private String mPassword; private AuthType mAuthType; - private int mConnectionSecurity; + private ConnectionSecurity mConnectionSecurity; private HashMap mFolders = new HashMap(); private Pop3Capabilities mCapabilities; @@ -231,27 +220,11 @@ public class Pop3Store extends Store { mHost = settings.host; mPort = settings.port; - switch (settings.connectionSecurity) { - case NONE: - mConnectionSecurity = CONNECTION_SECURITY_NONE; - break; - case STARTTLS_OPTIONAL: - mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL; - break; - case STARTTLS_REQUIRED: - mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED; - break; - case SSL_TLS_OPTIONAL: - mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; - break; - case SSL_TLS_REQUIRED: - mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; - break; - } + mConnectionSecurity = settings.connectionSecurity; mUsername = settings.username; mPassword = settings.password; - mAuthType = AuthType.valueOf(settings.authenticationType); + mAuthType = settings.authenticationType; } @Override @@ -327,13 +300,11 @@ public class Pop3Store extends Store { try { SocketAddress socketAddress = new InetSocketAddress(mHost, mPort); - if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED || - mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) { + if (mConnectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) { SSLContext sslContext = SSLContext.getInstance("TLS"); - final boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED; sslContext.init(null, new TrustManager[] { TrustManagerFactory.get(mHost, - mPort, secure) }, new SecureRandom()); + mPort) }, new SecureRandom()); mSocket = TrustedSocketFactory.createSocket(sslContext); } else { mSocket = new Socket(); @@ -348,21 +319,18 @@ public class Pop3Store extends Store { throw new MessagingException("Unable to connect socket"); } - // Eat the banner - executeSimpleCommand(null); + String serverGreeting = executeSimpleCommand(null); - if (mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL - || mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) { - mCapabilities = getCapabilities(); + mCapabilities = getCapabilities(); + if (mConnectionSecurity == ConnectionSecurity.STARTTLS_REQUIRED) { if (mCapabilities.stls) { executeSimpleCommand(STLS_COMMAND); SSLContext sslContext = SSLContext.getInstance("TLS"); - boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED; sslContext.init(null, new TrustManager[] { TrustManagerFactory.get( - mHost, mPort, secure) }, + mHost, mPort) }, new SecureRandom()); mSocket = TrustedSocketFactory.createSocket(sslContext, mSocket, mHost, mPort, true); @@ -372,31 +340,42 @@ public class Pop3Store extends Store { if (!isOpen()) { throw new MessagingException("Unable to connect socket"); } - } else if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) { - throw new MessagingException("TLS not supported but required"); + mCapabilities = getCapabilities(); + } else { + /* + * This exception triggers a "Certificate error" + * notification that takes the user to the incoming + * server settings for review. This might be needed if + * the account was configured with an obsolete + * "STARTTLS (if available)" setting. + */ + throw new CertificateValidationException( + "STARTTLS connection security not available", + new CertificateException()); } } - if (mAuthType == AuthType.CRAM_MD5) { - try { - String b64Nonce = executeSimpleCommand("AUTH CRAM-MD5").replace("+ ", ""); - - String b64CRAM = Authentication.computeCramMd5(mUsername, mPassword, b64Nonce); - executeSimpleCommand(b64CRAM); - - } catch (MessagingException me) { - throw new AuthenticationFailedException(null, me); + switch (mAuthType) { + case PLAIN: + if (mCapabilities.authPlain) { + authPlain(); + } else { + login(); } - } else { - try { - executeSimpleCommand(USER_COMMAND + " " + mUsername); - executeSimpleCommand(PASS_COMMAND + " " + mPassword, true); - } catch (MessagingException me) { - throw new AuthenticationFailedException(null, me); + break; + + case CRAM_MD5: + if (mCapabilities.cramMD5) { + authCramMD5(); + } else { + authAPOP(serverGreeting); } + break; + + default: + throw new MessagingException( + "Unhandled authentication method found in the server settings (bug)."); } - - mCapabilities = getCapabilities(); } catch (SSLException e) { throw new CertificateValidationException(e.getMessage(), e); } catch (GeneralSecurityException gse) { @@ -415,6 +394,67 @@ public class Pop3Store extends Store { mUidToMsgNumMap.clear(); } + private void login() throws MessagingException { + executeSimpleCommand(USER_COMMAND + " " + mUsername); + try { + executeSimpleCommand(PASS_COMMAND + " " + mPassword, true); + } catch (Pop3ErrorResponse e) { + throw new AuthenticationFailedException( + "POP3 login authentication failed: " + e.getMessage(), e); + } + } + + private void authPlain() throws MessagingException { + executeSimpleCommand("AUTH PLAIN"); + try { + byte[] encodedBytes = Base64.encodeBase64(("\000" + mUsername + + "\000" + mPassword).getBytes()); + executeSimpleCommand(new String(encodedBytes), true); + } catch (Pop3ErrorResponse e) { + throw new AuthenticationFailedException( + "POP3 SASL auth PLAIN authentication failed: " + + e.getMessage(), e); + } + } + + private void authAPOP(String serverGreeting) throws MessagingException { + // regex based on RFC 2449 (3.) "Greeting" + String timestamp = serverGreeting.replaceFirst( + "^\\+OK *(?:\\[[^\\]]+\\])?[^<]*(<[^>]*>)?[^<]*$", "$1"); + if ("".equals(timestamp)) { + throw new MessagingException( + "APOP authentication is not supported"); + } + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + throw new MessagingException( + "MD5 failure during POP3 auth APOP", e); + } + byte[] digest = md.digest((timestamp + mPassword).getBytes()); + String hexDigest = new String(Hex.encodeHex(digest)); + try { + executeSimpleCommand("APOP " + mUsername + " " + hexDigest, true); + } catch (Pop3ErrorResponse e) { + throw new AuthenticationFailedException( + "POP3 APOP authentication failed: " + e.getMessage(), e); + } + } + + private void authCramMD5() throws MessagingException { + String b64Nonce = executeSimpleCommand("AUTH CRAM-MD5").replace("+ ", ""); + + String b64CRAM = Authentication.computeCramMd5(mUsername, mPassword, b64Nonce); + try { + executeSimpleCommand(b64CRAM, true); + } catch (Pop3ErrorResponse e) { + throw new AuthenticationFailedException( + "POP3 CRAM-MD5 authentication failed: " + + e.getMessage(), e); + } + } + @Override public boolean isOpen() { return (mIn != null && mOut != null && mSocket != null @@ -984,22 +1024,54 @@ public class Pop3Store extends Store { private Pop3Capabilities getCapabilities() throws IOException { Pop3Capabilities capabilities = new Pop3Capabilities(); + try { + /* + * Try sending an AUTH command with no arguments. + * + * The server may respond with a list of supported SASL + * authentication mechanisms. + * + * Ref.: http://tools.ietf.org/html/draft-myers-sasl-pop3-05 + * + * While this never became a standard, there are servers that + * support it, and Thunderbird includes this check. + */ + String response = executeSimpleCommand(AUTH_COMMAND); + while ((response = readLine()) != null) { + if (response.equals(".")) { + break; + } + response = response.toUpperCase(Locale.US); + if (response.equals(AUTH_PLAIN_CAPABILITY)) { + capabilities.authPlain = true; + } else if (response.equals(AUTH_CRAM_MD5_CAPABILITY)) { + capabilities.cramMD5 = true; + } + } + } catch (MessagingException e) { + // Assume AUTH command with no arguments is not supported. + } try { String response = executeSimpleCommand(CAPA_COMMAND); while ((response = readLine()) != null) { if (response.equals(".")) { break; } - if (response.equalsIgnoreCase(STLS_CAPABILITY)) { + response = response.toUpperCase(Locale.US); + if (response.equals(STLS_CAPABILITY)) { capabilities.stls = true; - } else if (response.equalsIgnoreCase(UIDL_CAPABILITY)) { + } else if (response.equals(UIDL_CAPABILITY)) { capabilities.uidl = true; - } else if (response.equalsIgnoreCase(PIPELINING_CAPABILITY)) { - capabilities.pipelining = true; - } else if (response.equalsIgnoreCase(USER_CAPABILITY)) { - capabilities.user = true; - } else if (response.equalsIgnoreCase(TOP_CAPABILITY)) { + } else if (response.equals(TOP_CAPABILITY)) { capabilities.top = true; + } else if (response.startsWith(SASL_CAPABILITY)) { + List saslAuthMechanisms = Arrays.asList(response.split(" ")); + if (saslAuthMechanisms.contains(AUTH_PLAIN_CAPABILITY)) { + capabilities.authPlain = true; + } + if (saslAuthMechanisms.contains(AUTH_CRAM_MD5_CAPABILITY)) { + capabilities.cramMD5 = true; + } } } @@ -1116,20 +1188,20 @@ public class Pop3Store extends Store { } static class Pop3Capabilities { + public boolean cramMD5; + public boolean authPlain; public boolean stls; public boolean top; - public boolean user; public boolean uidl; - public boolean pipelining; @Override public String toString() { - return String.format("STLS %b, TOP %b, USER %b, UIDL %b, PIPELINING %b", + return String.format("CRAM-MD5 %b, PLAIN %b, STLS %b, TOP %b, UIDL %b", + cramMD5, + authPlain, stls, top, - user, - uidl, - pipelining); + uidl); } } diff --git a/src/com/fsck/k9/mail/store/WebDavSocketFactory.java b/src/com/fsck/k9/mail/store/WebDavSocketFactory.java index 9563f510e..73f110e4e 100644 --- a/src/com/fsck/k9/mail/store/WebDavSocketFactory.java +++ b/src/com/fsck/k9/mail/store/WebDavSocketFactory.java @@ -31,7 +31,7 @@ public class WebDavSocketFactory implements LayeredSocketFactory { public WebDavSocketFactory(String host, int port, boolean secure) throws NoSuchAlgorithmException, KeyManagementException { SSLContext sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, new TrustManager[] { - TrustManagerFactory.get(host, port, secure) + TrustManagerFactory.get(host, port) }, new SecureRandom()); mSocketFactory = sslContext.getSocketFactory(); mSchemeSocketFactory = org.apache.http.conn.ssl.SSLSocketFactory.getSocketFactory(); diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index ea33e917f..7b50984ae 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -57,13 +57,6 @@ import java.util.zip.GZIPInputStream; public class WebDavStore extends Store { public static final String STORE_TYPE = "WebDAV"; - // Security options - private static final short CONNECTION_SECURITY_NONE = 0; - private static final short CONNECTION_SECURITY_TLS_OPTIONAL = 1; - private static final short CONNECTION_SECURITY_TLS_REQUIRED = 2; - private static final short CONNECTION_SECURITY_SSL_OPTIONAL = 3; - private static final short CONNECTION_SECURITY_SSL_REQUIRED = 4; - // Authentication types private static final short AUTH_TYPE_NONE = 0; private static final short AUTH_TYPE_BASIC = 1; @@ -89,11 +82,8 @@ public class WebDavStore extends Store { * *

Possible forms:

*
-     * webdav://user:password@server:port CONNECTION_SECURITY_NONE
-     * webdav+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
-     * webdav+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
-     * webdav+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
-     * webdav+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
+     * webdav://user:password@server:port ConnectionSecurity.NONE
+     * webdav+ssl+://user:password@server:port ConnectionSecurity.SSL_TLS_REQUIRED
      * 
*/ public static WebDavStoreSettings decodeUri(String uri) { @@ -116,16 +106,22 @@ public class WebDavStore extends Store { } String scheme = webDavUri.getScheme(); + /* + * Currently available schemes are: + * webdav + * webdav+ssl+ + * + * The following are obsolete schemes that may be found in pre-existing + * settings from earlier versions or that may be found when imported. We + * continue to recognize them and re-map them appropriately: + * webdav+tls + * webdav+tls+ + * webdav+ssl + */ if (scheme.equals("webdav")) { connectionSecurity = ConnectionSecurity.NONE; - } else if (scheme.equals("webdav+ssl")) { - connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; - } else if (scheme.equals("webdav+ssl+")) { + } else if (scheme.startsWith("webdav+")) { connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; - } else if (scheme.equals("webdav+tls")) { - connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; - } else if (scheme.equals("webdav+tls+")) { - connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; } else { throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); } @@ -210,18 +206,9 @@ public class WebDavStore extends Store { String scheme; switch (server.connectionSecurity) { - case SSL_TLS_OPTIONAL: - scheme = "webdav+ssl"; - break; case SSL_TLS_REQUIRED: scheme = "webdav+ssl+"; break; - case STARTTLS_OPTIONAL: - scheme = "webdav+tls"; - break; - case STARTTLS_REQUIRED: - scheme = "webdav+tls+"; - break; default: case NONE: scheme = "webdav"; @@ -270,7 +257,7 @@ public class WebDavStore extends Store { public final String mailboxPath; protected WebDavStoreSettings(String host, int port, ConnectionSecurity connectionSecurity, - String authenticationType, String username, String password, String alias, + AuthType authenticationType, String username, String password, String alias, String path, String authPath, String mailboxPath) { super(STORE_TYPE, host, port, connectionSecurity, authenticationType, username, password); @@ -298,7 +285,7 @@ public class WebDavStore extends Store { } - private short mConnectionSecurity; + private ConnectionSecurity mConnectionSecurity; private String mUsername; /* Stores the username for authentications */ private String mAlias; /* Stores the alias for the user's mailbox */ private String mPassword; /* Stores the password for authentications */ @@ -334,23 +321,7 @@ public class WebDavStore extends Store { mHost = settings.host; mPort = settings.port; - switch (settings.connectionSecurity) { - case NONE: - mConnectionSecurity = CONNECTION_SECURITY_NONE; - break; - case STARTTLS_OPTIONAL: - mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL; - break; - case STARTTLS_REQUIRED: - mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED; - break; - case SSL_TLS_OPTIONAL: - mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; - break; - case SSL_TLS_REQUIRED: - mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; - break; - } + mConnectionSecurity = settings.connectionSecurity; mUsername = settings.username; mPassword = settings.password; @@ -383,16 +354,13 @@ public class WebDavStore extends Store { // The inbox path would look like: "https://mail.domain.com/Exchange/alias/Inbox". mUrl = getRoot() + mPath + mMailboxPath; - mSecure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED; + mSecure = mConnectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED; mAuthString = "Basic " + Utility.base64Encode(mUsername + ":" + mPassword); } private String getRoot() { String root; - if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED || - mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED || - mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL || - mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) { + if (mConnectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) { root = "https"; } else { root = "http"; diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java index d7478c89b..286253b5c 100644 --- a/src/com/fsck/k9/mail/transport/SmtpTransport.java +++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java @@ -19,6 +19,7 @@ import com.fsck.k9.net.ssl.TrustedSocketFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; + import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; @@ -27,44 +28,28 @@ import java.io.UnsupportedEncodingException; import java.net.*; import java.security.GeneralSecurityException; import java.security.SecureRandom; +import java.security.cert.CertificateException; import java.util.*; public class SmtpTransport extends Transport { public static final String TRANSPORT_TYPE = "SMTP"; - public static final int CONNECTION_SECURITY_NONE = 0; - public static final int CONNECTION_SECURITY_TLS_OPTIONAL = 1; - public static final int CONNECTION_SECURITY_TLS_REQUIRED = 2; - public static final int CONNECTION_SECURITY_SSL_REQUIRED = 3; - public static final int CONNECTION_SECURITY_SSL_OPTIONAL = 4; - - public static final String AUTH_PLAIN = "PLAIN"; - - public static final String AUTH_CRAM_MD5 = "CRAM_MD5"; - - public static final String AUTH_LOGIN = "LOGIN"; - - public static final String AUTH_AUTOMATIC = "AUTOMATIC"; - - /** * Decodes a SmtpTransport URI. * *

Possible forms:

*
-     * smtp://user:password@server:port CONNECTION_SECURITY_NONE
-     * smtp+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
-     * smtp+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
-     * smtp+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
-     * smtp+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
+     * smtp://user:password@server:port ConnectionSecurity.NONE
+     * smtp+tls+://user:password@server:port ConnectionSecurity.STARTTLS_REQUIRED
+     * smtp+ssl+://user:password@server:port ConnectionSecurity.SSL_TLS_REQUIRED
      * 
*/ public static ServerSettings decodeUri(String uri) { String host; int port; ConnectionSecurity connectionSecurity; - String authenticationType = AUTH_AUTOMATIC; + AuthType authType = AuthType.PLAIN; String username = null; String password = null; @@ -76,21 +61,27 @@ public class SmtpTransport extends Transport { } String scheme = smtpUri.getScheme(); + /* + * Currently available schemes are: + * smtp + * smtp+tls+ + * smtp+ssl+ + * + * The following are obsolete schemes that may be found in pre-existing + * settings from earlier versions or that may be found when imported. We + * continue to recognize them and re-map them appropriately: + * smtp+tls + * smtp+ssl + */ if (scheme.equals("smtp")) { connectionSecurity = ConnectionSecurity.NONE; port = 587; - } else if (scheme.equals("smtp+tls")) { - connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL; - port = 587; - } else if (scheme.equals("smtp+tls+")) { + } else if (scheme.startsWith("smtp+tls")) { connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED; port = 587; - } else if (scheme.equals("smtp+ssl+")) { + } else if (scheme.startsWith("smtp+ssl")) { connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED; port = 465; - } else if (scheme.equals("smtp+ssl")) { - connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL; - port = 465; } else { throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")"); } @@ -109,7 +100,7 @@ public class SmtpTransport extends Transport { password = URLDecoder.decode(userInfoParts[1], "UTF-8"); } if (userInfoParts.length > 2) { - authenticationType = userInfoParts[2]; + authType = AuthType.valueOf(userInfoParts[2]); } } catch (UnsupportedEncodingException enc) { // This shouldn't happen since the encoding is hardcoded to UTF-8 @@ -118,7 +109,7 @@ public class SmtpTransport extends Transport { } return new ServerSettings(TRANSPORT_TYPE, host, port, connectionSecurity, - authenticationType, username, password); + authType, username, password); } /** @@ -147,15 +138,9 @@ public class SmtpTransport extends Transport { String scheme; switch (server.connectionSecurity) { - case SSL_TLS_OPTIONAL: - scheme = "smtp+ssl"; - break; case SSL_TLS_REQUIRED: scheme = "smtp+ssl+"; break; - case STARTTLS_OPTIONAL: - scheme = "smtp+tls"; - break; case STARTTLS_REQUIRED: scheme = "smtp+tls+"; break; @@ -165,15 +150,11 @@ public class SmtpTransport extends Transport { break; } - String authType = server.authenticationType; - if (!(AUTH_AUTOMATIC.equals(authType) || - AUTH_LOGIN.equals(authType) || - AUTH_PLAIN.equals(authType) || - AUTH_CRAM_MD5.equals(authType))) { - throw new IllegalArgumentException("Invalid authentication type: " + authType); + String userInfo = userEnc + ":" + passwordEnc; + AuthType authType = server.authenticationType; + if (authType != null) { + userInfo += ":" + authType.name(); } - - String userInfo = userEnc + ":" + passwordEnc + ":" + authType; try { return new URI(scheme, userInfo, server.host, server.port, null, null, null).toString(); @@ -187,8 +168,8 @@ public class SmtpTransport extends Transport { int mPort; String mUsername; String mPassword; - String mAuthType; - int mConnectionSecurity; + AuthType mAuthType; + ConnectionSecurity mConnectionSecurity; Socket mSocket; PeekableInputStream mIn; OutputStream mOut; @@ -206,23 +187,7 @@ public class SmtpTransport extends Transport { mHost = settings.host; mPort = settings.port; - switch (settings.connectionSecurity) { - case NONE: - mConnectionSecurity = CONNECTION_SECURITY_NONE; - break; - case STARTTLS_OPTIONAL: - mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL; - break; - case STARTTLS_REQUIRED: - mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED; - break; - case SSL_TLS_OPTIONAL: - mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; - break; - case SSL_TLS_REQUIRED: - mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED; - break; - } + mConnectionSecurity = settings.connectionSecurity; mAuthType = settings.authenticationType; mUsername = settings.username; @@ -232,20 +197,20 @@ public class SmtpTransport extends Transport { @Override public void open() throws MessagingException { try { + boolean secureConnection = false; InetAddress[] addresses = InetAddress.getAllByName(mHost); for (int i = 0; i < addresses.length; i++) { try { SocketAddress socketAddress = new InetSocketAddress(addresses[i], mPort); - if (mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED || - mConnectionSecurity == CONNECTION_SECURITY_SSL_OPTIONAL) { + if (mConnectionSecurity == ConnectionSecurity.SSL_TLS_REQUIRED) { SSLContext sslContext = SSLContext.getInstance("TLS"); - boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED; sslContext.init(null, new TrustManager[] { TrustManagerFactory.get( - mHost, mPort, secure) }, + mHost, mPort) }, new SecureRandom()); mSocket = TrustedSocketFactory.createSocket(sslContext); mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); + secureConnection = true; } else { mSocket = new Socket(); mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); @@ -264,7 +229,7 @@ public class SmtpTransport extends Transport { mSocket.setSoTimeout(SOCKET_READ_TIMEOUT); mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), 1024)); - mOut = mSocket.getOutputStream(); + mOut = new BufferedOutputStream(mSocket.getOutputStream(), 1024); // Eat the banner executeSimpleCommand(null); @@ -288,104 +253,128 @@ public class SmtpTransport extends Transport { } } - List results = sendHello(localHost); + HashMap extensions = sendHello(localHost); - m8bitEncodingAllowed = results.contains("8BITMIME"); + m8bitEncodingAllowed = extensions.containsKey("8BITMIME"); - if (mConnectionSecurity == CONNECTION_SECURITY_TLS_OPTIONAL - || mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) { - if (results.contains("STARTTLS")) { + if (mConnectionSecurity == ConnectionSecurity.STARTTLS_REQUIRED) { + if (extensions.containsKey("STARTTLS")) { executeSimpleCommand("STARTTLS"); SSLContext sslContext = SSLContext.getInstance("TLS"); - boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED; sslContext.init(null, new TrustManager[] { TrustManagerFactory.get(mHost, - mPort, secure) }, new SecureRandom()); + mPort) }, new SecureRandom()); mSocket = TrustedSocketFactory.createSocket(sslContext, mSocket, mHost, mPort, true); mIn = new PeekableInputStream(new BufferedInputStream(mSocket.getInputStream(), 1024)); - mOut = mSocket.getOutputStream(); + mOut = new BufferedOutputStream(mSocket.getOutputStream(), 1024); /* * Now resend the EHLO. Required by RFC2487 Sec. 5.2, and more specifically, * Exim. */ - results = sendHello(localHost); - } else if (mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED) { - throw new MessagingException("TLS not supported but required"); + extensions = sendHello(localHost); + secureConnection = true; + } else { + /* + * This exception triggers a "Certificate error" + * notification that takes the user to the incoming + * server settings for review. This might be needed if + * the account was configured with an obsolete + * "STARTTLS (if available)" setting. + */ + throw new CertificateValidationException( + "STARTTLS connection security not available", + new CertificateException()); } } - boolean useAuthLogin = AUTH_LOGIN.equals(mAuthType); - boolean useAuthPlain = AUTH_PLAIN.equals(mAuthType); - boolean useAuthCramMD5 = AUTH_CRAM_MD5.equals(mAuthType); - - // Automatically choose best authentication method if none was explicitly selected - boolean useAutomaticAuth = !(useAuthLogin || useAuthPlain || useAuthCramMD5); - boolean authLoginSupported = false; boolean authPlainSupported = false; boolean authCramMD5Supported = false; - for (String result : results) { - if (result.matches(".*AUTH.*LOGIN.*$")) { - authLoginSupported = true; - } - if (result.matches(".*AUTH.*PLAIN.*$")) { - authPlainSupported = true; - } - if (result.matches(".*AUTH.*CRAM-MD5.*$")) { - authCramMD5Supported = true; - } - if (result.matches(".*SIZE \\d*$")) { - try { - mLargestAcceptableMessage = Integer.parseInt(result.substring(result.lastIndexOf(' ') + 1)); - } catch (Exception e) { - if (K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { - Log.d(K9.LOG_TAG, "Tried to parse " + result + " and get an int out of the last word", e); - } + if (extensions.containsKey("AUTH")) { + List saslMech = Arrays.asList(extensions.get("AUTH").split(" ")); + authLoginSupported = saslMech.contains("LOGIN"); + authPlainSupported = saslMech.contains("PLAIN"); + authCramMD5Supported = saslMech.contains("CRAM-MD5"); + } + if (extensions.containsKey("SIZE")) { + try { + mLargestAcceptableMessage = Integer.parseInt(extensions.get("SIZE")); + } catch (Exception e) { + if (K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { + Log.d(K9.LOG_TAG, "Tried to parse " + extensions.get("SIZE") + " and get an int", e); } } } if (mUsername != null && mUsername.length() > 0 && mPassword != null && mPassword.length() > 0) { - if (useAuthCramMD5 || (useAutomaticAuth && authCramMD5Supported)) { - if (!authCramMD5Supported && K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { - Log.d(K9.LOG_TAG, "Using CRAM_MD5 as authentication method although the " + - "server didn't advertise support for it in EHLO response."); - } - saslAuthCramMD5(mUsername, mPassword); - } else if (useAuthPlain || (useAutomaticAuth && authPlainSupported)) { - if (!authPlainSupported && K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { - Log.d(K9.LOG_TAG, "Using PLAIN as authentication method although the " + - "server didn't advertise support for it in EHLO response."); - } - try { + + switch (mAuthType) { + + /* + * LOGIN is an obsolete option which is unavailable to users, + * but it still may exist in a user's settings from a previous + * version, or it may have been imported. + */ + case LOGIN: + case PLAIN: + // try saslAuthPlain first, because it supports UTF-8 explicitly + if (authPlainSupported) { saslAuthPlain(mUsername, mPassword); - } catch (MessagingException ex) { - // PLAIN is a special case. Historically, PLAIN has represented both PLAIN and LOGIN; only the - // protocol being advertised by the server would be used, with PLAIN taking precedence. Instead - // of using only the requested protocol, we'll try PLAIN and then try LOGIN. - if (useAuthPlain && authLoginSupported) { - if (K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { - Log.d(K9.LOG_TAG, "Using legacy PLAIN authentication behavior and trying LOGIN."); - } + } else if (authLoginSupported) { + saslAuthLogin(mUsername, mPassword); + } else { + throw new MessagingException("Authentication methods SASL PLAIN and LOGIN are unavailable."); + } + break; + + case CRAM_MD5: + if (authCramMD5Supported) { + saslAuthCramMD5(mUsername, mPassword); + } else { + throw new MessagingException("Authentication method CRAM-MD5 is unavailable."); + } + break; + + /* + * AUTOMATIC is an obsolete option which is unavailable to users, + * but it still may exist in a user's settings from a previous + * version, or it may have been imported. + */ + case AUTOMATIC: + if (secureConnection) { + // try saslAuthPlain first, because it supports UTF-8 explicitly + if (authPlainSupported) { + saslAuthPlain(mUsername, mPassword); + } else if (authLoginSupported) { saslAuthLogin(mUsername, mPassword); + } else if (authCramMD5Supported) { + saslAuthCramMD5(mUsername, mPassword); } else { - // If it was auto detected and failed, continue throwing the exception back up. - throw ex; + throw new MessagingException("No supported authentication methods available."); + } + } else { + if (authCramMD5Supported) { + saslAuthCramMD5(mUsername, mPassword); + } else { + /* + * We refuse to insecurely transmit the password + * using the obsolete AUTOMATIC setting because of + * the potential for a MITM attack. Affected users + * must choose a different setting. + */ + throw new MessagingException( + "Update your outgoing server authentication setting. AUTOMATIC auth. is unavailable."); } } - } else if (useAuthLogin || (useAutomaticAuth && authLoginSupported)) { - if (!authPlainSupported && K9.DEBUG && K9.DEBUG_PROTOCOL_SMTP) { - Log.d(K9.LOG_TAG, "Using LOGIN as authentication method although the " + - "server didn't advertise support for it in EHLO response."); - } - saslAuthLogin(mUsername, mPassword); - } else { - throw new MessagingException("No valid authentication mechanism found."); + break; + + default: + throw new MessagingException("Unhandled authentication method found in the server settings (bug)."); } } } catch (SSLException e) { @@ -410,19 +399,24 @@ public class SmtpTransport extends Transport { * @param host * The EHLO/HELO parameter as defined by the RFC. * - * @return The list of capabilities as returned by the EHLO command or an empty list. + * @return A (possibly empty) {@code HashMap} of extensions (upper case) and + * their parameters (possibly 0 length) as returned by the EHLO command * * @throws IOException * In case of a network error. * @throws MessagingException * In case of a malformed response. */ - private List sendHello(String host) throws IOException, MessagingException { + private HashMap sendHello(String host) throws IOException, MessagingException { + HashMap extensions = new HashMap(); try { - //TODO: We currently assume the extension keywords returned by the server are always - // uppercased. But the RFC allows mixed-case keywords! - - return executeSimpleCommand("EHLO " + host); + List results = executeSimpleCommand("EHLO " + host); + // Remove the EHLO greeting response + results.remove(0); + for (String result : results) { + String[] pair = result.split(" ", 2); + extensions.put(pair[0].toUpperCase(Locale.US), pair.length == 1 ? "" : pair[1]); + } } catch (NegativeSmtpReplyException e) { if (K9.DEBUG) { Log.v(K9.LOG_TAG, "Server doesn't support the EHLO command. Trying HELO..."); @@ -434,8 +428,7 @@ public class SmtpTransport extends Transport { Log.w(K9.LOG_TAG, "Server doesn't support the HELO command. Continuing anyway."); } } - - return new ArrayList(0); + return extensions; } @Override @@ -500,8 +493,7 @@ public class SmtpTransport extends Transport { executeSimpleCommand("DATA"); EOLConvertingOutputStream msgOut = new EOLConvertingOutputStream( - new LineWrapOutputStream(new SmtpDataStuffing( - new BufferedOutputStream(mOut, 1024)), 1000)); + new LineWrapOutputStream(new SmtpDataStuffing(mOut), 1000)); message.writeTo(msgOut); @@ -714,7 +706,7 @@ public class SmtpTransport extends Transport { List respList = executeSimpleCommand("AUTH CRAM-MD5"); if (respList.size() != 1) { - throw new AuthenticationFailedException("Unable to negotiate CRAM-MD5"); + throw new MessagingException("Unable to negotiate CRAM-MD5"); } String b64Nonce = respList.get(0); @@ -722,8 +714,8 @@ public class SmtpTransport extends Transport { try { executeSimpleCommand(b64CRAMString, true); - } catch (MessagingException me) { - throw new AuthenticationFailedException("Unable to negotiate MD5 CRAM"); + } catch (NegativeSmtpReplyException exception) { + throw new AuthenticationFailedException(exception.getMessage(), exception); } } diff --git a/src/com/fsck/k9/mail/transport/imap/ImapSettings.java b/src/com/fsck/k9/mail/transport/imap/ImapSettings.java index 63c22f6d9..dc765d54f 100644 --- a/src/com/fsck/k9/mail/transport/imap/ImapSettings.java +++ b/src/com/fsck/k9/mail/transport/imap/ImapSettings.java @@ -1,7 +1,8 @@ package com.fsck.k9.mail.transport.imap; +import com.fsck.k9.mail.AuthType; +import com.fsck.k9.mail.ConnectionSecurity; import com.fsck.k9.mail.store.ImapStore; -import com.fsck.k9.mail.store.ImapStore.AuthType; import com.fsck.k9.mail.store.ImapStore.ImapConnection; /** @@ -12,7 +13,7 @@ public interface ImapSettings { int getPort(); - int getConnectionSecurity(); + ConnectionSecurity getConnectionSecurity(); AuthType getAuthType(); diff --git a/src/com/fsck/k9/net/ssl/TrustManagerFactory.java b/src/com/fsck/k9/net/ssl/TrustManagerFactory.java index 6b6b54138..27b2c70bb 100644 --- a/src/com/fsck/k9/net/ssl/TrustManagerFactory.java +++ b/src/com/fsck/k9/net/ssl/TrustManagerFactory.java @@ -21,23 +21,9 @@ public final class TrustManagerFactory { private static final String LOG_TAG = "TrustManagerFactory"; private static X509TrustManager defaultTrustManager; - private static X509TrustManager unsecureTrustManager; private static LocalKeyStore keyStore; - private static class SimpleX509TrustManager implements X509TrustManager { - public void checkClientTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - } - - public void checkServerTrusted(X509Certificate[] chain, String authType) - throws CertificateException { - } - - public X509Certificate[] getAcceptedIssuers() { - return null; - } - } private static class SecureX509TrustManager implements X509TrustManager { private static final Map mTrustManager = @@ -126,14 +112,12 @@ public final class TrustManagerFactory { } catch (KeyStoreException e) { Log.e(LOG_TAG, "Key Store exception while initializing TrustManagerFactory ", e); } - unsecureTrustManager = new SimpleX509TrustManager(); } private TrustManagerFactory() { } - public static X509TrustManager get(String host, int port, boolean secure) { - return secure ? SecureX509TrustManager.getInstance(host, port) : - unsecureTrustManager; + public static X509TrustManager get(String host, int port) { + return SecureX509TrustManager.getInstance(host, port); } } diff --git a/src/com/fsck/k9/preferences/SettingsExporter.java b/src/com/fsck/k9/preferences/SettingsExporter.java index 2497376aa..b09ef359d 100644 --- a/src/com/fsck/k9/preferences/SettingsExporter.java +++ b/src/com/fsck/k9/preferences/SettingsExporter.java @@ -230,7 +230,7 @@ public class SettingsExporter { writeElement(serializer, PORT_ELEMENT, Integer.toString(incoming.port)); } writeElement(serializer, CONNECTION_SECURITY_ELEMENT, incoming.connectionSecurity.name()); - writeElement(serializer, AUTHENTICATION_TYPE_ELEMENT, incoming.authenticationType); + writeElement(serializer, AUTHENTICATION_TYPE_ELEMENT, incoming.authenticationType.name()); writeElement(serializer, USERNAME_ELEMENT, incoming.username); // XXX For now we don't export the password //writeElement(serializer, PASSWORD_ELEMENT, incoming.password); @@ -257,7 +257,7 @@ public class SettingsExporter { writeElement(serializer, PORT_ELEMENT, Integer.toString(outgoing.port)); } writeElement(serializer, CONNECTION_SECURITY_ELEMENT, outgoing.connectionSecurity.name()); - writeElement(serializer, AUTHENTICATION_TYPE_ELEMENT, outgoing.authenticationType); + writeElement(serializer, AUTHENTICATION_TYPE_ELEMENT, outgoing.authenticationType.name()); writeElement(serializer, USERNAME_ELEMENT, outgoing.username); // XXX For now we don't export the password //writeElement(serializer, PASSWORD_ELEMENT, outgoing.password); diff --git a/src/com/fsck/k9/preferences/SettingsImporter.java b/src/com/fsck/k9/preferences/SettingsImporter.java index 2a774f64f..02458b919 100644 --- a/src/com/fsck/k9/preferences/SettingsImporter.java +++ b/src/com/fsck/k9/preferences/SettingsImporter.java @@ -23,6 +23,7 @@ import com.fsck.k9.Identity; import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.helper.Utility; +import com.fsck.k9.mail.AuthType; import com.fsck.k9.mail.ConnectionSecurity; import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.Store; @@ -971,7 +972,8 @@ public class SettingsImporter { } else if (SettingsExporter.CONNECTION_SECURITY_ELEMENT.equals(element)) { server.connectionSecurity = getText(xpp); } else if (SettingsExporter.AUTHENTICATION_TYPE_ELEMENT.equals(element)) { - server.authenticationType = getText(xpp); + String text = getText(xpp); + server.authenticationType = AuthType.valueOf(text); } else if (SettingsExporter.USERNAME_ELEMENT.equals(element)) { server.username = getText(xpp); } else if (SettingsExporter.PASSWORD_ELEMENT.equals(element)) { @@ -1108,6 +1110,16 @@ public class SettingsImporter { private static ConnectionSecurity convertConnectionSecurity(String connectionSecurity) { try { + /* + * TODO: + * Add proper settings validation and upgrade capability for server settings. + * Once that exists, move this code into a SettingsUpgrader. + */ + if ("SSL_TLS_OPTIONAL".equals(connectionSecurity)) { + return ConnectionSecurity.SSL_TLS_REQUIRED; + } else if ("STARTTLS_OPTIONAL".equals(connectionSecurity)) { + return ConnectionSecurity.STARTTLS_REQUIRED; + } return ConnectionSecurity.valueOf(connectionSecurity); } catch (Exception e) { return ConnectionSecurity.NONE; @@ -1140,7 +1152,7 @@ public class SettingsImporter { public String host; public String port; public String connectionSecurity; - public String authenticationType; + public AuthType authenticationType; public String username; public String password; public ImportedSettings extras; diff --git a/tests/src/com/fsck/k9/mail/store/ImapStoreUriTest.java b/tests/src/com/fsck/k9/mail/store/ImapStoreUriTest.java index 4f5f7129e..442b443ce 100644 --- a/tests/src/com/fsck/k9/mail/store/ImapStoreUriTest.java +++ b/tests/src/com/fsck/k9/mail/store/ImapStoreUriTest.java @@ -2,6 +2,8 @@ package com.fsck.k9.mail.store; import java.util.HashMap; import java.util.Map; + +import com.fsck.k9.mail.AuthType; import com.fsck.k9.mail.ConnectionSecurity; import com.fsck.k9.mail.ServerSettings; import com.fsck.k9.mail.Store; @@ -13,7 +15,7 @@ public class ImapStoreUriTest extends TestCase { String uri = "imap://PLAIN:user:pass@server:143/0%7CcustomPathPrefix"; ServerSettings settings = Store.decodeStoreUri(uri); - assertEquals("PLAIN", settings.authenticationType); + assertEquals(AuthType.PLAIN, settings.authenticationType); assertEquals("user", settings.username); assertEquals("pass", settings.password); assertEquals("server", settings.host); @@ -26,7 +28,7 @@ public class ImapStoreUriTest extends TestCase { String uri = "imap://PLAIN:user:pass@server:143/"; ServerSettings settings = Store.decodeStoreUri(uri); - assertEquals("PLAIN", settings.authenticationType); + assertEquals(AuthType.PLAIN, settings.authenticationType); assertEquals("user", settings.username); assertEquals("pass", settings.password); assertEquals("server", settings.host); @@ -38,7 +40,7 @@ public class ImapStoreUriTest extends TestCase { String uri = "imap://PLAIN:user:pass@server:143/customPathPrefix"; ServerSettings settings = Store.decodeStoreUri(uri); - assertEquals("PLAIN", settings.authenticationType); + assertEquals(AuthType.PLAIN, settings.authenticationType); assertEquals("user", settings.username); assertEquals("pass", settings.password); assertEquals("server", settings.host); @@ -51,7 +53,7 @@ public class ImapStoreUriTest extends TestCase { String uri = "imap://PLAIN:user:pass@server:143/0%7C"; ServerSettings settings = Store.decodeStoreUri(uri); - assertEquals("PLAIN", settings.authenticationType); + assertEquals(AuthType.PLAIN, settings.authenticationType); assertEquals("user", settings.username); assertEquals("pass", settings.password); assertEquals("server", settings.host); @@ -64,7 +66,7 @@ public class ImapStoreUriTest extends TestCase { String uri = "imap://PLAIN:user:pass@server:143/1%7CcustomPathPrefix"; ServerSettings settings = Store.decodeStoreUri(uri); - assertEquals("PLAIN", settings.authenticationType); + assertEquals(AuthType.PLAIN, settings.authenticationType); assertEquals("user", settings.username); assertEquals("pass", settings.password); assertEquals("server", settings.host); @@ -80,7 +82,7 @@ public class ImapStoreUriTest extends TestCase { extra.put("pathPrefix", "customPathPrefix"); ServerSettings settings = new ServerSettings(ImapStore.STORE_TYPE, "server", 143, - ConnectionSecurity.NONE, "PLAIN", "user", "pass", extra); + ConnectionSecurity.NONE, AuthType.PLAIN, "user", "pass", extra); String uri = Store.createStoreUri(settings); @@ -93,7 +95,7 @@ public class ImapStoreUriTest extends TestCase { extra.put("pathPrefix", ""); ServerSettings settings = new ServerSettings(ImapStore.STORE_TYPE, "server", 143, - ConnectionSecurity.NONE, "PLAIN", "user", "pass", extra); + ConnectionSecurity.NONE, AuthType.PLAIN, "user", "pass", extra); String uri = Store.createStoreUri(settings); @@ -102,7 +104,7 @@ public class ImapStoreUriTest extends TestCase { public void testCreateStoreUriImapNoExtra() { ServerSettings settings = new ServerSettings(ImapStore.STORE_TYPE, "server", 143, - ConnectionSecurity.NONE, "PLAIN", "user", "pass"); + ConnectionSecurity.NONE, AuthType.PLAIN, "user", "pass"); String uri = Store.createStoreUri(settings); @@ -114,7 +116,7 @@ public class ImapStoreUriTest extends TestCase { extra.put("autoDetectNamespace", "true"); ServerSettings settings = new ServerSettings(ImapStore.STORE_TYPE, "server", 143, - ConnectionSecurity.NONE, "PLAIN", "user", "pass", extra); + ConnectionSecurity.NONE, AuthType.PLAIN, "user", "pass", extra); String uri = Store.createStoreUri(settings); diff --git a/tests/src/com/fsck/k9/net/ssl/TrustManagerFactoryTest.java b/tests/src/com/fsck/k9/net/ssl/TrustManagerFactoryTest.java index 710009eee..ca33ba106 100644 --- a/tests/src/com/fsck/k9/net/ssl/TrustManagerFactoryTest.java +++ b/tests/src/com/fsck/k9/net/ssl/TrustManagerFactoryTest.java @@ -214,27 +214,27 @@ public class TrustManagerFactoryTest extends AndroidTestCase { mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT1, mCert1); mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT2, mCert2); - X509TrustManager trustManager1 = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1, true); - X509TrustManager trustManager2 = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT2, true); + X509TrustManager trustManager1 = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1); + X509TrustManager trustManager2 = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT2); trustManager2.checkServerTrusted(new X509Certificate[] { mCert2 }, "authType"); trustManager1.checkServerTrusted(new X509Certificate[] { mCert1 }, "authType"); } public void testSelfSignedCertificateMatchingHost() throws Exception { mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1); - X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true); + X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1); trustManager.checkServerTrusted(new X509Certificate[] { mCert1 }, "authType"); } public void testSelfSignedCertificateNotMatchingHost() throws Exception { mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT1, mCert1); - X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1, true); + X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1); trustManager.checkServerTrusted(new X509Certificate[] { mCert1 }, "authType"); } public void testWrongCertificate() throws Exception { mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1); - X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true); + X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1); assertCertificateRejection(trustManager, new X509Certificate[] { mCert2 }); } @@ -242,44 +242,44 @@ public class TrustManagerFactoryTest extends AndroidTestCase { mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert1); mKeyStore.addCertificate(MATCHING_HOST, PORT2, mCert2); - X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true); + X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1); assertCertificateRejection(trustManager, new X509Certificate[] { mCert2 }); } public void testUntrustedCertificateChain() throws Exception { - X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true); + X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1); assertCertificateRejection(trustManager, new X509Certificate[] { mCert3, mCaCert }); } public void testLocallyTrustedCertificateChain() throws Exception { mKeyStore.addCertificate(MATCHING_HOST, PORT1, mCert3); - X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true); + X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1); trustManager.checkServerTrusted(new X509Certificate[] { mCert3, mCaCert }, "authType"); } public void testLocallyTrustedCertificateChainNotMatchingHost() throws Exception { mKeyStore.addCertificate(NOT_MATCHING_HOST, PORT1, mCert3); - X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1, true); + X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1); trustManager.checkServerTrusted(new X509Certificate[] { mCert3, mCaCert }, "authType"); } public void testGloballyTrustedCertificateChain() throws Exception { - X509TrustManager trustManager = TrustManagerFactory.get("www.linux.com", PORT1, true); + X509TrustManager trustManager = TrustManagerFactory.get("www.linux.com", PORT1); X509Certificate[] certificates = new X509Certificate[] { mLinuxComCert, mStarfieldCert }; trustManager.checkServerTrusted(certificates, "authType"); } public void testGloballyTrustedCertificateNotMatchingHost() throws Exception { - X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1, true); + X509TrustManager trustManager = TrustManagerFactory.get(NOT_MATCHING_HOST, PORT1); assertCertificateRejection(trustManager, new X509Certificate[] { mLinuxComCert, mStarfieldCert }); } public void testGloballyTrustedCertificateNotMatchingHostOverride() throws Exception { mKeyStore.addCertificate(MATCHING_HOST, PORT1, mLinuxComCert); - X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1, true); + X509TrustManager trustManager = TrustManagerFactory.get(MATCHING_HOST, PORT1); X509Certificate[] certificates = new X509Certificate[] { mLinuxComCert, mStarfieldCert }; trustManager.checkServerTrusted(certificates, "authType"); }