Commit 55f06a7b authored by Jim Nelson's avatar Jim Nelson

Present user with dialog when TLS cert warnings detected: Bug #713247

When a TLS certificate warning is detected, the user will now be
presented with a warning dialog presenting them with three options:
Trust This Server, Always Trust This Server, and Don't Trust This
Server (the default).  The user must select one of the first two
buttons for Geary to continue connecting to the server, otherwise it
will close the Account object for the duration of the application
session.

This patch introduces a dependency on gcr-3, which is used to pin TLS
certificates (i.e. persist the user's choice of ignoring the TLS
warnings).

Conflicts:
	src/client/application/secret-mediator.vala
	src/engine/api/geary-account-information.vala
parent bd696b25
......@@ -20,7 +20,8 @@ Build-Depends: debhelper (>= 8),
intltool,
libgirepository1.0-dev (>= 1.32.0),
desktop-file-utils,
gnome-doc-utils
gnome-doc-utils,
libgcr-3-dev
Standards-Version: 3.8.3
Homepage: http://www.yorba.org
......@@ -38,7 +39,8 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
libgmime-2.6-0 (>= 2.6.0),
libsecret-1-0 (>= 0.11),
libmessaging-menu0 (>= 12.10.2),
libunity9 (>= 5.12.0)
libunity9 (>= 5.12.0),
libgcr-3-1
Description: Email client
Geary is an email client built for the GNOME desktop environment. It
allows you to read and send email with a simple, modern interface.
......
......@@ -42,6 +42,7 @@ src/client/conversation-viewer/conversation-viewer.vala
src/client/conversation-viewer/conversation-web-view.vala
src/client/dialogs/alert-dialog.vala
src/client/dialogs/attachment-dialog.vala
src/client/dialogs/certificate-warning-dialog.vala
src/client/dialogs/password-dialog.vala
src/client/dialogs/preferences-dialog.vala
src/client/dialogs/upgrade-dialog.vala
......@@ -108,6 +109,7 @@ src/engine/api/geary-named-flag.vala
src/engine/api/geary-progress-monitor.vala
src/engine/api/geary-search-folder.vala
src/engine/api/geary-search-query.vala
src/engine/api/geary-service.vala
src/engine/api/geary-service-provider.vala
src/engine/api/geary-special-folder-type.vala
src/engine/app/app-conversation-monitor.vala
......@@ -346,6 +348,7 @@ src/engine/util/util-trillian.vala
[type: gettext/glade]ui/account_list.glade
[type: gettext/glade]ui/account_spinner.glade
[type: gettext/glade]ui/app_menu.interface
[type: gettext/glade]ui/certificate_warning_dialog.glade
[type: gettext/glade]ui/composer_accelerators.ui
[type: gettext/glade]ui/composer.glade
[type: gettext/glade]ui/find_bar.glade
......
......@@ -40,6 +40,7 @@ engine/api/geary-named-flags.vala
engine/api/geary-progress-monitor.vala
engine/api/geary-search-folder.vala
engine/api/geary-search-query.vala
engine/api/geary-service.vala
engine/api/geary-service-provider.vala
engine/api/geary-special-folder-type.vala
......@@ -339,6 +340,7 @@ client/conversation-viewer/conversation-web-view.vala
client/dialogs/alert-dialog.vala
client/dialogs/attachment-dialog.vala
client/dialogs/certificate-warning-dialog.vala
client/dialogs/password-dialog.vala
client/dialogs/preferences-dialog.vala
client/dialogs/upgrade-dialog.vala
......@@ -522,6 +524,7 @@ pkg_check_modules(DEPS REQUIRED
gmime-2.6>=2.6.0
libsecret-1>=0.11
libxml-2.0>=2.7.8
gcr-3
${EXTRA_CLIENT_PKG_CONFIG}
)
......@@ -532,7 +535,7 @@ set(ENGINE_PACKAGES
# webkitgtk-3.0 is listed as a custom VAPI (below) to ensure it's treated as a dependency and
# built before compilation
set(CLIENT_PACKAGES
gtk+-3.0 libsecret-1 libsoup-2.4 libnotify libcanberra ${EXTRA_CLIENT_PACKAGES}
gtk+-3.0 libsecret-1 libsoup-2.4 libnotify libcanberra gcr-3 ${EXTRA_CLIENT_PACKAGES}
)
set(CONSOLE_PACKAGES
......@@ -554,6 +557,7 @@ set(CFLAGS
-D_GSETTINGS_DIR=\"${CMAKE_BINARY_DIR}/gsettings\"
-DGETTEXT_PACKAGE=\"${GETTEXT_PACKAGE}\"
-DLANGUAGE_SUPPORT_DIRECTORY=\"${LANGUAGE_SUPPORT_DIRECTORY}\"
-DGCR_API_SUBJECT_TO_CHANGE
-g
)
......
......@@ -109,8 +109,7 @@ public class AccountDialog : Gtk.Dialog {
return;
try {
yield account.get_passwords_async(Geary.CredentialsMediator.ServiceFlag.IMAP |
Geary.CredentialsMediator.ServiceFlag.SMTP);
yield account.get_passwords_async(Geary.ServiceFlag.IMAP | Geary.ServiceFlag.SMTP);
} catch (Error err) {
debug("Unable to fetch password(s) for account: %s", err.message);
}
......@@ -183,22 +182,34 @@ public class AccountDialog : Gtk.Dialog {
options |= Geary.Engine.ValidationOption.CHECK_CONNECTIONS;
// Validate account.
GearyApplication.instance.controller.validate_async.begin(info, options, null,
on_save_add_or_edit_completed);
do_save_or_edit_async.begin(info, options);
}
private void on_save_add_or_edit_completed(Object? source, AsyncResult result) {
Geary.Engine.ValidationResult validation_result =
GearyApplication.instance.controller.validate_async.end(result);
// If account was successfully added return to the account list. Otherwise, go back to the
// account add page so the user can try again.
if (validation_result == Geary.Engine.ValidationResult.OK) {
account_list_pane.present();
} else {
add_edit_pane.set_validation_result(validation_result);
add_edit_pane.present();
private async void do_save_or_edit_async(Geary.AccountInformation account_information,
Geary.Engine.ValidationOption options) {
Geary.Engine.ValidationResult validation_result = Geary.Engine.ValidationResult.OK;
for (;;) {
validation_result = yield GearyApplication.instance.controller.validate_async(
account_information, options);
// If account was successfully added return to the account list.
if (validation_result == Geary.Engine.ValidationResult.OK) {
account_list_pane.present();
return;
}
// check for TLS warnings
bool retry_required;
validation_result = yield GearyApplication.instance.controller.validation_check_for_tls_warnings_async(
account_information, validation_result, out retry_required);
if (!retry_required)
break;
}
// Otherwise, go back to the account add page so the user can try again.
add_edit_pane.set_validation_result(validation_result);
add_edit_pane.present();
}
private void on_cancel_back_to_list() {
......
......@@ -22,6 +22,7 @@ private const OptionEntry[] options = {
/// "Normalization" can also be called "synchronization"
{ "log-folder-normalization", 0, 0, OptionArg.NONE, ref log_folder_normalization, N_("Log folder normalization"), null },
{ "inspector", 'i', 0, OptionArg.NONE, ref inspector, N_("Allow inspection of WebView"), null },
{ "revoke-certs", 0, 0, OptionArg.NONE, ref revoke_certs, N_("Revoke all server certificates with TLS warnings"), null },
{ "version", 'V', 0, OptionArg.NONE, ref version, N_("Display program version"), null },
{ null }
};
......@@ -36,6 +37,7 @@ public bool log_periodic = false;
public bool log_sql = false;
public bool log_folder_normalization = false;
public bool inspector = false;
public bool revoke_certs = false;
public bool version = false;
public bool parse(string[] args) {
......
......@@ -4,6 +4,16 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
// Required because Gcr's VAPI is behind-the-times
// TODO: When bindings available, use async variants of these calls
extern const string GCR_PURPOSE_SERVER_AUTH;
extern bool gcr_trust_add_pinned_certificate(Gcr.Certificate cert, string purpose, string peer,
Cancellable? cancellable) throws Error;
extern bool gcr_trust_is_certificate_pinned(Gcr.Certificate cert, string purpose, string peer,
Cancellable? cancellable) throws Error;
extern bool gcr_trust_remove_pinned_certificate(Gcr.Certificate cert, string purpose, string peer,
Cancellable? cancellable) throws Error;
// Primary controller object for Geary.
public class GearyController : Geary.BaseObject {
// Named actions.
......@@ -107,6 +117,8 @@ public class GearyController : Geary.BaseObject {
private LoginDialog? login_dialog = null;
private UpgradeDialog upgrade_dialog;
private Gee.List<string> pending_mailtos = new Gee.ArrayList<string>();
private Geary.Nonblocking.Mutex untrusted_host_prompt_mutex = new Geary.Nonblocking.Mutex();
private Gee.HashSet<Geary.Endpoint> validating_endpoints = new Gee.HashSet<Geary.Endpoint>();
// List of windows we're waiting to close before Geary closes.
private Gee.List<ComposerWindow> waiting_to_close = new Gee.ArrayList<ComposerWindow>();
......@@ -173,6 +185,7 @@ public class GearyController : Geary.BaseObject {
Geary.Engine.instance.account_available.connect(on_account_available);
Geary.Engine.instance.account_unavailable.connect(on_account_unavailable);
Geary.Engine.instance.untrusted_host.connect(on_untrusted_host);
// Connect to various UI signals.
main_window.conversation_list_view.conversations_selected.connect(on_conversations_selected);
......@@ -492,6 +505,135 @@ public class GearyController : Geary.BaseObject {
close_account(get_account_instance(account_information));
}
private void on_untrusted_host(Geary.AccountInformation account_information,
Geary.Endpoint endpoint, Geary.Endpoint.SecurityType security, TlsConnection cx,
Geary.Service service) {
prompt_untrusted_host_async.begin(account_information, endpoint, security, cx, service);
}
private async void prompt_untrusted_host_async(Geary.AccountInformation account_information,
Geary.Endpoint endpoint, Geary.Endpoint.SecurityType security, TlsConnection cx,
Geary.Service service) {
// use a mutex to prevent multiple dialogs popping up at the same time
int token = Geary.Nonblocking.Mutex.INVALID_TOKEN;
try {
token = yield untrusted_host_prompt_mutex.claim_async();
} catch (Error err) {
message("Unable to lock mutex to prompt user about invalid certificate: %s", err.message);
return;
}
yield locked_prompt_untrusted_host_async(account_information, endpoint, security, cx,
service);
try {
untrusted_host_prompt_mutex.release(ref token);
} catch (Error err) {
message("Unable to release mutex after prompting user about invalid certificate: %s",
err.message);
}
}
private static void get_gcr_params(Geary.Endpoint endpoint, out Gcr.Certificate cert,
out string peer) {
cert = new Gcr.SimpleCertificate(endpoint.untrusted_certificate.certificate.data);
peer = "%s:%u".printf(endpoint.remote_address.hostname, endpoint.remote_address.port);
}
private async void locked_prompt_untrusted_host_async(Geary.AccountInformation account_information,
Geary.Endpoint endpoint, Geary.Endpoint.SecurityType security, TlsConnection cx,
Geary.Service service) {
// possible while waiting on mutex that this endpoint became trusted or untrusted
if (endpoint.trust_untrusted_host != Geary.Trillian.UNKNOWN)
return;
// get GCR parameters
Gcr.Certificate cert;
string peer;
get_gcr_params(endpoint, out cert, out peer);
// Geary allows for user to auto-revoke all questionable server certificates without
// digging around in a keyring/pk manager
if (Args.revoke_certs) {
debug("Auto-revoking certificate for %s...", peer);
try {
gcr_trust_remove_pinned_certificate(cert, GCR_PURPOSE_SERVER_AUTH, peer, null);
} catch (Error err) {
message("Unable to auto-revoke server certificate for %s: %s", peer, err.message);
// drop through, not absolutely valid to do this (might also mean certificate
// was never pinned)
}
}
// if pinned, the user has already made an exception for this server and its certificate,
// so go ahead w/o asking
try {
if (gcr_trust_is_certificate_pinned(cert, GCR_PURPOSE_SERVER_AUTH, peer, null)) {
debug("Certificate for %s is pinned, accepting connection...", peer);
endpoint.trust_untrusted_host = Geary.Trillian.TRUE;
return;
}
} catch (Error err) {
message("Unable to check if server certificate for %s is pinned, assuming not: %s",
peer, err.message);
}
// if these are in validation, there are complex GTK and workflow issues from simply
// presenting the prompt now, so caller who connected will need to do it on their own dime
if (!validating_endpoints.contains(endpoint))
prompt_for_untrusted_host(main_window, account_information, endpoint, service, false);
}
private void prompt_for_untrusted_host(Gtk.Window? parent, Geary.AccountInformation account_information,
Geary.Endpoint endpoint, Geary.Service service, bool is_validation) {
CertificateWarningDialog dialog = new CertificateWarningDialog(parent, account_information,
service, endpoint.tls_validation_warnings, is_validation);
switch (dialog.run()) {
case CertificateWarningDialog.Result.TRUST:
endpoint.trust_untrusted_host = Geary.Trillian.TRUE;
break;
case CertificateWarningDialog.Result.ALWAYS_TRUST:
endpoint.trust_untrusted_host = Geary.Trillian.TRUE;
// get GCR parameters for pinning
Gcr.Certificate cert;
string peer;
get_gcr_params(endpoint, out cert, out peer);
// pinning the certificate creates an exception for the next time a connection
// is attempted
debug("Pinning certificate for %s...", peer);
try {
gcr_trust_add_pinned_certificate(cert, GCR_PURPOSE_SERVER_AUTH, peer, null);
} catch (Error err) {
ErrorDialog error_dialog = new ErrorDialog(main_window,
_("Unable to store server trust exception"), err.message);
error_dialog.run();
}
break;
default:
endpoint.trust_untrusted_host = Geary.Trillian.FALSE;
// close the account; can't go any further w/o offline mode
try {
if (Geary.Engine.instance.get_accounts().has_key(account_information.email)) {
Geary.Account account = Geary.Engine.instance.get_account_instance(account_information);
close_account(account);
}
} catch (Error err) {
message("Unable to close account due to user trust issues: %s", err.message);
}
break;
}
}
private void create_account() {
Geary.AccountInformation? account_information = request_account_information(null);
if (account_information != null)
......@@ -509,6 +651,82 @@ public class GearyController : Geary.BaseObject {
login_dialog.hide();
}
// Returns possibly modified validation results
private Geary.Engine.ValidationResult validation_check_endpoint_for_tls_warnings(
Geary.AccountInformation account_information, Geary.Service service,
Geary.Engine.ValidationResult validation_result, out bool prompted, out bool retry_required) {
prompted = false;
retry_required = false;
// use LoginDialog for parent only if available and visible
Gtk.Window? parent;
if (login_dialog != null && login_dialog.visible)
parent = login_dialog;
else
parent = main_window;
Geary.Endpoint endpoint = account_information.get_endpoint_for_service(service);
// If Endpoint had unresolved TLS issues, prompt user about them
if (endpoint.tls_validation_warnings != 0 && endpoint.trust_untrusted_host != Geary.Trillian.TRUE) {
prompt_for_untrusted_host(parent, account_information, endpoint, service, true);
prompted = true;
}
// If there are still TLS connection issues that caused the connection to fail (happens on the
// first attempt), clear those errors and retry
if (endpoint.tls_validation_warnings != 0 && endpoint.trust_untrusted_host == Geary.Trillian.TRUE) {
Geary.Engine.ValidationResult flag = (service == Geary.Service.IMAP)
? Geary.Engine.ValidationResult.IMAP_CONNECTION_FAILED
: Geary.Engine.ValidationResult.SMTP_CONNECTION_FAILED;
if ((validation_result & flag) != 0) {
validation_result &= ~flag;
retry_required = true;
}
}
return validation_result;
}
// Use after validating to see if TLS warnings were handled by the user and need to retry the
// validation; this will also modify the validation results to better indicate issues to the user
//
// Returns possibly modified validation results
public async Geary.Engine.ValidationResult validation_check_for_tls_warnings_async(
Geary.AccountInformation account_information, Geary.Engine.ValidationResult validation_result,
out bool retry_required) {
retry_required = false;
// Because TLS warnings need cycles to process, sleep and give 'em a chance to do their
// thing ... note that the signal handler does *not* invoke the user prompt dialog when the
// login dialog is in play, so this sleep does not need to worry about user input
yield Geary.Scheduler.sleep_ms_async(100);
// check each service for problems, prompting user each time for verification
bool imap_prompted, imap_retry_required;
validation_result = validation_check_endpoint_for_tls_warnings(account_information,
Geary.Service.IMAP, validation_result, out imap_prompted, out imap_retry_required);
bool smtp_prompted, smtp_retry_required;
validation_result = validation_check_endpoint_for_tls_warnings(account_information,
Geary.Service.SMTP, validation_result, out smtp_prompted, out smtp_retry_required);
// if prompted for user acceptance of bad certificates and they agreed to both, try again
if (imap_prompted && smtp_prompted
&& account_information.get_imap_endpoint().is_trusted_or_never_connected
&& account_information.get_smtp_endpoint().is_trusted_or_never_connected) {
retry_required = true;
} else if (validation_result == Geary.Engine.ValidationResult.OK) {
retry_required = true;
} else {
// if prompt requires retry or otherwise detected it, retry
retry_required = imap_retry_required && smtp_retry_required;
}
return validation_result;
}
// Returns null if we are done validating, or the revised account information if we should retry.
private async Geary.AccountInformation? validate_or_retry_async(Geary.AccountInformation account_information,
Cancellable? cancellable = null) {
......@@ -517,6 +735,16 @@ public class GearyController : Geary.BaseObject {
if (result == Geary.Engine.ValidationResult.OK)
return null;
// check Endpoints for trust (TLS) issues
bool retry_required;
result = yield validation_check_for_tls_warnings_async(account_information, result,
out retry_required);
// return for retry if required; check can also change validation results, in which case
// revalidate entirely to have them written out
if (retry_required)
return account_information;
debug("Validation failed. Prompting user for revised account information");
Geary.AccountInformation? new_account_information =
request_account_information(account_information, result);
......@@ -538,6 +766,10 @@ public class GearyController : Geary.BaseObject {
public async Geary.Engine.ValidationResult validate_async(
Geary.AccountInformation account_information, Geary.Engine.ValidationOption options,
Cancellable? cancellable = null) {
// add Endpoints to set of validating endpoints to prevent the prompt from appearing
validating_endpoints.add(account_information.get_imap_endpoint());
validating_endpoints.add(account_information.get_smtp_endpoint());
Geary.Engine.ValidationResult result = Geary.Engine.ValidationResult.OK;
try {
result = yield Geary.Engine.instance.validate_account_information_async(account_information,
......@@ -549,6 +781,9 @@ public class GearyController : Geary.BaseObject {
return result;
}
validating_endpoints.remove(account_information.get_imap_endpoint());
validating_endpoints.remove(account_information.get_smtp_endpoint());
if (result == Geary.Engine.ValidationResult.OK) {
Geary.AccountInformation real_account_information = account_information;
if (account_information.is_copy()) {
......@@ -559,8 +794,8 @@ public class GearyController : Geary.BaseObject {
}
real_account_information.store_async.begin(cancellable);
do_update_stored_passwords_async.begin(Geary.CredentialsMediator.ServiceFlag.IMAP |
Geary.CredentialsMediator.ServiceFlag.SMTP, real_account_information);
do_update_stored_passwords_async.begin(Geary.ServiceFlag.IMAP | Geary.ServiceFlag.SMTP,
real_account_information);
debug("Successfully validated account information");
}
......@@ -628,7 +863,7 @@ public class GearyController : Geary.BaseObject {
return new_info;
}
private async void do_update_stored_passwords_async(Geary.CredentialsMediator.ServiceFlag services,
private async void do_update_stored_passwords_async(Geary.ServiceFlag services,
Geary.AccountInformation account_information) {
try {
yield account_information.update_stored_passwords_async(services);
......
......@@ -8,12 +8,12 @@
public class SecretMediator : Geary.CredentialsMediator, Object {
private const string OLD_GEARY_USERNAME_PREFIX = "org.yorba.geary username:";
private string get_key_name(Geary.CredentialsMediator.Service service, string user) {
private string get_key_name(Geary.Service service, string user) {
switch (service) {
case Service.IMAP:
case Geary.Service.IMAP:
return "org.yorba.geary imap_username:" + user;
case Service.SMTP:
case Geary.Service.SMTP:
return "org.yorba.geary smtp_username:" + user;
default:
......@@ -22,7 +22,7 @@ public class SecretMediator : Geary.CredentialsMediator, Object {
}
public virtual async string? get_password_async(
Geary.CredentialsMediator.Service service, string username, Cancellable? cancellable = null)
Geary.Service service, string username, Cancellable? cancellable = null)
throws Error {
string? password = yield Secret.password_lookup(Secret.SCHEMA_COMPAT_NETWORK, cancellable,
"user", get_key_name(service, username));
......@@ -40,7 +40,7 @@ public class SecretMediator : Geary.CredentialsMediator, Object {
}
public virtual async void set_password_async(
Geary.CredentialsMediator.Service service, Geary.Credentials credentials,
Geary.Service service, Geary.Credentials credentials,
Cancellable? cancellable = null) throws Error {
string key_name = get_key_name(service, credentials.user);
......@@ -52,7 +52,7 @@ public class SecretMediator : Geary.CredentialsMediator, Object {
}
public virtual async void clear_password_async(
Geary.CredentialsMediator.Service service, string username, Cancellable? cancellable = null)
Geary.Service service, string username, Cancellable? cancellable = null)
throws Error {
// delete new-style and old-style locations
yield Secret.password_clear(Secret.SCHEMA_COMPAT_NETWORK, cancellable, "user",
......@@ -61,7 +61,7 @@ public class SecretMediator : Geary.CredentialsMediator, Object {
OLD_GEARY_USERNAME_PREFIX + username);
}
public virtual async bool prompt_passwords_async(Geary.CredentialsMediator.ServiceFlag services,
public virtual async bool prompt_passwords_async(Geary.ServiceFlag services,
Geary.AccountInformation account_information,
out string? imap_password, out string? smtp_password,
out bool imap_remember_password, out bool smtp_remember_password) throws Error {
......
/* Copyright 2014 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class CertificateWarningDialog {
public enum Result {
DONT_TRUST,
TRUST,
ALWAYS_TRUST
}
private const string BULLET = "&#8226; ";
private Gtk.Dialog dialog;
public CertificateWarningDialog(Gtk.Window? parent, Geary.AccountInformation account_information,
Geary.Service service, TlsCertificateFlags warnings, bool is_validation) {
Gtk.Builder builder = GearyApplication.instance.create_builder("certificate_warning_dialog.glade");
dialog = (Gtk.Dialog) builder.get_object("CertificateWarningDialog");
dialog.transient_for = parent;
dialog.modal = true;
Gtk.Label title_label = (Gtk.Label) builder.get_object("untrusted_connection_label");
Gtk.Label top_label = (Gtk.Label) builder.get_object("top_label");
Gtk.Label warnings_label = (Gtk.Label) builder.get_object("warnings_label");
Gtk.Label trust_label = (Gtk.Label) builder.get_object("trust_label");
Gtk.Label dont_trust_label = (Gtk.Label) builder.get_object("dont_trust_label");
Gtk.Label contact_label = (Gtk.Label) builder.get_object("contact_label");
title_label.label = _("Untrusted Connection: %s").printf(account_information.email);
Geary.Endpoint endpoint = account_information.get_endpoint_for_service(service);
top_label.label = _("The identity of the %s mail server at %s:%u could not be verified.").printf(
service.user_label(), endpoint.remote_address.hostname, endpoint.remote_address.port);
warnings_label.label = generate_warning_list(warnings);
warnings_label.use_markup = true;
trust_label.label =
"<b>"
+_("Selecting \"Trust This Server\" or \"Always Trust This Server\" may cause your username and password to be transmitted insecurely.")
+ "</b>";
trust_label.use_markup = true;
if (is_validation) {
// could be a new or existing account
dont_trust_label.label =
"<b>"
+ _("Selecting \"Don't Trust This Server\" will cause Geary not to access this server.")
+ "</b> "
+ _("Geary will not add or update this email account.");
} else {
// a registered account
dont_trust_label.label =
"<b>"
+ _("Selecting \"Don't Trust This Server\" will cause Geary to stop accessing this account.")
+ "</b> "
+ _("Geary will exit if you have no other open email accounts.");
}
dont_trust_label.use_markup = true;
contact_label.label =
_("Contact your system administrator or email service provider if you have any question about these issues.");
}
private static string generate_warning_list(TlsCertificateFlags warnings) {
StringBuilder builder = new StringBuilder();
if ((warnings & TlsCertificateFlags.UNKNOWN_CA) != 0)
builder.append(BULLET + _("The server's certificate is not signed by a known authority") + "\n");
if ((warnings & TlsCertificateFlags.BAD_IDENTITY) != 0)
builder.append(BULLET + _("The server's identity does not match the identity in the certificate") + "\n");
if ((warnings & TlsCertificateFlags.EXPIRED) != 0)
builder.append(BULLET + _("The server's certificate has expired") + "\n");
if ((warnings & TlsCertificateFlags.NOT_ACTIVATED) != 0)
builder.append(BULLET + _("The server's certificate has not been activated") + "\n");
if ((warnings & TlsCertificateFlags.REVOKED) != 0)
builder.append(BULLET + _("The server's certificate has been revoked and is now invalid") + "\n");
if ((warnings & TlsCertificateFlags.INSECURE) != 0)
builder.append(BULLET + _("The server's certificate is considered insecure") + "\n");
if ((warnings & TlsCertificateFlags.GENERIC_ERROR) != 0)
builder.append(BULLET + _("An error has occurred processing the server's certificate") + "\n");
return builder.str;
}
public Result run() {
dialog.show_all();
int response = dialog.run();
dialog.destroy();
// these values are defined in the Glade file
switch (response) {
case 1:
return Result.TRUST;
case 2:
return Result.ALWAYS_TRUST;
default:
return Result.DONT_TRUST;
}
}
}
......@@ -24,7 +24,7 @@ public class PasswordDialog {
public bool remember_password { get; private set; }
public PasswordDialog(bool smtp, Geary.AccountInformation account_information,
Geary.CredentialsMediator.ServiceFlag password_flags) {
Geary.ServiceFlag password_flags) {
Gtk.Builder builder = GearyApplication.instance.create_builder("password-dialog.glade");
dialog = (Gtk.Dialog) builder.get_object("PasswordDialog");
......
......@@ -47,6 +47,8 @@ public class Geary.AccountInformation : BaseObject {
public static int default_ordinal = 0;
private static Gee.HashMap<string, Geary.Endpoint>? known_endpoints = null;
internal File? settings_dir = null;
internal File? file = null;
......@@ -98,6 +100,20 @@ public class Geary.AccountInformation : BaseObject {
public bool smtp_remember_password { get; set; default = true; }
private bool _save_sent_mail = true;
private Endpoint? imap_endpoint = null;
private Endpoint? smtp_endpoint = null;
/**
* Indicates the supplied {@link Endpoint} has reported TLS certificate warnings during
* connection.
*
* Since this {@link Endpoint} persists for the lifetime of the {@link AccountInformation},
* marking it as trusted once will survive the application session. It is up to the caller to
* pin the certificate appropriately if the user does not want to receive these warnings in
* the future.
*/
public signal void untrusted_host(Endpoint endpoint, Endpoint.SecurityType security,
TlsConnection cx, Service service);
// Used to create temporary AccountInformation objects. (Note that these cannot be saved.)
public AccountInformation.temp_copy(AccountInformation copy) {
......@@ -168,6 +184,32 @@ public class Geary.AccountInformation : BaseObject {
}
}
~AccountInformation() {
if (imap_endpoint != null)
imap_endpoint.untrusted_host.disconnect(on_imap_untrusted_host);
if (smtp_endpoint != null)
smtp_endpoint.untrusted_host.disconnect(on_smtp_untrusted_host);
}
internal static void init() {
known_endpoints = new Gee.HashMap<string, Geary.Endpoint>();
}
private static Geary.Endpoint get_shared_endpoint(Service service, Endpoint endpoint) {
string key = "%s/%s:%u".printf(service.user_label(), endpoint.remote_address.hostname,
endpoint.remote_address.port);
// if already known, prefer it over this one
if (known_endpoints.has_key(key))
return known_endpoints.get(key);
// save for future use and return this one
known_endpoints.set(key, endpoint);
return endpoint;
}
// Copies all data from the "from" object into this one.
public void copy_from(AccountInformation from) {
real_name = from.real_name;
......@@ -273,19 +315,19 @@ public class Geary.AccountInformation : BaseObject {