From 2f2dca09b138d94407e1fa99a746f8203a0bce2b Mon Sep 17 00:00:00 2001 From: Nate Lillich Date: Thu, 24 May 2012 12:14:05 -0700 Subject: [PATCH] Closes #4780. This adds support for STARTTLS with SMTP as well as replaces the previous uses of "TLS" with "SSL" to prevent confusion related to the two similar protocols. Also it adds debug support to geary-mailer. --- src/client/geary-application.vala | 6 +- src/client/ui/geary-login.vala | 32 +++---- src/console/main.vala | 4 +- src/engine/api/geary-account-information.vala | 24 +++--- src/engine/api/geary-endpoint.vala | 21 ++--- src/engine/api/geary-engine.vala | 4 +- .../transport/imap-client-connection.vala | 2 +- src/engine/impl/geary-gmail-account.vala | 8 +- src/engine/impl/geary-yahoo-account.vala | 8 +- src/engine/smtp/smtp-client-connection.vala | 86 +++++++++++++++---- src/engine/smtp/smtp-client-session.vala | 34 ++------ src/engine/smtp/smtp-command.vala | 13 ++- src/engine/smtp/smtp-error.vala | 1 + src/engine/smtp/smtp-response-code.vala | 9 +- src/mailer/main.vala | 14 ++- ui/login.glade | 4 +- 16 files changed, 162 insertions(+), 108 deletions(-) diff --git a/src/client/geary-application.vala b/src/client/geary-application.vala index 57b20798..a4f4cc71 100644 --- a/src/client/geary-application.vala +++ b/src/client/geary-application.vala @@ -131,7 +131,7 @@ along with Geary; if not, write to the Free Software Foundation, Inc., Geary.Logging.log_to(stdout); return 0; - } + } public override int startup() { exec_dir = (File.new_for_path(Environment.find_program_in_path(args[0]))).get_parent(); @@ -227,11 +227,11 @@ along with Geary; if not, write to the Free Software Foundation, Inc., account_info.imap_server_host = login.imap_host; account_info.imap_server_port = login.imap_port; - account_info.imap_server_tls = login.imap_tls; + account_info.imap_server_ssl = login.imap_ssl; account_info.imap_server_pipeline = (login.provider != Geary.ServiceProvider.OTHER); account_info.smtp_server_host = login.smtp_host; account_info.smtp_server_port = login.smtp_port; - account_info.smtp_server_tls = login.smtp_tls; + account_info.smtp_server_ssl = login.smtp_ssl; } else { exit(1); return null; diff --git a/src/client/ui/geary-login.vala b/src/client/ui/geary-login.vala index ca8e77f2..a8c2be73 100644 --- a/src/client/ui/geary-login.vala +++ b/src/client/ui/geary-login.vala @@ -15,10 +15,10 @@ public class LoginDialog { private Gtk.Alignment other_info; private Gtk.Entry entry_imap_host; private Gtk.Entry entry_imap_port; - private Gtk.CheckButton check_imap_tls; + private Gtk.CheckButton check_imap_ssl; private Gtk.Entry entry_smtp_host; private Gtk.Entry entry_smtp_port; - private Gtk.CheckButton check_smtp_tls; + private Gtk.CheckButton check_smtp_ssl; private Gtk.ResponseType response; private Gtk.Button ok_button; @@ -34,12 +34,12 @@ public class LoginDialog { public string imap_host { get; private set; default = ""; } public uint16 imap_port { get; private set; - default = Geary.Imap.ClientConnection.DEFAULT_PORT_TLS; } - public bool imap_tls { get; private set; default = true; } + default = Geary.Imap.ClientConnection.DEFAULT_PORT_SSL; } + public bool imap_ssl { get; private set; default = true; } public string smtp_host { get; private set; default = ""; } public uint16 smtp_port { get; private set; - default = Geary.Smtp.ClientConnection.SECURE_SMTP_PORT; } - public bool smtp_tls { get; private set; default = true; } + default = Geary.Smtp.ClientConnection.DEFAULT_PORT_SSL; } + public bool smtp_ssl { get; private set; default = true; } public LoginDialog(string default_real_name = "", string default_username = "", string default_password = "", Geary.AccountInformation? default_account_info = null) { @@ -57,10 +57,10 @@ public class LoginDialog { other_info = builder.get_object("other_info") as Gtk.Alignment; entry_imap_host = builder.get_object("imap host") as Gtk.Entry; entry_imap_port = builder.get_object("imap port") as Gtk.Entry; - check_imap_tls = builder.get_object("imap tls") as Gtk.CheckButton; + check_imap_ssl = builder.get_object("imap ssl") as Gtk.CheckButton; entry_smtp_host = builder.get_object("smtp host") as Gtk.Entry; entry_smtp_port = builder.get_object("smtp port") as Gtk.Entry; - check_smtp_tls = builder.get_object("smtp tls") as Gtk.CheckButton; + check_smtp_ssl = builder.get_object("smtp ssl") as Gtk.CheckButton; combo_service.changed.connect(on_service_changed); @@ -94,8 +94,8 @@ public class LoginDialog { entry_smtp_host.changed.connect(on_changed); entry_smtp_port.changed.connect(on_changed); - check_imap_tls.toggled.connect(on_check_imap_tls_toggled); - check_smtp_tls.toggled.connect(on_check_smtp_tls_toggled); + check_imap_ssl.toggled.connect(on_check_imap_ssl_toggled); + check_smtp_ssl.toggled.connect(on_check_smtp_ssl_toggled); entry_imap_port.insert_text.connect(on_port_insert_text); entry_smtp_port.insert_text.connect(on_port_insert_text); @@ -122,10 +122,10 @@ public class LoginDialog { provider = get_service_provider(); imap_host = entry_imap_host.text.strip(); imap_port = (uint16) int.parse(entry_imap_port.text.strip()); - imap_tls = check_imap_tls.active; + imap_ssl = check_imap_ssl.active; smtp_host = entry_smtp_host.text.strip(); smtp_port = (uint16) int.parse(entry_smtp_port.text.strip()); - smtp_tls = check_smtp_tls.active; + smtp_ssl = check_smtp_ssl.active; dialog.destroy(); } @@ -160,20 +160,20 @@ public class LoginDialog { } } - private void on_check_imap_tls_toggled() { + private void on_check_imap_ssl_toggled() { if (edited_imap_port) return; - entry_imap_port.text = (check_imap_tls.active ? Geary.Imap.ClientConnection.DEFAULT_PORT_TLS : + entry_imap_port.text = (check_imap_ssl.active ? Geary.Imap.ClientConnection.DEFAULT_PORT_SSL : Geary.Imap.ClientConnection.DEFAULT_PORT).to_string(); edited_imap_port = false; } - private void on_check_smtp_tls_toggled() { + private void on_check_smtp_ssl_toggled() { if (edited_smtp_port) return; - entry_smtp_port.text = (check_smtp_tls.active ? Geary.Smtp.ClientConnection.SECURE_SMTP_PORT : + entry_smtp_port.text = (check_smtp_ssl.active ? Geary.Smtp.ClientConnection.DEFAULT_PORT_SSL : Geary.Smtp.ClientConnection.DEFAULT_PORT).to_string(); edited_smtp_port = false; } diff --git a/src/console/main.vala b/src/console/main.vala index 68a22518..db96582b 100644 --- a/src/console/main.vala +++ b/src/console/main.vala @@ -279,8 +279,8 @@ class ImapConsole : Gtk.Window { check_args(cmd, args, 1, "hostname[:port]"); cx = new Geary.Imap.ClientConnection( - new Geary.Endpoint(args[0], Geary.Imap.ClientConnection.DEFAULT_PORT_TLS, - Geary.Endpoint.Flags.TLS | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, + new Geary.Endpoint(args[0], Geary.Imap.ClientConnection.DEFAULT_PORT_SSL, + Geary.Endpoint.Flags.SSL | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, Geary.Imap.ClientConnection.DEFAULT_TIMEOUT_SEC)); cx.sent_command.connect(on_sent_command); diff --git a/src/engine/api/geary-account-information.vala b/src/engine/api/geary-account-information.vala index 0ca45c8d..abdd48e0 100644 --- a/src/engine/api/geary-account-information.vala +++ b/src/engine/api/geary-account-information.vala @@ -10,24 +10,24 @@ public class Geary.AccountInformation : Object { private const string SERVICE_PROVIDER_KEY = "service_provider"; private const string IMAP_HOST = "imap_host"; private const string IMAP_PORT = "imap_port"; - private const string IMAP_TLS = "imap_tls"; + private const string IMAP_SSL = "imap_ssl"; private const string IMAP_PIPELINE = "imap_pipeline"; private const string SMTP_HOST = "smtp_host"; private const string SMTP_PORT = "smtp_port"; - private const string SMTP_TLS = "smtp_tls"; + private const string SMTP_SSL = "smtp_ssl"; internal File? file = null; public string real_name { get; set; } public Geary.ServiceProvider service_provider { get; set; } public string imap_server_host { get; set; default = ""; } - public uint16 imap_server_port { get; set; default = Imap.ClientConnection.DEFAULT_PORT_TLS; } - public bool imap_server_tls { get; set; default = true; } + public uint16 imap_server_port { get; set; default = Imap.ClientConnection.DEFAULT_PORT_SSL; } + public bool imap_server_ssl { get; set; default = true; } public bool imap_server_pipeline { get; set; default = true; } public string smtp_server_host { get; set; default = ""; } - public uint16 smtp_server_port { get; set; default = Smtp.ClientConnection.SECURE_SMTP_PORT; } - public bool smtp_server_tls { get; set; default = true; } + public uint16 smtp_server_port { get; set; default = Smtp.ClientConnection.DEFAULT_PORT_SSL; } + public bool smtp_server_ssl { get; set; default = true; } public AccountInformation() { } @@ -46,14 +46,14 @@ public class Geary.AccountInformation : Object { imap_server_host = get_string_value(key_file, GROUP, IMAP_HOST); imap_server_port = get_uint16_value(key_file, GROUP, IMAP_PORT, - Imap.ClientConnection.DEFAULT_PORT_TLS); - imap_server_tls = get_bool_value(key_file, GROUP, IMAP_TLS, true); + Imap.ClientConnection.DEFAULT_PORT_SSL); + imap_server_ssl = get_bool_value(key_file, GROUP, IMAP_SSL, true); imap_server_pipeline = get_bool_value(key_file, GROUP, IMAP_PIPELINE, true); smtp_server_host = get_string_value(key_file, GROUP, SMTP_HOST); smtp_server_port = get_uint16_value(key_file, GROUP, SMTP_PORT, - Geary.Smtp.ClientConnection.SECURE_SMTP_PORT); - smtp_server_tls = get_bool_value(key_file, GROUP, SMTP_TLS, true); + Geary.Smtp.ClientConnection.DEFAULT_PORT_SSL); + smtp_server_ssl = get_bool_value(key_file, GROUP, SMTP_SSL, true); } } @@ -99,12 +99,12 @@ public class Geary.AccountInformation : Object { key_file.set_value(GROUP, IMAP_HOST, imap_server_host); key_file.set_integer(GROUP, IMAP_PORT, imap_server_port); - key_file.set_boolean(GROUP, IMAP_TLS, imap_server_tls); + key_file.set_boolean(GROUP, IMAP_SSL, imap_server_ssl); key_file.set_boolean(GROUP, IMAP_PIPELINE, imap_server_pipeline); key_file.set_value(GROUP, SMTP_HOST, smtp_server_host); key_file.set_integer(GROUP, SMTP_PORT, smtp_server_port); - key_file.set_boolean(GROUP, SMTP_TLS, smtp_server_tls); + key_file.set_boolean(GROUP, SMTP_SSL, smtp_server_ssl); string data = key_file.to_data(); string new_etag; diff --git a/src/engine/api/geary-endpoint.vala b/src/engine/api/geary-endpoint.vala index 9b87c827..68f2a55b 100644 --- a/src/engine/api/geary-endpoint.vala +++ b/src/engine/api/geary-endpoint.vala @@ -8,7 +8,8 @@ public class Geary.Endpoint : Object { [Flags] public enum Flags { NONE = 0, - TLS, + SSL, + STARTTLS, GRACEFUL_DISCONNECT; public inline bool is_all_set(Flags flags) { @@ -37,30 +38,30 @@ public class Geary.Endpoint : Object { public SocketClient get_socket_client() { if (socket_client != null) return socket_client; - + socket_client = new SocketClient(); - - if (flags.is_all_set(Flags.TLS)) { + + if (flags.is_all_set(Flags.SSL)) { socket_client.set_tls(true); socket_client.set_tls_validation_flags(TlsCertificateFlags.UNKNOWN_CA); } - + socket_client.set_timeout(timeout_sec); - + return socket_client; } - + public async SocketConnection connect_async(Cancellable? cancellable = null) throws Error { SocketConnection cx = yield get_socket_client().connect_to_host_async(host_specifier, default_port, cancellable); - + TcpConnection? tcp = cx as TcpConnection; if (tcp != null) tcp.set_graceful_disconnect(flags.is_all_set(Flags.GRACEFUL_DISCONNECT)); - + return cx; } - + public string to_string() { return "%s/default:%u".printf(host_specifier, default_port); } diff --git a/src/engine/api/geary-engine.vala b/src/engine/api/geary-engine.vala index 08ec29f6..6b1e026e 100644 --- a/src/engine/api/geary-engine.vala +++ b/src/engine/api/geary-engine.vala @@ -50,11 +50,11 @@ public class Geary.Engine { account_info), new Geary.Sqlite.Account(cred, user_data_dir, resource_dir)); case ServiceProvider.OTHER: - Endpoint.Flags imap_flags = account_info.imap_server_tls ? Endpoint.Flags.TLS + Endpoint.Flags imap_flags = account_info.imap_server_ssl ? Endpoint.Flags.SSL : Endpoint.Flags.NONE; imap_flags |= Endpoint.Flags.GRACEFUL_DISCONNECT; - Endpoint.Flags smtp_flags = account_info.smtp_server_tls ? Endpoint.Flags.TLS + Endpoint.Flags smtp_flags = account_info.smtp_server_ssl ? Endpoint.Flags.SSL : Endpoint.Flags.NONE; smtp_flags |= Geary.Endpoint.Flags.GRACEFUL_DISCONNECT; diff --git a/src/engine/imap/transport/imap-client-connection.vala b/src/engine/imap/transport/imap-client-connection.vala index ced9ffb5..80105f20 100644 --- a/src/engine/imap/transport/imap-client-connection.vala +++ b/src/engine/imap/transport/imap-client-connection.vala @@ -6,7 +6,7 @@ public class Geary.Imap.ClientConnection { public const uint16 DEFAULT_PORT = 143; - public const uint16 DEFAULT_PORT_TLS = 993; + public const uint16 DEFAULT_PORT_SSL = 993; // TODO: This is set very high to allow for IDLE connections to remain connected even when // there is no traffic on them. The side-effect is that if the physical connection is dropped, diff --git a/src/engine/impl/geary-gmail-account.vala b/src/engine/impl/geary-gmail-account.vala index a8b07a2d..2ac93e2d 100644 --- a/src/engine/impl/geary-gmail-account.vala +++ b/src/engine/impl/geary-gmail-account.vala @@ -12,8 +12,8 @@ private class Geary.GmailAccount : Geary.GenericImapAccount { if (_imap_endpoint == null) { _imap_endpoint = new Geary.Endpoint( "imap.gmail.com", - Imap.ClientConnection.DEFAULT_PORT_TLS, - Geary.Endpoint.Flags.TLS | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, + Imap.ClientConnection.DEFAULT_PORT_SSL, + Geary.Endpoint.Flags.SSL | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, Imap.ClientConnection.DEFAULT_TIMEOUT_SEC); } @@ -25,8 +25,8 @@ private class Geary.GmailAccount : Geary.GenericImapAccount { if (_smtp_endpoint == null) { _smtp_endpoint = new Geary.Endpoint( "smtp.gmail.com", - Smtp.ClientConnection.SECURE_SMTP_PORT, - Geary.Endpoint.Flags.TLS | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, + Smtp.ClientConnection.DEFAULT_PORT_SSL, + Geary.Endpoint.Flags.SSL | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC); } diff --git a/src/engine/impl/geary-yahoo-account.vala b/src/engine/impl/geary-yahoo-account.vala index 7f9a6a45..d0a3508c 100644 --- a/src/engine/impl/geary-yahoo-account.vala +++ b/src/engine/impl/geary-yahoo-account.vala @@ -10,8 +10,8 @@ private class Geary.YahooAccount : Geary.GenericImapAccount { if (_imap_endpoint == null) { _imap_endpoint = new Geary.Endpoint( "imap.mail.yahoo.com", - Imap.ClientConnection.DEFAULT_PORT_TLS, - Geary.Endpoint.Flags.TLS | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, + Imap.ClientConnection.DEFAULT_PORT_SSL, + Geary.Endpoint.Flags.SSL | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, Imap.ClientConnection.DEFAULT_TIMEOUT_SEC); } @@ -23,8 +23,8 @@ private class Geary.YahooAccount : Geary.GenericImapAccount { if (_smtp_endpoint == null) { _smtp_endpoint = new Geary.Endpoint( "smtp.mail.yahoo.com", - Smtp.ClientConnection.SECURE_SMTP_PORT, - Geary.Endpoint.Flags.TLS | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, + Smtp.ClientConnection.DEFAULT_PORT_SSL, + Geary.Endpoint.Flags.SSL | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC); } diff --git a/src/engine/smtp/smtp-client-connection.vala b/src/engine/smtp/smtp-client-connection.vala index 824c3924..34ac4cb9 100644 --- a/src/engine/smtp/smtp-client-connection.vala +++ b/src/engine/smtp/smtp-client-connection.vala @@ -6,15 +6,17 @@ public class Geary.Smtp.ClientConnection { public const uint16 DEFAULT_PORT = 25; - public const uint16 SUBMISSION_PORT = 587; - public const uint16 SECURE_SMTP_PORT = 465; + public const uint16 DEFAULT_PORT_SSL = 465; + public const uint16 DEFAULT_PORT_STARTTLS = 587; public const uint DEFAULT_TIMEOUT_SEC = 60; private Geary.Endpoint endpoint; - private SocketConnection? cx = null; + private IOStream? cx = null; + private SocketConnection? socket_cx = null; private DataInputStream? dins = null; private DataOutputStream douts = null; + private Gee.List? capabilities = null; public ClientConnection(Geary.Endpoint endpoint) { this.endpoint = endpoint; @@ -31,11 +33,8 @@ public class Geary.Smtp.ClientConnection { return null; } - cx = yield endpoint.connect_async(cancellable); - - dins = new DataInputStream(cx.input_stream); - dins.set_newline_type(DataFormat.LINE_TERMINATOR_TYPE); - douts = new DataOutputStream(cx.output_stream); + cx = socket_cx = yield endpoint.connect_async(cancellable); + set_data_streams(cx); // read and deserialize the greeting return Greeting.deserialize(yield read_line_async(cancellable)); @@ -50,16 +49,35 @@ public class Geary.Smtp.ClientConnection { return true; } - + /** * Returns the final Response of the challenge-response. */ public async Response authenticate_async(Authenticator authenticator, Cancellable? cancellable = null) throws Error { check_connected(); - - Response response = yield transaction_async(authenticator.initiate(), cancellable); - + + Response response; + if (endpoint.flags.is_all_set(Endpoint.Flags.STARTTLS)) { + response = yield transaction_async(new Request(Command.STARTTLS)); + if (!response.code.is_starttls_ready()) { + throw new SmtpError.STARTTLS_FAILED("STARTTLS failed: %s", response.to_string()); + } + + // TLS started, lets wrap the connection and shake hands. + TlsClientConnection tls_cx = TlsClientConnection.new(cx, socket_cx.get_remote_address()); + cx = tls_cx; + tls_cx.set_validation_flags(TlsCertificateFlags.UNKNOWN_CA); + set_data_streams(tls_cx); + yield tls_cx.handshake_async(Priority.DEFAULT, cancellable); + + // Now that we are on an encrypted line we need to say hello again in order to get the + // updated capabilities. + yield say_hello_async(cancellable); + } + + response = yield transaction_async(authenticator.initiate(), cancellable); + // Possible for initiate() Request to: // (a) immediately generate success (due to valid authentication being passed in Request); // (b) immediately fails; @@ -72,17 +90,17 @@ public class Geary.Smtp.ClientConnection { uint8[]? data = authenticator.challenge(step++, response); if (data == null || data.length == 0) data = DataFormat.CANCEL_AUTHENTICATION.data; - + yield Stream.write_all_async(douts, data, 0, -1, Priority.DEFAULT, cancellable); douts.put_string(DataFormat.LINE_TERMINATOR); yield douts.flush_async(Priority.DEFAULT, cancellable); - + response = yield recv_response_async(cancellable); } - + return response; } - + /** * Sends a block of data (mail message) by first issuing the DATA command and transmitting * the block if the appropriate response is sent. The data block should *not* have the SMTP @@ -132,11 +150,35 @@ public class Geary.Smtp.ClientConnection { return new Response(lines); } - + + public async Response say_hello_async(Cancellable? cancellable = null) throws Error { + // try EHLO first, then fall back on HELO + Response response = yield transaction_async(new Request(Command.EHLO), cancellable); + if (response.code.is_success_completed()) { + // save list of caps returned in EHLO command, skipping first line because it's the + // EHLO response + capabilities = new Gee.ArrayList(); + for (int ctr = 1; ctr < response.lines.size; ctr++) { + if (!String.is_empty(response.lines[ctr].explanation)) + capabilities.add(response.lines[ctr].explanation); + } + } else { + response = yield transaction_async(new Request(Command.HELO), cancellable); + if (!response.code.is_success_completed()) + throw new SmtpError.SERVER_ERROR("Refused service: %s", response.to_string()); + } + return response; + } + + public async Response quit_async(Cancellable? cancellable = null) throws Error { + capabilities = null; + return yield transaction_async(new Request(Command.QUIT), cancellable); + } + public async Response transaction_async(Request request, Cancellable? cancellable = null) throws Error { yield send_request_async(request, cancellable); - + return yield recv_response_async(cancellable); } @@ -158,5 +200,13 @@ public class Geary.Smtp.ClientConnection { public string to_string() { return endpoint.to_string(); } + + private void set_data_streams(IOStream stream) { + dins = new DataInputStream(stream.input_stream); + dins.set_newline_type(DataFormat.LINE_TERMINATOR_TYPE); + dins.set_close_base_stream(false); + douts = new DataOutputStream(stream.output_stream); + douts.set_close_base_stream(false); + } } diff --git a/src/engine/smtp/smtp-client-session.vala b/src/engine/smtp/smtp-client-session.vala index bdcc0594..84579876 100644 --- a/src/engine/smtp/smtp-client-session.vala +++ b/src/engine/smtp/smtp-client-session.vala @@ -6,7 +6,6 @@ public class Geary.Smtp.ClientSession { private ClientConnection cx; - private Gee.List? capabilities = null; private bool rset_required = false; public virtual signal void connected(Greeting greeting) { @@ -37,35 +36,21 @@ public class Geary.Smtp.ClientSession { public async Greeting? login_async(Credentials? creds, Cancellable? cancellable = null) throws Error { if (cx.is_connected()) throw new SmtpError.ALREADY_CONNECTED("Connection to %s already exists", to_string()); - + + // Greet the SMTP server. Greeting? greeting = yield cx.connect_async(cancellable); if (greeting == null) throw new SmtpError.ALREADY_CONNECTED("Connection to %s already exists", to_string()); - - // try EHLO first, then fall back on HELO - Response response = yield cx.transaction_async(new Request(Command.EHLO), cancellable); - if (response.code.is_success_completed()) { - // save list of caps returned in EHLO command, skipping first line because it's the - // EHLO response - capabilities = new Gee.ArrayList(); - for (int ctr = 1; ctr < response.lines.size; ctr++) { - if (!String.is_empty(response.lines[ctr].explanation)) - capabilities.add(response.lines[ctr].explanation); - } - } else { - response = yield cx.transaction_async(new Request(Command.HELO), cancellable); - if (!response.code.is_success_completed()) - throw new SmtpError.SERVER_ERROR("Refused service: %s", response.to_string()); - } - + yield cx.say_hello_async(cancellable); + notify_connected(greeting); - + // authenticate if credentials supplied (in almost every case they should be) // TODO: Select an authentication method based on AUTH capabilities line, falling back on // LOGIN or PLAIN if none match or are present if (creds != null) { Authenticator authenticator = new LoginAuthenticator(creds); - response = yield cx.authenticate_async(authenticator, cancellable); + Response response = yield cx.authenticate_async(authenticator, cancellable); if (!response.code.is_success_completed()) throw new SmtpError.AUTHENTICATION_FAILED("Unable to authenticate with %s", to_string()); @@ -74,11 +59,11 @@ public class Geary.Smtp.ClientSession { return greeting; } - + public async Response? logout_async(Cancellable? cancellable = null) throws Error { Response? response = null; try { - response = yield cx.transaction_async(new Request(Command.QUIT), cancellable); + response = yield cx.quit_async(cancellable); } catch (Error err) { // catch because although error occurred, still attempt to close the connection message("Unable to QUIT: %s", err.message); @@ -93,8 +78,7 @@ public class Geary.Smtp.ClientSession { } rset_required = false; - capabilities = null; - + return response; } diff --git a/src/engine/smtp/smtp-command.vala b/src/engine/smtp/smtp-command.vala index 88c27990..012610f6 100644 --- a/src/engine/smtp/smtp-command.vala +++ b/src/engine/smtp/smtp-command.vala @@ -14,7 +14,8 @@ public enum Geary.Smtp.Command { AUTH, MAIL, RCPT, - DATA; + DATA, + STARTTLS; public string serialize() { switch (this) { @@ -47,7 +48,10 @@ public enum Geary.Smtp.Command { case DATA: return "data"; - + + case STARTTLS: + return "starttls"; + default: assert_not_reached(); } @@ -84,7 +88,10 @@ public enum Geary.Smtp.Command { case "data": return DATA; - + + case "starttls": + return STARTTLS; + default: throw new SmtpError.PARSE_ERROR("Unknown command \"%s\"", str); } diff --git a/src/engine/smtp/smtp-error.vala b/src/engine/smtp/smtp-error.vala index cbcb691d..e3538f15 100644 --- a/src/engine/smtp/smtp-error.vala +++ b/src/engine/smtp/smtp-error.vala @@ -6,6 +6,7 @@ public errordomain Geary.SmtpError { PARSE_ERROR, + STARTTLS_FAILED, AUTHENTICATION_FAILED, SERVER_ERROR, ALREADY_CONNECTED, diff --git a/src/engine/smtp/smtp-response-code.vala b/src/engine/smtp/smtp-response-code.vala index a4c800c3..c731060a 100644 --- a/src/engine/smtp/smtp-response-code.vala +++ b/src/engine/smtp/smtp-response-code.vala @@ -11,6 +11,7 @@ public class Geary.Smtp.ResponseCode { public const int MAX = 599; public const string START_DATA_CODE = "354"; + public const string STARTTLS_READY_CODE = "220"; public enum Status { POSITIVE_PRELIMINARY = 1, @@ -95,11 +96,15 @@ public class Geary.Smtp.ResponseCode { return false; } } - + public bool is_start_data() { return str == START_DATA_CODE; } - + + public bool is_starttls_ready() { + return str == STARTTLS_READY_CODE; + } + public string serialize() { return str; } diff --git a/src/mailer/main.vala b/src/mailer/main.vala index a1e4899d..15ed0982 100644 --- a/src/mailer/main.vala +++ b/src/mailer/main.vala @@ -47,6 +47,7 @@ void on_main_completed(Object? object, AsyncResult result) { string arg_hostname; int arg_port = 25; +bool arg_debug = false; bool arg_gmail = false; bool arg_no_tls = false; string arg_user; @@ -55,6 +56,7 @@ string arg_from; string arg_to; int arg_count = 1; const OptionEntry[] options = { + { "debug", 0, 0, OptionArg.NONE, ref arg_debug, "Output debugging information", null }, { "host", 'h', 0, OptionArg.STRING, ref arg_hostname, "SMTP server host", "" }, { "port", 'P', 0, OptionArg.INT, ref arg_port, "SMTP server port", "" }, { "gmail", 'G', 0, OptionArg.NONE, ref arg_gmail, "Gmail SMTP (no-tls ignored)", null }, @@ -105,18 +107,22 @@ int main(string[] args) { arg_count = 1; if (arg_gmail) { - endpoint = new Geary.Endpoint("smtp.gmail.com", Geary.Smtp.ClientConnection.SECURE_SMTP_PORT, - Geary.Endpoint.Flags.TLS | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, + endpoint = new Geary.Endpoint("smtp.gmail.com", Geary.Smtp.ClientConnection.DEFAULT_PORT_STARTTLS, + Geary.Endpoint.Flags.STARTTLS | Geary.Endpoint.Flags.GRACEFUL_DISCONNECT, Geary.Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC); } else { Geary.Endpoint.Flags flags = Geary.Endpoint.Flags.GRACEFUL_DISCONNECT; if (!arg_no_tls) - flags |= Geary.Endpoint.Flags.TLS; + flags |= Geary.Endpoint.Flags.SSL; endpoint = new Geary.Endpoint(arg_hostname, (uint16) arg_port, flags, Geary.Smtp.ClientConnection.DEFAULT_TIMEOUT_SEC); } - + + stdout.printf("Enabling debug: %s\n", arg_debug.to_string()); + if (arg_debug) + Geary.Logging.log_to(stdout); + credentials = new Geary.Credentials(arg_user, arg_pass); composed_email = new Geary.ComposedEmail(new DateTime.now_local(), diff --git a/ui/login.glade b/ui/login.glade index 9e29f779..cb4e1285 100644 --- a/ui/login.glade +++ b/ui/login.glade @@ -293,7 +293,7 @@ - + SS_L/TLS encryption False True @@ -398,7 +398,7 @@ - + SS_L/TLS encryption False True