From 7d642f468dd3f0fe66dcc6cd927efd89d1d17fe9 Mon Sep 17 00:00:00 2001 From: M66B Date: Tue, 23 Oct 2018 07:52:20 +0000 Subject: [PATCH] Allow insecure connections --- FAQ.md | 12 +- app/schemas/eu.faircode.email.DB/23.json | 1034 +++++++++++++++++ app/src/main/java/eu/faircode/email/DB.java | 11 +- .../java/eu/faircode/email/EntityAccount.java | 4 + .../eu/faircode/email/EntityIdentity.java | 6 +- .../eu/faircode/email/FragmentAccount.java | 57 +- .../eu/faircode/email/FragmentFolder.java | 8 +- .../eu/faircode/email/FragmentIdentity.java | 51 +- .../eu/faircode/email/FragmentOptions.java | 10 + .../java/eu/faircode/email/MessageHelper.java | 40 +- .../eu/faircode/email/ServiceSynchronize.java | 6 +- .../eu/faircode/email/ViewModelBrowse.java | 4 +- app/src/main/res/layout/fragment_account.xml | 28 +- app/src/main/res/layout/fragment_identity.xml | 11 +- app/src/main/res/layout/fragment_options.xml | 12 +- app/src/main/res/values/strings.xml | 1 + 16 files changed, 1226 insertions(+), 69 deletions(-) create mode 100644 app/schemas/eu.faircode.email.DB/23.json diff --git a/FAQ.md b/FAQ.md index 0d1b2624..88bd97f4 100644 --- a/FAQ.md +++ b/FAQ.md @@ -99,15 +99,15 @@ This IMAP extension is required to implement two way synchronization, which is n So, unless your provider can enable this extension, you cannot use FairEmail for this provider. -**(11) Why is STARTTLS for IMAP not supported?** +~~**(11) Why is STARTTLS for IMAP not supported?**~~ -STARTTLS starts with a not encrypted connection and is therefore not secure. -All well known IMAP servers support IMAP with a plain SSL connection, so there is no need to support STARTTLS for IMAP. -If you encounter an IMAP server that requires STARTTLS, please let me know. +~~STARTTLS starts with a not encrypted connection and is therefore not secure.~~ +~~All well known IMAP servers support IMAP with a plain SSL connection, so there is no need to support STARTTLS for IMAP.~~ +~~If you encounter an IMAP server that requires STARTTLS, please let me know.~~ -For more background information, please see [this article](https://www.eff.org/nl/deeplinks/2018/06/announcing-starttls-everywhere-securing-hop-hop-email-delivery). +~~For more background information, please see [this article](https://www.eff.org/nl/deeplinks/2018/06/announcing-starttls-everywhere-securing-hop-hop-email-delivery).~~ -tl;dr; "*Additionally, even if you configure STARTTLS perfectly and use a valid certificate, there’s still no guarantee your communication will be encrypted.*" +~~tl;dr; "*Additionally, even if you configure STARTTLS perfectly and use a valid certificate, there’s still no guarantee your communication will be encrypted.*"~~ **(13) How does search on server work?** diff --git a/app/schemas/eu.faircode.email.DB/23.json b/app/schemas/eu.faircode.email.DB/23.json new file mode 100644 index 00000000..f7e32c97 --- /dev/null +++ b/app/schemas/eu.faircode.email.DB/23.json @@ -0,0 +1,1034 @@ +{ + "formatVersion": 1, + "database": { + "version": 23, + "identityHash": "50f45495091ca49a0bcaa5fc9f827604", + "entities": [ + { + "tableName": "identity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `email` TEXT NOT NULL, `replyto` TEXT, `account` INTEGER NOT NULL, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `store_sent` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "email", + "columnName": "email", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "replyto", + "columnName": "replyto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "host", + "columnName": "host", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "starttls", + "columnName": "starttls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "insecure", + "columnName": "insecure", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "user", + "columnName": "user", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "auth_type", + "columnName": "auth_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "primary", + "columnName": "primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "store_sent", + "columnName": "store_sent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_identity_account", + "unique": false, + "columnNames": [ + "account" + ], + "createSql": "CREATE INDEX `index_identity_account` ON `${TABLE_NAME}` (`account`)" + } + ], + "foreignKeys": [ + { + "table": "account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "account", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `signature` TEXT, `host` TEXT NOT NULL, `starttls` INTEGER NOT NULL, `insecure` INTEGER NOT NULL, `port` INTEGER NOT NULL, `user` TEXT NOT NULL, `password` TEXT NOT NULL, `auth_type` INTEGER NOT NULL, `synchronize` INTEGER NOT NULL, `primary` INTEGER NOT NULL, `color` INTEGER, `store_sent` INTEGER NOT NULL, `poll_interval` INTEGER NOT NULL, `seen_until` INTEGER, `state` TEXT, `error` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "signature", + "columnName": "signature", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "host", + "columnName": "host", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "starttls", + "columnName": "starttls", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "insecure", + "columnName": "insecure", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "port", + "columnName": "port", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "user", + "columnName": "user", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "auth_type", + "columnName": "auth_type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "primary", + "columnName": "primary", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "store_sent", + "columnName": "store_sent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "poll_interval", + "columnName": "poll_interval", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "seen_until", + "columnName": "seen_until", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "folder", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `synchronize` INTEGER NOT NULL, `poll_interval` INTEGER, `after` INTEGER NOT NULL, `display` TEXT, `hide` INTEGER NOT NULL, `unified` INTEGER NOT NULL, `state` TEXT, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "synchronize", + "columnName": "synchronize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "poll_interval", + "columnName": "poll_interval", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "after", + "columnName": "after", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "display", + "columnName": "display", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "hide", + "columnName": "hide", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "unified", + "columnName": "unified", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_folder_account_name", + "unique": true, + "columnNames": [ + "account", + "name" + ], + "createSql": "CREATE UNIQUE INDEX `index_folder_account_name` ON `${TABLE_NAME}` (`account`, `name`)" + }, + { + "name": "index_folder_account", + "unique": false, + "columnNames": [ + "account" + ], + "createSql": "CREATE INDEX `index_folder_account` ON `${TABLE_NAME}` (`account`)" + }, + { + "name": "index_folder_name", + "unique": false, + "columnNames": [ + "name" + ], + "createSql": "CREATE INDEX `index_folder_name` ON `${TABLE_NAME}` (`name`)" + }, + { + "name": "index_folder_type", + "unique": false, + "columnNames": [ + "type" + ], + "createSql": "CREATE INDEX `index_folder_type` ON `${TABLE_NAME}` (`type`)" + }, + { + "name": "index_folder_unified", + "unique": false, + "columnNames": [ + "unified" + ], + "createSql": "CREATE INDEX `index_folder_unified` ON `${TABLE_NAME}` (`unified`)" + } + ], + "foreignKeys": [ + { + "table": "account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "message", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `account` INTEGER, `folder` INTEGER NOT NULL, `identity` INTEGER, `replying` INTEGER, `uid` INTEGER, `msgid` TEXT, `references` TEXT, `deliveredto` TEXT, `inreplyto` TEXT, `thread` TEXT, `avatar` TEXT, `from` TEXT, `to` TEXT, `cc` TEXT, `bcc` TEXT, `reply` TEXT, `headers` TEXT, `subject` TEXT, `size` INTEGER, `content` INTEGER NOT NULL, `sent` INTEGER, `received` INTEGER NOT NULL, `stored` INTEGER NOT NULL, `seen` INTEGER NOT NULL, `flagged` INTEGER NOT NULL, `ui_seen` INTEGER NOT NULL, `ui_flagged` INTEGER NOT NULL, `ui_hide` INTEGER NOT NULL, `ui_found` INTEGER NOT NULL, `ui_ignored` INTEGER NOT NULL, `error` TEXT, FOREIGN KEY(`account`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`identity`) REFERENCES `identity`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`replying`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "account", + "columnName": "account", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folder", + "columnName": "folder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "identity", + "columnName": "identity", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "replying", + "columnName": "replying", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "uid", + "columnName": "uid", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "msgid", + "columnName": "msgid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "references", + "columnName": "references", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deliveredto", + "columnName": "deliveredto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "inreplyto", + "columnName": "inreplyto", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "thread", + "columnName": "thread", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "avatar", + "columnName": "avatar", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "from", + "columnName": "from", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "to", + "columnName": "to", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cc", + "columnName": "cc", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "bcc", + "columnName": "bcc", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "reply", + "columnName": "reply", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "headers", + "columnName": "headers", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "content", + "columnName": "content", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sent", + "columnName": "sent", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "received", + "columnName": "received", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "stored", + "columnName": "stored", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "seen", + "columnName": "seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "flagged", + "columnName": "flagged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_seen", + "columnName": "ui_seen", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_flagged", + "columnName": "ui_flagged", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_hide", + "columnName": "ui_hide", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_found", + "columnName": "ui_found", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ui_ignored", + "columnName": "ui_ignored", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "error", + "columnName": "error", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_message_account", + "unique": false, + "columnNames": [ + "account" + ], + "createSql": "CREATE INDEX `index_message_account` ON `${TABLE_NAME}` (`account`)" + }, + { + "name": "index_message_folder", + "unique": false, + "columnNames": [ + "folder" + ], + "createSql": "CREATE INDEX `index_message_folder` ON `${TABLE_NAME}` (`folder`)" + }, + { + "name": "index_message_identity", + "unique": false, + "columnNames": [ + "identity" + ], + "createSql": "CREATE INDEX `index_message_identity` ON `${TABLE_NAME}` (`identity`)" + }, + { + "name": "index_message_replying", + "unique": false, + "columnNames": [ + "replying" + ], + "createSql": "CREATE INDEX `index_message_replying` ON `${TABLE_NAME}` (`replying`)" + }, + { + "name": "index_message_folder_uid_ui_found", + "unique": true, + "columnNames": [ + "folder", + "uid", + "ui_found" + ], + "createSql": "CREATE UNIQUE INDEX `index_message_folder_uid_ui_found` ON `${TABLE_NAME}` (`folder`, `uid`, `ui_found`)" + }, + { + "name": "index_message_msgid_folder_ui_found", + "unique": true, + "columnNames": [ + "msgid", + "folder", + "ui_found" + ], + "createSql": "CREATE UNIQUE INDEX `index_message_msgid_folder_ui_found` ON `${TABLE_NAME}` (`msgid`, `folder`, `ui_found`)" + }, + { + "name": "index_message_thread", + "unique": false, + "columnNames": [ + "thread" + ], + "createSql": "CREATE INDEX `index_message_thread` ON `${TABLE_NAME}` (`thread`)" + }, + { + "name": "index_message_received", + "unique": false, + "columnNames": [ + "received" + ], + "createSql": "CREATE INDEX `index_message_received` ON `${TABLE_NAME}` (`received`)" + }, + { + "name": "index_message_ui_seen", + "unique": false, + "columnNames": [ + "ui_seen" + ], + "createSql": "CREATE INDEX `index_message_ui_seen` ON `${TABLE_NAME}` (`ui_seen`)" + }, + { + "name": "index_message_ui_hide", + "unique": false, + "columnNames": [ + "ui_hide" + ], + "createSql": "CREATE INDEX `index_message_ui_hide` ON `${TABLE_NAME}` (`ui_hide`)" + }, + { + "name": "index_message_ui_found", + "unique": false, + "columnNames": [ + "ui_found" + ], + "createSql": "CREATE INDEX `index_message_ui_found` ON `${TABLE_NAME}` (`ui_found`)" + }, + { + "name": "index_message_ui_ignored", + "unique": false, + "columnNames": [ + "ui_ignored" + ], + "createSql": "CREATE INDEX `index_message_ui_ignored` ON `${TABLE_NAME}` (`ui_ignored`)" + } + ], + "foreignKeys": [ + { + "table": "account", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "account" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "folder", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "folder" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "identity", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "identity" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "message", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "replying" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "attachment", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` INTEGER NOT NULL, `sequence` INTEGER NOT NULL, `name` TEXT, `type` TEXT NOT NULL, `cid` TEXT, `size` INTEGER, `progress` INTEGER, `available` INTEGER NOT NULL, FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sequence", + "columnName": "sequence", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "cid", + "columnName": "cid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "size", + "columnName": "size", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "progress", + "columnName": "progress", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "available", + "columnName": "available", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_attachment_message", + "unique": false, + "columnNames": [ + "message" + ], + "createSql": "CREATE INDEX `index_attachment_message` ON `${TABLE_NAME}` (`message`)" + }, + { + "name": "index_attachment_message_sequence", + "unique": true, + "columnNames": [ + "message", + "sequence" + ], + "createSql": "CREATE UNIQUE INDEX `index_attachment_message_sequence` ON `${TABLE_NAME}` (`message`, `sequence`)" + }, + { + "name": "index_attachment_message_cid", + "unique": true, + "columnNames": [ + "message", + "cid" + ], + "createSql": "CREATE UNIQUE INDEX `index_attachment_message_cid` ON `${TABLE_NAME}` (`message`, `cid`)" + } + ], + "foreignKeys": [ + { + "table": "message", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "message" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "operation", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `folder` INTEGER NOT NULL, `message` INTEGER NOT NULL, `name` TEXT NOT NULL, `args` TEXT NOT NULL, `created` INTEGER NOT NULL, FOREIGN KEY(`folder`) REFERENCES `folder`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`message`) REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "folder", + "columnName": "folder", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "args", + "columnName": "args", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_operation_folder", + "unique": false, + "columnNames": [ + "folder" + ], + "createSql": "CREATE INDEX `index_operation_folder` ON `${TABLE_NAME}` (`folder`)" + }, + { + "name": "index_operation_message", + "unique": false, + "columnNames": [ + "message" + ], + "createSql": "CREATE INDEX `index_operation_message` ON `${TABLE_NAME}` (`message`)" + } + ], + "foreignKeys": [ + { + "table": "folder", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "folder" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "message", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "message" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "answer", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `text` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "text", + "columnName": "text", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "log", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `time` INTEGER NOT NULL, `data` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "data", + "columnName": "data", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [ + { + "name": "index_log_time", + "unique": false, + "columnNames": [ + "time" + ], + "createSql": "CREATE INDEX `index_log_time` ON `${TABLE_NAME}` (`time`)" + } + ], + "foreignKeys": [] + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"50f45495091ca49a0bcaa5fc9f827604\")" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/faircode/email/DB.java b/app/src/main/java/eu/faircode/email/DB.java index 66523fe5..f5998a62 100644 --- a/app/src/main/java/eu/faircode/email/DB.java +++ b/app/src/main/java/eu/faircode/email/DB.java @@ -45,7 +45,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase; // https://developer.android.com/topic/libraries/architecture/room.html @Database( - version = 22, + version = 23, entities = { EntityIdentity.class, EntityAccount.class, @@ -275,6 +275,15 @@ public abstract class DB extends RoomDatabase { db.execSQL("CREATE UNIQUE INDEX `index_message_msgid_folder_ui_found` ON `message` (`msgid`, `folder`, `ui_found`)"); } }) + .addMigrations(new Migration(22, 23) { + @Override + public void migrate(SupportSQLiteDatabase db) { + Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion); + db.execSQL("ALTER TABLE `account` ADD COLUMN `starttls` INTEGER NOT NULL DEFAULT 0"); + db.execSQL("ALTER TABLE `account` ADD COLUMN `insecure` INTEGER NOT NULL DEFAULT 0"); + db.execSQL("ALTER TABLE `identity` ADD COLUMN `insecure` INTEGER NOT NULL DEFAULT 0"); + } + }) .build(); } diff --git a/app/src/main/java/eu/faircode/email/EntityAccount.java b/app/src/main/java/eu/faircode/email/EntityAccount.java index 9f680d08..4e0881f9 100644 --- a/app/src/main/java/eu/faircode/email/EntityAccount.java +++ b/app/src/main/java/eu/faircode/email/EntityAccount.java @@ -41,6 +41,10 @@ public class EntityAccount { @NonNull public String host; // IMAP @NonNull + public Boolean starttls; + @NonNull + public Boolean insecure; + @NonNull public Integer port; @NonNull public String user; diff --git a/app/src/main/java/eu/faircode/email/EntityIdentity.java b/app/src/main/java/eu/faircode/email/EntityIdentity.java index 8260b49e..60ebc76b 100644 --- a/app/src/main/java/eu/faircode/email/EntityIdentity.java +++ b/app/src/main/java/eu/faircode/email/EntityIdentity.java @@ -54,10 +54,12 @@ public class EntityIdentity { @NonNull public String host; // SMTP @NonNull - public Integer port; - @NonNull public Boolean starttls; @NonNull + public Boolean insecure; + @NonNull + public Integer port; + @NonNull public String user; @NonNull public String password; diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java index 33104fc9..2f20766c 100644 --- a/app/src/main/java/eu/faircode/email/FragmentAccount.java +++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java @@ -28,12 +28,14 @@ import android.app.Activity; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.preference.PreferenceManager; import android.text.Editable; import android.text.Html; import android.text.TextUtils; @@ -93,14 +95,19 @@ public class FragmentAccount extends FragmentEx { private ViewGroup view; private Spinner spProvider; - private EditText etHost; + private EditText etDomain; private Button btnAutoConfig; + + private EditText etHost; + private CheckBox cbStartTls; + private CheckBox cbInsecure; private EditText etPort; - private Button btnAuthorize; private EditText etUser; private TextInputLayout tilPassword; + private Button btnAuthorize; + private Button btnAdvanced; private TextView tvName; @@ -155,6 +162,9 @@ public class FragmentAccount extends FragmentEx { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { setSubtitle(R.string.title_edit_account); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + final boolean insecure = prefs.getBoolean("insecure", false); + view = (ViewGroup) inflater.inflate(R.layout.fragment_account, container, false); // Get controls @@ -165,11 +175,13 @@ public class FragmentAccount extends FragmentEx { etHost = view.findViewById(R.id.etHost); etPort = view.findViewById(R.id.etPort); - - btnAuthorize = view.findViewById(R.id.btnAuthorize); + cbStartTls = view.findViewById(R.id.cbStartTls); + cbInsecure = view.findViewById(R.id.cbInsecure); etUser = view.findViewById(R.id.etUser); tilPassword = view.findViewById(R.id.tilPassword); + btnAuthorize = view.findViewById(R.id.btnAuthorize); + btnAdvanced = view.findViewById(R.id.btnAdvanced); etName = view.findViewById(R.id.etName); @@ -214,6 +226,8 @@ public class FragmentAccount extends FragmentEx { public void onItemSelected(AdapterView adapterView, View view, int position, long id) { Provider provider = (Provider) adapterView.getSelectedItem(); grpServer.setVisibility(position == 1 ? View.VISIBLE : View.GONE); + cbStartTls.setVisibility(position == 1 && insecure ? View.VISIBLE : View.GONE); + cbInsecure.setVisibility(position == 1 && insecure ? View.VISIBLE : View.GONE); grpAuthorize.setVisibility(position > 0 ? View.VISIBLE : View.GONE); btnAuthorize.setVisibility(provider.type == null ? View.GONE : View.VISIBLE); @@ -293,6 +307,13 @@ public class FragmentAccount extends FragmentEx { } }); + cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + etPort.setHint(checked ? "143" : "993"); + } + }); + tilPassword.getEditText().addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -401,6 +422,8 @@ public class FragmentAccount extends FragmentEx { Bundle args = new Bundle(); args.putLong("id", id); args.putString("host", etHost.getText().toString()); + args.putBoolean("starttls", cbStartTls.isChecked()); + args.putBoolean("insecure", cbInsecure.isChecked()); args.putString("port", etPort.getText().toString()); args.putString("user", etUser.getText().toString()); args.putString("password", tilPassword.getEditText().getText().toString()); @@ -411,6 +434,8 @@ public class FragmentAccount extends FragmentEx { protected CheckResult onLoad(Context context, Bundle args) throws Throwable { long id = args.getLong("id"); String host = args.getString("host"); + boolean starttls = args.getBoolean("starttls"); + boolean insecure = args.getBoolean("insecure"); String port = args.getString("port"); String user = args.getString("user"); String password = args.getString("password"); @@ -419,22 +444,22 @@ public class FragmentAccount extends FragmentEx { if (TextUtils.isEmpty(host)) throw new Throwable(getContext().getString(R.string.title_no_host)); if (TextUtils.isEmpty(port)) - port = "993"; + port = (starttls ? "143" : "993"); if (TextUtils.isEmpty(user)) throw new Throwable(getContext().getString(R.string.title_no_user)); - if (TextUtils.isEmpty(password)) + if (TextUtils.isEmpty(password) && !insecure) throw new Throwable(getContext().getString(R.string.title_no_password)); CheckResult result = new CheckResult(); result.folders = new ArrayList<>(); // Check IMAP server / get folders - Properties props = MessageHelper.getSessionProperties(auth_type); + Properties props = MessageHelper.getSessionProperties(auth_type, insecure); Session isession = Session.getInstance(props, null); isession.setDebug(true); IMAPStore istore = null; try { - istore = (IMAPStore) isession.getStore("imaps"); + istore = (IMAPStore) isession.getStore(starttls ? "imap" : "imaps"); try { istore.connect(host, Integer.parseInt(port), user, password); } catch (AuthenticationFailedException ex) { @@ -574,6 +599,8 @@ public class FragmentAccount extends FragmentEx { Bundle args = new Bundle(); args.putLong("id", id); args.putString("host", etHost.getText().toString()); + args.putBoolean("starttls", cbStartTls.isChecked()); + args.putBoolean("insecure", cbInsecure.isChecked()); args.putString("port", etPort.getText().toString()); args.putString("user", etUser.getText().toString()); args.putString("password", tilPassword.getEditText().getText().toString()); @@ -597,6 +624,8 @@ public class FragmentAccount extends FragmentEx { @Override protected Void onLoad(Context context, Bundle args) throws Throwable { String host = args.getString("host"); + boolean starttls = args.getBoolean("starttls"); + boolean insecure = args.getBoolean("insecure"); String port = args.getString("port"); String user = args.getString("user"); String password = args.getString("password"); @@ -619,10 +648,10 @@ public class FragmentAccount extends FragmentEx { if (TextUtils.isEmpty(host)) throw new Throwable(getContext().getString(R.string.title_no_host)); if (TextUtils.isEmpty(port)) - port = "993"; + port = (starttls ? "143" : "993"); if (TextUtils.isEmpty(user)) throw new Throwable(getContext().getString(R.string.title_no_user)); - if (TextUtils.isEmpty(password)) + if (TextUtils.isEmpty(password) && !insecure) throw new Throwable(getContext().getString(R.string.title_no_password)); if (TextUtils.isEmpty(interval)) interval = "9"; @@ -633,11 +662,11 @@ public class FragmentAccount extends FragmentEx { // Check IMAP server if (synchronize) { - Session isession = Session.getInstance(MessageHelper.getSessionProperties(auth_type), null); + Session isession = Session.getInstance(MessageHelper.getSessionProperties(auth_type, insecure), null); isession.setDebug(true); IMAPStore istore = null; try { - istore = (IMAPStore) isession.getStore("imaps"); + istore = (IMAPStore) isession.getStore(starttls ? "imap" : "imaps"); try { istore.connect(host, Integer.parseInt(port), user, password); } catch (AuthenticationFailedException ex) { @@ -669,6 +698,8 @@ public class FragmentAccount extends FragmentEx { account = new EntityAccount(); account.host = host; + account.starttls = starttls; + account.insecure = insecure; account.port = Integer.parseInt(port); account.user = user; account.password = password; @@ -828,6 +859,8 @@ public class FragmentAccount extends FragmentEx { // Initialize Helper.setViewsEnabled(view, false); btnAuthorize.setVisibility(View.GONE); + cbStartTls.setVisibility(View.GONE); + cbInsecure.setVisibility(View.GONE); tilPassword.setPasswordVisibilityToggleEnabled(id < 0); btnAdvanced.setVisibility(View.GONE); diff --git a/app/src/main/java/eu/faircode/email/FragmentFolder.java b/app/src/main/java/eu/faircode/email/FragmentFolder.java index 7c7e1d64..c39f895f 100644 --- a/app/src/main/java/eu/faircode/email/FragmentFolder.java +++ b/app/src/main/java/eu/faircode/email/FragmentFolder.java @@ -135,9 +135,9 @@ public class FragmentFolder extends FragmentEx { if (folder == null || !folder.name.equals(name)) { EntityAccount account = db.account().getAccount(folder == null ? aid : folder.account); - Properties props = MessageHelper.getSessionProperties(account.auth_type); + Properties props = MessageHelper.getSessionProperties(account.auth_type, account.insecure); Session isession = Session.getInstance(props, null); - istore = (IMAPStore) isession.getStore("imaps"); + istore = (IMAPStore) isession.getStore(account.starttls ? "imap" : "imaps"); Helper.connect(context, istore, account); if (folder == null) { @@ -239,9 +239,9 @@ public class FragmentFolder extends FragmentEx { EntityFolder folder = db.folder().getFolder(id); EntityAccount account = db.account().getAccount(folder.account); - Properties props = MessageHelper.getSessionProperties(account.auth_type); + Properties props = MessageHelper.getSessionProperties(account.auth_type, account.insecure); Session isession = Session.getInstance(props, null); - istore = (IMAPStore) isession.getStore("imaps"); + istore = (IMAPStore) isession.getStore(account.starttls ? "imap" : "imaps"); Helper.connect(context, istore, account); IMAPFolder ifolder = (IMAPFolder) istore.getFolder(folder.name); diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java index e7906ea4..49ae40b3 100644 --- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java +++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java @@ -21,8 +21,10 @@ package eu.faircode.email; import android.content.Context; import android.content.DialogInterface; +import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; +import android.preference.PreferenceManager; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; @@ -74,6 +76,7 @@ public class FragmentIdentity extends FragmentEx { private Button btnAutoConfig; private EditText etHost; private CheckBox cbStartTls; + private CheckBox cbInsecure; private EditText etPort; private EditText etUser; private TextInputLayout tilPassword; @@ -102,6 +105,9 @@ public class FragmentIdentity extends FragmentEx { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { setSubtitle(R.string.title_edit_identity); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); + final boolean insecure = prefs.getBoolean("insecure", false); + view = (ViewGroup) inflater.inflate(R.layout.fragment_identity, container, false); // Get controls @@ -120,8 +126,8 @@ public class FragmentIdentity extends FragmentEx { etHost = view.findViewById(R.id.etHost); cbStartTls = view.findViewById(R.id.cbStartTls); + cbInsecure = view.findViewById(R.id.cbInsecure); etPort = view.findViewById(R.id.etPort); - etUser = view.findViewById(R.id.etUser); tilPassword = view.findViewById(R.id.tilPassword); @@ -191,21 +197,6 @@ public class FragmentIdentity extends FragmentEx { } }); - btnAdvanced.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - int visibility = (grpAdvanced.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE); - grpAdvanced.setVisibility(visibility); - if (visibility == View.VISIBLE) - new Handler().post(new Runnable() { - @Override - public void run() { - ((ScrollView) view).smoothScrollTo(0, tvEmail.getTop()); - } - }); - } - }); - spProvider.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView adapterView, View view, int position, long id) { @@ -275,6 +266,22 @@ public class FragmentIdentity extends FragmentEx { } }); + btnAdvanced.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int visibility = (grpAdvanced.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE); + grpAdvanced.setVisibility(visibility); + cbInsecure.setVisibility(insecure ? visibility : View.GONE); + if (visibility == View.VISIBLE) + new Handler().post(new Runnable() { + @Override + public void run() { + ((ScrollView) view).smoothScrollTo(0, tvEmail.getTop()); + } + }); + } + }); + cbStartTls.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { @@ -307,6 +314,7 @@ public class FragmentIdentity extends FragmentEx { args.putInt("auth_type", account == null || account.auth_type == null ? Helper.AUTH_TYPE_PASSWORD : account.auth_type); args.putString("host", etHost.getText().toString()); args.putBoolean("starttls", cbStartTls.isChecked()); + args.putBoolean("insecure", cbInsecure.isChecked()); args.putString("port", etPort.getText().toString()); args.putString("user", etUser.getText().toString()); args.putString("password", tilPassword.getEditText().getText().toString()); @@ -324,6 +332,7 @@ public class FragmentIdentity extends FragmentEx { String replyto = args.getString("replyto"); String host = args.getString("host"); boolean starttls = args.getBoolean("starttls"); + boolean insecure = args.getBoolean("insecure"); String port = args.getString("port"); String user = args.getString("user"); String password = args.getString("password"); @@ -339,10 +348,10 @@ public class FragmentIdentity extends FragmentEx { if (TextUtils.isEmpty(host)) throw new IllegalArgumentException(getContext().getString(R.string.title_no_host)); if (TextUtils.isEmpty(port)) - port = "465"; + port = (starttls ? "587" : "465"); if (TextUtils.isEmpty(user)) throw new IllegalArgumentException(getContext().getString(R.string.title_no_user)); - if (TextUtils.isEmpty(password)) + if (TextUtils.isEmpty(password) && !insecure) throw new IllegalArgumentException(getContext().getString(R.string.title_no_password)); if (TextUtils.isEmpty(replyto)) @@ -350,7 +359,7 @@ public class FragmentIdentity extends FragmentEx { // Check SMTP server if (synchronize) { - Properties props = MessageHelper.getSessionProperties(auth_type); + Properties props = MessageHelper.getSessionProperties(auth_type, insecure); Session isession = Session.getInstance(props, null); isession.setDebug(true); Transport itransport = isession.getTransport(starttls ? "smtp" : "smtps"); @@ -382,8 +391,9 @@ public class FragmentIdentity extends FragmentEx { identity.email = email; identity.replyto = replyto; identity.host = host; - identity.port = Integer.parseInt(port); identity.starttls = starttls; + identity.insecure = insecure; + identity.port = Integer.parseInt(port); identity.user = user; identity.password = password; identity.auth_type = auth_type; @@ -476,6 +486,7 @@ public class FragmentIdentity extends FragmentEx { // Initialize Helper.setViewsEnabled(view, false); + cbInsecure.setVisibility(View.GONE); tilPassword.setPasswordVisibilityToggleEnabled(id < 0); btnSave.setVisibility(View.GONE); btnAdvanced.setVisibility(View.GONE); diff --git a/app/src/main/java/eu/faircode/email/FragmentOptions.java b/app/src/main/java/eu/faircode/email/FragmentOptions.java index eec2b512..8e53dfa6 100644 --- a/app/src/main/java/eu/faircode/email/FragmentOptions.java +++ b/app/src/main/java/eu/faircode/email/FragmentOptions.java @@ -38,6 +38,7 @@ public class FragmentOptions extends FragmentEx { private CheckBox cbBrowse; private CheckBox cbSwipe; private CheckBox cbCompact; + private CheckBox cbInsecure; private CheckBox cbDebug; @Override @@ -54,6 +55,7 @@ public class FragmentOptions extends FragmentEx { cbBrowse = view.findViewById(R.id.cbBrowse); cbSwipe = view.findViewById(R.id.cbSwipe); cbCompact = view.findViewById(R.id.cbCompact); + cbInsecure = view.findViewById(R.id.cbInsecure); cbDebug = view.findViewById(R.id.cbDebug); // Wire controls @@ -112,6 +114,14 @@ public class FragmentOptions extends FragmentEx { } }); + cbInsecure.setChecked(prefs.getBoolean("insecure", false)); + cbInsecure.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton compoundButton, boolean checked) { + prefs.edit().putBoolean("insecure", checked).apply(); + } + }); + cbDebug.setChecked(prefs.getBoolean("debug", false)); cbDebug.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override diff --git a/app/src/main/java/eu/faircode/email/MessageHelper.java b/app/src/main/java/eu/faircode/email/MessageHelper.java index be6c0133..9105a31f 100644 --- a/app/src/main/java/eu/faircode/email/MessageHelper.java +++ b/app/src/main/java/eu/faircode/email/MessageHelper.java @@ -63,11 +63,13 @@ public class MessageHelper { final static int NETWORK_TIMEOUT = 60 * 1000; // milliseconds - static Properties getSessionProperties(int auth_type) { + static Properties getSessionProperties(int auth_type, boolean insecure) { Properties props = new Properties(); + String checkserveridentity = Boolean.toString(!insecure).toLowerCase(); + // https://javaee.github.io/javamail/docs/api/com/sun/mail/imap/package-summary.html#properties - props.put("mail.imaps.ssl.checkserveridentity", "true"); + props.put("mail.imaps.ssl.checkserveridentity", checkserveridentity); props.put("mail.imaps.ssl.trust", "*"); props.put("mail.imaps.starttls.enable", "false"); @@ -79,22 +81,35 @@ public class MessageHelper { props.put("mail.imaps.connectionpool.debug", "true"); props.put("mail.imaps.connectionpooltimeout", Integer.toString(3 * 60 * 1000)); // default: 45 sec - // "mail.imaps.finalizecleanclose" - // https://tools.ietf.org/html/rfc4978 // https://docs.oracle.com/javase/8/docs/api/java/util/zip/Deflater.html - if (true) { - Log.i(Helper.TAG, "IMAP compress enabled"); - props.put("mail.imaps.compress.enable", "true"); - //props.put("mail.imaps.compress.level", "-1"); - //props.put("mail.imaps.compress.strategy", "0"); - } + props.put("mail.imaps.compress.enable", "true"); + //props.put("mail.imaps.compress.level", "-1"); + //props.put("mail.imaps.compress.strategy", "0"); props.put("mail.imaps.fetchsize", Integer.toString(48 * 1024)); // default 16K props.put("mail.imaps.peek", "true"); + props.put("mail.imap.ssl.checkserveridentity", checkserveridentity); + props.put("mail.imap.ssl.trust", "*"); + props.put("mail.imap.starttls.enable", "true"); + props.put("mail.imap.starttls.required", "true"); + props.put("mail.imap.auth", "true"); + + props.put("mail.imap.connectiontimeout", Integer.toString(NETWORK_TIMEOUT)); + props.put("mail.imap.timeout", Integer.toString(NETWORK_TIMEOUT)); + props.put("mail.imap.writetimeout", Integer.toString(NETWORK_TIMEOUT)); // one thread overhead + + props.put("mail.imap.connectionpool.debug", "true"); + props.put("mail.imap.connectionpooltimeout", Integer.toString(3 * 60 * 1000)); // default: 45 sec + + props.put("mail.imap.compress.enable", "true"); + + props.put("mail.imap.fetchsize", Integer.toString(48 * 1024)); // default 16K + props.put("mail.imap.peek", "true"); + // https://javaee.github.io/javamail/docs/api/com/sun/mail/smtp/package-summary.html#properties - props.put("mail.smtps.ssl.checkserveridentity", "true"); + props.put("mail.smtps.ssl.checkserveridentity", checkserveridentity); props.put("mail.smtps.ssl.trust", "*"); props.put("mail.smtps.starttls.enable", "false"); props.put("mail.smtps.starttls.required", "false"); @@ -104,7 +119,7 @@ public class MessageHelper { props.put("mail.smtps.writetimeout", Integer.toString(NETWORK_TIMEOUT)); // one thread overhead props.put("mail.smtps.timeout", Integer.toString(NETWORK_TIMEOUT)); - props.put("mail.smtp.ssl.checkserveridentity", "true"); + props.put("mail.smtp.ssl.checkserveridentity", checkserveridentity); props.put("mail.smtp.ssl.trust", "*"); props.put("mail.smtp.starttls.enable", "true"); props.put("mail.smtp.starttls.required", "true"); @@ -145,6 +160,7 @@ public class MessageHelper { Log.i(Helper.TAG, "Auth type=" + auth_type); if (auth_type == Helper.AUTH_TYPE_GMAIL) { props.put("mail.imaps.auth.mechanisms", "XOAUTH2"); + props.put("mail.imap.auth.mechanisms", "XOAUTH2"); props.put("mail.smtps.auth.mechanisms", "XOAUTH2"); props.put("mail.smtp.auth.mechanisms", "XOAUTH2"); } diff --git a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java index 2ba4cc0b..647c6be8 100644 --- a/app/src/main/java/eu/faircode/email/ServiceSynchronize.java +++ b/app/src/main/java/eu/faircode/email/ServiceSynchronize.java @@ -574,12 +574,12 @@ public class ServiceSynchronize extends LifecycleService { System.setProperty("mail.socket.debug", Boolean.toString(debug)); // Create session - Properties props = MessageHelper.getSessionProperties(account.auth_type); + Properties props = MessageHelper.getSessionProperties(account.auth_type, account.insecure); final Session isession = Session.getInstance(props, null); isession.setDebug(debug); // adb -t 1 logcat | grep "fairemail\|System.out" - final IMAPStore istore = (IMAPStore) isession.getStore("imaps"); + final IMAPStore istore = (IMAPStore) isession.getStore(account.starttls ? "imap" : "imaps"); final Map folders = new HashMap<>(); List syncs = new ArrayList<>(); List idlers = new ArrayList<>(); @@ -1331,7 +1331,7 @@ public class ServiceSynchronize extends LifecycleService { } // Create session - Properties props = MessageHelper.getSessionProperties(ident.auth_type); + Properties props = MessageHelper.getSessionProperties(ident.auth_type, ident.insecure); final Session isession = Session.getInstance(props, null); // Create message diff --git a/app/src/main/java/eu/faircode/email/ViewModelBrowse.java b/app/src/main/java/eu/faircode/email/ViewModelBrowse.java index c0673230..b18d8610 100644 --- a/app/src/main/java/eu/faircode/email/ViewModelBrowse.java +++ b/app/src/main/java/eu/faircode/email/ViewModelBrowse.java @@ -68,12 +68,12 @@ public class ViewModelBrowse extends ViewModel { EntityAccount account = db.account().getAccount(folder.account); if (imessages == null) { - Properties props = MessageHelper.getSessionProperties(account.auth_type); + Properties props = MessageHelper.getSessionProperties(account.auth_type, account.insecure); props.setProperty("mail.imap.throwsearchexception", "true"); Session isession = Session.getInstance(props, null); Log.i(Helper.TAG, "Boundary connecting account=" + account.name); - istore = (IMAPStore) isession.getStore("imaps"); + istore = (IMAPStore) isession.getStore(account.starttls ? "imap" : "imaps"); Helper.connect(context, istore, account); Log.i(Helper.TAG, "Boundary opening folder=" + folder.name); diff --git a/app/src/main/res/layout/fragment_account.xml b/app/src/main/res/layout/fragment_account.xml index e9b501f4..c95e3733 100644 --- a/app/src/main/res/layout/fragment_account.xml +++ b/app/src/main/res/layout/fragment_account.xml @@ -58,8 +58,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="12dp" - android:minHeight="0dp" android:minWidth="0dp" + android:minHeight="0dp" android:text="@string/title_autoconfig" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/etDomain" /> @@ -118,6 +118,24 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tvHost" /> + + + + + app:layout_constraintTop_toBottomOf="@id/cbInsecure" /> @@ -207,8 +225,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="12dp" - android:minHeight="0dp" android:minWidth="0dp" + android:minHeight="0dp" android:text="@string/title_setup_advanced" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/tilPassword" /> @@ -241,8 +259,8 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="12dp" - android:minHeight="0dp" android:minWidth="0dp" + android:minHeight="0dp" android:text="@string/title_account_color" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/etName" /> diff --git a/app/src/main/res/layout/fragment_identity.xml b/app/src/main/res/layout/fragment_identity.xml index 334cace4..9643c514 100644 --- a/app/src/main/res/layout/fragment_identity.xml +++ b/app/src/main/res/layout/fragment_identity.xml @@ -214,6 +214,15 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/etHost" /> + + + app:layout_constraintTop_toBottomOf="@id/cbInsecure" /> + + + app:layout_constraintTop_toBottomOf="@id/cbInsecure" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 17634e9b..b7d7d680 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -106,6 +106,7 @@ Custom Host name STARTTLS + Allow insecure connections Port number User name Password