1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-smtp-transport.c : class for a smtp transport */
5 * Authors: Jeffrey Stedfast <fejj@ximian.com>
7 * Copyright (C) 2000 Ximian, Inc. (www.ximian.com)
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of version 2 of the GNU Lesser General Public
11 * License as published by the Free Software Foundation.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
35 #include <sys/param.h>
36 #include <sys/types.h>
38 #include <glib/gi18n-lib.h>
43 #include "camel-exception.h"
44 #include "camel-mime-filter-crlf.h"
45 #include "camel-mime-message.h"
46 #include "camel-mime-part.h"
47 #include "camel-multipart.h"
48 #include "camel-net-utils.h"
49 #include "camel-operation.h"
50 #include "camel-sasl.h"
51 #include "camel-session.h"
52 #include "camel-smtp-transport.h"
53 #include "camel-stream-buffer.h"
54 #include "camel-stream-filter.h"
55 #include "camel-tcp-stream-raw.h"
56 #include "camel-tcp-stream.h"
59 #include "camel-tcp-stream-ssl.h"
62 extern int camel_verbose_debug;
63 #define d(x) (camel_verbose_debug ? (x) : 0)
65 /* Specified in RFC 821 */
66 #define SMTP_PORT "25"
67 #define SMTPS_PORT "465"
69 /* camel smtp transport class prototypes */
70 static gboolean smtp_send_to (CamelTransport *transport, CamelMimeMessage *message,
71 CamelAddress *from, CamelAddress *recipients, CamelException *ex);
73 /* support prototypes */
74 static void smtp_construct (CamelService *service, CamelSession *session,
75 CamelProvider *provider, CamelURL *url,
77 static gboolean smtp_connect (CamelService *service, CamelException *ex);
78 static gboolean smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex);
79 static GHashTable *esmtp_get_authtypes (const unsigned char *buffer);
80 static GList *query_auth_types (CamelService *service, CamelException *ex);
81 static char *get_name (CamelService *service, gboolean brief);
83 static gboolean smtp_helo (CamelSmtpTransport *transport, CamelException *ex);
84 static gboolean smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex);
85 static gboolean smtp_mail (CamelSmtpTransport *transport, const char *sender,
86 gboolean has_8bit_parts, CamelException *ex);
87 static gboolean smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex);
88 static gboolean smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex);
89 static gboolean smtp_rset (CamelSmtpTransport *transport, CamelException *ex);
90 static gboolean smtp_quit (CamelSmtpTransport *transport, CamelException *ex);
92 static void smtp_set_exception (CamelSmtpTransport *transport, gboolean disconnect, const char *respbuf,
93 const char *message, CamelException *ex);
95 /* private data members */
96 static CamelTransportClass *parent_class = NULL;
99 camel_smtp_transport_class_init (CamelSmtpTransportClass *camel_smtp_transport_class)
101 CamelTransportClass *camel_transport_class =
102 CAMEL_TRANSPORT_CLASS (camel_smtp_transport_class);
103 CamelServiceClass *camel_service_class =
104 CAMEL_SERVICE_CLASS (camel_smtp_transport_class);
106 parent_class = CAMEL_TRANSPORT_CLASS (camel_type_get_global_classfuncs (camel_transport_get_type ()));
108 /* virtual method overload */
109 camel_service_class->construct = smtp_construct;
110 camel_service_class->connect = smtp_connect;
111 camel_service_class->disconnect = smtp_disconnect;
112 camel_service_class->query_auth_types = query_auth_types;
113 camel_service_class->get_name = get_name;
115 camel_transport_class->send_to = smtp_send_to;
119 camel_smtp_transport_init (gpointer object)
121 CamelSmtpTransport *smtp = CAMEL_SMTP_TRANSPORT (object);
124 smtp->connected = FALSE;
128 camel_smtp_transport_get_type (void)
130 static CamelType type = CAMEL_INVALID_TYPE;
132 if (type == CAMEL_INVALID_TYPE) {
133 type = camel_type_register (CAMEL_TRANSPORT_TYPE,
134 "CamelSmtpTransport",
135 sizeof (CamelSmtpTransport),
136 sizeof (CamelSmtpTransportClass),
137 (CamelObjectClassInitFunc) camel_smtp_transport_class_init,
139 (CamelObjectInitFunc) camel_smtp_transport_init,
147 smtp_construct (CamelService *service, CamelSession *session,
148 CamelProvider *provider, CamelURL *url,
151 CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
155 smtp_error_string (int error)
157 /* SMTP error codes grabbed from rfc821 */
160 /* looks like a read problem, check errno */
162 return g_strerror (errno);
166 return _("Syntax error, command unrecognized");
168 return _("Syntax error in parameters or arguments");
170 return _("Command not implemented");
172 return _("Command parameter not implemented");
174 return _("System status, or system help reply");
176 return _("Help message");
178 return _("Service ready");
180 return _("Service closing transmission channel");
182 return _("Service not available, closing transmission channel");
184 return _("Requested mail action okay, completed");
186 return _("User not local; will forward to <forward-path>");
188 return _("Requested mail action not taken: mailbox unavailable");
190 return _("Requested action not taken: mailbox unavailable");
192 return _("Requested action aborted: error in processing");
194 return _("User not local; please try <forward-path>");
196 return _("Requested action not taken: insufficient system storage");
198 return _("Requested mail action aborted: exceeded storage allocation");
200 return _("Requested action not taken: mailbox name not allowed");
202 return _("Start mail input; end with <CRLF>.<CRLF>");
204 return _("Transaction failed");
206 /* AUTH error codes: */
208 return _("A password transition is needed");
210 return _("Authentication mechanism is too weak");
212 return _("Encryption required for requested authentication mechanism");
214 return _("Temporary authentication failure");
216 return _("Authentication required");
230 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
231 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
235 connect_to_server (CamelService *service, struct addrinfo *ai, int ssl_mode, CamelException *ex)
237 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
238 CamelStream *tcp_stream;
239 char *respbuf = NULL;
242 if (!CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex))
245 /* set some smtp transport defaults */
246 transport->flags = 0;
247 transport->authtypes = NULL;
249 if (ssl_mode != MODE_CLEAR) {
251 if (ssl_mode == MODE_TLS) {
252 tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
254 tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
257 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
258 _("Could not connect to %s: %s"),
259 service->url->host, _("SSL unavailable"));
262 #endif /* HAVE_SSL */
264 tcp_stream = camel_tcp_stream_raw_new ();
267 if ((ret = camel_tcp_stream_connect ((CamelTcpStream *) tcp_stream, ai)) == -1) {
269 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
270 _("Connection canceled"));
272 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
273 _("Could not connect to %s: %s"),
274 service->url->host, g_strerror (errno));
276 camel_object_unref (tcp_stream);
281 transport->connected = TRUE;
283 /* get the localaddr - needed later by smtp_helo */
284 transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream), &transport->localaddrlen);
286 transport->ostream = tcp_stream;
287 transport->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
289 /* Read the greeting, note whether the server is ESMTP or not. */
291 /* Check for "220" */
293 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
294 if (!respbuf || strncmp (respbuf, "220", 3)) {
295 smtp_set_exception (transport, FALSE, respbuf, _("Welcome response error"), ex);
299 } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
302 /* Try sending EHLO */
303 transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
304 if (!smtp_helo (transport, ex)) {
305 if (!transport->connected)
308 /* Fall back to HELO */
309 camel_exception_clear (ex);
310 transport->flags &= ~CAMEL_SMTP_TRANSPORT_IS_ESMTP;
311 if (!smtp_helo (transport, ex) && !transport->connected)
315 /* clear any EHLO/HELO exception and assume that any SMTP errors encountered were non-fatal */
316 camel_exception_clear (ex);
318 if (ssl_mode != MODE_TLS) {
324 if (!(transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS)) {
325 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
326 _("Failed to connect to SMTP server %s in secure mode: %s"),
327 service->url->host, _("STARTTLS not supported"));
329 goto exception_cleanup;
332 d(fprintf (stderr, "sending : STARTTLS\r\n"));
333 if (camel_stream_write (tcp_stream, "STARTTLS\r\n", 10) == -1) {
334 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
335 _("STARTTLS command failed: %s"),
337 goto exception_cleanup;
343 /* Check for "220 Ready for TLS" */
345 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
347 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
349 if (!respbuf || strncmp (respbuf, "220", 3)) {
350 smtp_set_exception (transport, FALSE, respbuf, _("STARTTLS command failed"), ex);
352 goto exception_cleanup;
354 } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
356 /* Okay, now toggle SSL/TLS mode */
357 if (camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)) == -1) {
358 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
359 _("Failed to connect to SMTP server %s in secure mode: %s"),
360 service->url->host, g_strerror (errno));
361 goto exception_cleanup;
364 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
365 _("Failed to connect to SMTP server %s in secure mode: %s"),
366 service->url->host, _("SSL is not available in this build"));
367 goto exception_cleanup;
368 #endif /* HAVE_SSL */
370 /* We are supposed to re-EHLO after a successful STARTTLS to
371 re-fetch any supported extensions. */
372 if (!smtp_helo (transport, ex) && !transport->connected)
379 camel_object_unref (transport->istream);
380 transport->istream = NULL;
381 camel_object_unref (transport->ostream);
382 transport->ostream = NULL;
384 transport->connected = FALSE;
395 { "", "smtps", SMTPS_PORT, MODE_SSL }, /* really old (1.x) */
396 { "always", "smtps", SMTPS_PORT, MODE_SSL },
397 { "when-possible", "smtp", SMTP_PORT, MODE_TLS },
398 { "never", "smtp", SMTP_PORT, MODE_CLEAR },
399 { NULL, "smtp", SMTP_PORT, MODE_CLEAR },
403 connect_to_server_wrapper (CamelService *service, CamelException *ex)
405 struct addrinfo hints, *ai;
406 const char *ssl_mode;
411 if ((ssl_mode = camel_url_get_param (service->url, "use_ssl"))) {
412 for (i = 0; ssl_options[i].value; i++)
413 if (!strcmp (ssl_options[i].value, ssl_mode))
415 mode = ssl_options[i].mode;
416 serv = ssl_options[i].serv;
417 port = ssl_options[i].port;
424 if (service->url->port) {
425 serv = g_alloca (16);
426 sprintf (serv, "%d", service->url->port);
430 memset (&hints, 0, sizeof (hints));
431 hints.ai_socktype = SOCK_STREAM;
432 hints.ai_family = PF_UNSPEC;
433 ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
434 if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) {
435 camel_exception_clear (ex);
436 ai = camel_getaddrinfo(service->url->host, port, &hints, ex);
442 ret = connect_to_server (service, ai, mode, ex);
444 camel_freeaddrinfo (ai);
450 smtp_connect (CamelService *service, CamelException *ex)
452 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
453 gboolean has_authtypes;
455 /* We (probably) need to check popb4smtp before we connect ... */
456 if (service->url->authmech && !strcmp (service->url->authmech, "POPB4SMTP")) {
461 sasl = camel_sasl_new ("smtp", "POPB4SMTP", service);
462 chal = camel_sasl_challenge (sasl, NULL, ex);
463 truth = camel_sasl_authenticated (sasl);
465 g_byte_array_free (chal, TRUE);
466 camel_object_unref (sasl);
471 return connect_to_server_wrapper (service, ex);
474 if (!connect_to_server_wrapper (service, ex))
477 /* check to see if AUTH is required, if so...then AUTH ourselves */
478 has_authtypes = transport->authtypes ? g_hash_table_size (transport->authtypes) > 0 : FALSE;
479 if (service->url->authmech && (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) && has_authtypes) {
480 CamelSession *session = camel_service_get_session (service);
481 CamelServiceAuthType *authtype;
482 gboolean authenticated = FALSE;
485 if (!g_hash_table_lookup (transport->authtypes, service->url->authmech)) {
486 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
487 _("SMTP server %s does not support requested "
488 "authentication type %s."),
489 service->url->host, service->url->authmech);
490 camel_service_disconnect (service, TRUE, NULL);
494 authtype = camel_sasl_authtype (service->url->authmech);
496 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
497 _("No support for authentication type %s"),
498 service->url->authmech);
499 camel_service_disconnect (service, TRUE, NULL);
503 if (!authtype->need_password) {
504 /* authentication mechanism doesn't need a password,
505 so if it fails there's nothing we can do */
506 authenticated = smtp_auth (transport, authtype->authproto, ex);
507 if (!authenticated) {
508 camel_service_disconnect (service, TRUE, NULL);
513 /* keep trying to login until either we succeed or the user cancels */
514 while (!authenticated) {
516 /* We need to un-cache the password before prompting again */
517 camel_session_forget_password (session, service, NULL, "password", NULL);
518 g_free (service->url->passwd);
519 service->url->passwd = NULL;
522 if (!service->url->passwd) {
525 prompt = g_strdup_printf (_("%sPlease enter the SMTP password for %s on host %s"),
526 errbuf ? errbuf : "", service->url->user,
529 service->url->passwd = camel_session_get_password (session, service, NULL,
530 prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, ex);
536 if (!service->url->passwd) {
537 camel_service_disconnect (service, TRUE, NULL);
542 authenticated = smtp_auth (transport, authtype->authproto, ex);
543 if (!authenticated) {
544 errbuf = g_strdup_printf (_("Unable to authenticate "
545 "to SMTP server.\n%s\n\n"),
546 camel_exception_get_description (ex));
547 camel_exception_clear (ex);
556 authtypes_free (gpointer key, gpointer value, gpointer data)
562 smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex)
564 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
566 /*if (!service->connected)
570 if (transport->connected && clean) {
571 /* send the QUIT command to the SMTP server */
572 smtp_quit (transport, ex);
575 if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
578 if (transport->authtypes) {
579 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
580 g_hash_table_destroy (transport->authtypes);
581 transport->authtypes = NULL;
584 if (transport->istream) {
585 camel_object_unref (transport->istream);
586 transport->istream = NULL;
589 if (transport->ostream) {
590 camel_object_unref (transport->ostream);
591 transport->ostream = NULL;
594 g_free(transport->localaddr);
595 transport->localaddr = NULL;
597 transport->connected = FALSE;
603 esmtp_get_authtypes (const unsigned char *buffer)
605 const unsigned char *start, *end;
606 GHashTable *table = NULL;
608 /* advance to the first token */
610 while (isspace ((int) *start) || *start == '=')
616 table = g_hash_table_new (g_str_hash, g_str_equal);
621 /* advance to the end of the token */
623 while (*end && !isspace ((int) *end))
626 type = g_strndup (start, end - start);
627 g_hash_table_insert (table, type, type);
629 /* advance to the next token */
631 while (isspace ((int) *start))
639 query_auth_types (CamelService *service, CamelException *ex)
641 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
642 CamelServiceAuthType *authtype;
643 GList *types, *t, *next;
645 if (!connect_to_server_wrapper (service, ex))
648 types = g_list_copy (service->provider->authtypes);
649 for (t = types; t; t = next) {
653 if (!g_hash_table_lookup (transport->authtypes, authtype->authproto)) {
654 types = g_list_remove_link (types, t);
659 smtp_disconnect (service, TRUE, NULL);
665 get_name (CamelService *service, gboolean brief)
668 return g_strdup_printf (_("SMTP server %s"), service->url->host);
670 return g_strdup_printf (_("SMTP mail delivery via %s"),
676 smtp_send_to (CamelTransport *transport, CamelMimeMessage *message,
677 CamelAddress *from, CamelAddress *recipients,
680 CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (transport);
681 const CamelInternetAddress *cia;
682 gboolean has_8bit_parts;
686 if (!smtp_transport->connected) {
687 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_NOT_CONNECTED,
688 _("Cannot send message: service not connected."));
692 if (!camel_internet_address_get (CAMEL_INTERNET_ADDRESS (from), 0, NULL, &addr)) {
693 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
694 _("Cannot send message: sender address not valid."));
698 camel_operation_start (NULL, _("Sending message"));
700 /* find out if the message has 8bit mime parts */
701 has_8bit_parts = camel_mime_message_has_8bit_parts (message);
703 /* rfc1652 (8BITMIME) requires that you notify the ESMTP daemon that
704 you'll be sending an 8bit mime message at "MAIL FROM:" time. */
705 if (!smtp_mail (smtp_transport, addr, has_8bit_parts, ex)) {
706 camel_operation_end (NULL);
710 len = camel_address_length (recipients);
712 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
713 _("Cannot send message: no recipients defined."));
714 camel_operation_end (NULL);
718 cia = CAMEL_INTERNET_ADDRESS (recipients);
719 for (i = 0; i < len; i++) {
722 if (!camel_internet_address_get (cia, i, NULL, &addr)) {
723 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
724 _("Cannot send message: one or more invalid recipients"));
725 camel_operation_end (NULL);
729 enc = camel_internet_address_encode_address(NULL, NULL, addr);
730 if (!smtp_rcpt (smtp_transport, enc, ex)) {
732 camel_operation_end (NULL);
738 if (!smtp_data (smtp_transport, message, ex)) {
739 camel_operation_end (NULL);
743 /* reset the service for our next transfer session */
744 if (!smtp_rset (smtp_transport, ex))
745 camel_exception_clear (ex);
747 camel_operation_end (NULL);
753 smtp_next_token (const char *buf)
755 const unsigned char *token;
757 token = (const unsigned char *) buf;
758 while (*token && !isspace ((int) *token))
761 while (*token && isspace ((int) *token))
764 return (const char *) token;
767 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : (c) - 'A' + 10)
771 * 5.1.1 Mailbox "nosuchuser" does not exist
773 * The human-readable status code is what we want. Since this text
774 * could possibly be encoded, we must decode it.
776 * "xtext" is formally defined as follows:
778 * xtext = *( xchar / hexchar / linear-white-space / comment )
780 * xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
781 * except for "+", "\" and "(".
783 * "hexchar"s are intended to encode octets that cannot be represented
784 * as plain text, either because they are reserved, or because they are
785 * non-printable. However, any octet value may be represented by a
788 * hexchar = ASCII "+" immediately followed by two upper case
792 smtp_decode_status_code (const char *in, size_t len)
794 unsigned char *inptr, *outptr;
795 const unsigned char *inend;
798 outptr = outbuf = g_malloc (len + 1);
800 inptr = (unsigned char *) in;
802 while (inptr < inend) {
804 if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
805 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
808 *outptr++ = *inptr++;
810 *outptr++ = *inptr++;
819 smtp_set_exception (CamelSmtpTransport *transport, gboolean disconnect, const char *respbuf, const char *message, CamelException *ex)
821 const char *token, *rbuf = respbuf;
826 if (!respbuf || !(transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES)) {
828 error = respbuf ? atoi (respbuf) : 0;
829 camel_exception_setv (ex, error == 0 && errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
830 "%s: %s", message, smtp_error_string (error));
832 string = g_string_new ("");
834 token = smtp_next_token (rbuf + 4);
835 if (*token == '\0') {
837 g_string_free (string, TRUE);
838 goto fake_status_code;
841 g_string_append (string, token);
842 if (*(rbuf + 3) == '-') {
844 buffer = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
845 g_string_append_c (string, '\n');
854 buffer = smtp_decode_status_code (string->str, string->len);
855 g_string_free (string, TRUE);
857 goto fake_status_code;
859 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
860 "%s: %s", message, buffer);
866 /* we got disconnected */
868 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
870 transport->connected = FALSE;
875 smtp_helo (CamelSmtpTransport *transport, CamelException *ex)
877 char *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
878 const char *token, *numeric = NULL;
879 struct sockaddr *addr;
882 /* these are flags that we set, so unset them in case we
883 are being called a second time (ie, after a STARTTLS) */
884 transport->flags &= ~(CAMEL_SMTP_TRANSPORT_8BITMIME |
885 CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES |
886 CAMEL_SMTP_TRANSPORT_STARTTLS);
888 if (transport->authtypes) {
889 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
890 g_hash_table_destroy (transport->authtypes);
891 transport->authtypes = NULL;
894 camel_operation_start_transient (NULL, _("SMTP Greeting"));
896 addr = transport->localaddr;
897 addrlen = transport->localaddrlen;
899 if (camel_getnameinfo (addr, addrlen, &name, NULL, NI_NUMERICHOST, NULL) != 0) {
900 name = g_strdup ("localhost.localdomain");
902 if (addr->sa_family == AF_INET6)
908 token = (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) ? "EHLO" : "HELO";
910 cmdbuf = g_strdup_printf("%s [%s%s]\r\n", token, numeric, name);
912 cmdbuf = g_strdup_printf("%s %s\r\n", token, name);
915 d(fprintf (stderr, "sending : %s", cmdbuf));
916 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
918 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
919 _("HELO command failed: %s"), g_strerror (errno));
920 camel_operation_end (NULL);
922 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
929 /* Check for "250" */
931 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
933 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
935 if (!respbuf || strncmp (respbuf, "250", 3)) {
936 smtp_set_exception (transport, FALSE, respbuf, _("HELO command failed"), ex);
937 camel_operation_end (NULL);
945 if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) {
946 if (!strncmp (token, "8BITMIME", 8)) {
947 d(fprintf (stderr, "This server supports 8bit MIME\n"));
948 transport->flags |= CAMEL_SMTP_TRANSPORT_8BITMIME;
949 } else if (!strncmp (token, "ENHANCEDSTATUSCODES", 19)) {
950 d(fprintf (stderr, "This server supports enhanced status codes\n"));
951 transport->flags |= CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES;
952 } else if (!strncmp (token, "STARTTLS", 8)) {
953 d(fprintf (stderr, "This server supports STARTTLS\n"));
954 transport->flags |= CAMEL_SMTP_TRANSPORT_STARTTLS;
955 } else if (!strncmp (token, "AUTH", 4)) {
956 if (!transport->authtypes || transport->flags & CAMEL_SMTP_TRANSPORT_AUTH_EQUAL) {
957 /* Don't bother parsing any authtypes if we already have a list.
958 * Some servers will list AUTH twice, once the standard way and
959 * once the way Microsoft Outlook requires them to be:
961 * 250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-MD5
962 * 250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-MD5
964 * Since they can come in any order, parse each list that we get
965 * until we parse an authtype list that does not use the AUTH=
966 * format. We want to let the standard way have priority over the
971 transport->flags |= CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
973 transport->flags &= ~CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
975 /* parse for supported AUTH types */
978 if (transport->authtypes) {
979 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
980 g_hash_table_destroy (transport->authtypes);
983 transport->authtypes = esmtp_get_authtypes (token);
987 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
990 camel_operation_end (NULL);
996 smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex)
998 char *cmdbuf, *respbuf = NULL, *challenge;
999 gboolean auth_challenge = FALSE;
1000 CamelSasl *sasl = NULL;
1002 camel_operation_start_transient (NULL, _("SMTP Authentication"));
1004 sasl = camel_sasl_new ("smtp", mech, CAMEL_SERVICE (transport));
1006 camel_operation_end (NULL);
1007 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1008 _("Error creating SASL authentication object."));
1012 challenge = camel_sasl_challenge_base64 (sasl, NULL, ex);
1014 auth_challenge = TRUE;
1015 cmdbuf = g_strdup_printf ("AUTH %s %s\r\n", mech, challenge);
1018 cmdbuf = g_strdup_printf ("AUTH %s\r\n", mech);
1021 d(fprintf (stderr, "sending : %s", cmdbuf));
1022 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1024 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1025 _("AUTH command failed: %s"), g_strerror (errno));
1030 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1031 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1033 while (!camel_sasl_authenticated (sasl)) {
1035 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1036 _("AUTH command failed: %s"), g_strerror (errno));
1040 /* the server challenge/response should follow a 334 code */
1041 if (strncmp (respbuf, "334", 3) != 0) {
1042 smtp_set_exception (transport, FALSE, respbuf, _("AUTH command failed"), ex);
1049 d(fprintf (stderr, "Your SMTP server's implementation of the %s SASL\n"
1050 "authentication mechanism is broken. Please report this to the\n"
1051 "appropriate vendor and suggest that they re-read rfc2554 again\n"
1052 "for the first time (specifically Section 4).\n",
1057 for (challenge = respbuf + 4; isspace (*challenge); challenge++);
1059 challenge = camel_sasl_challenge_base64 (sasl, challenge, ex);
1061 if (challenge == NULL)
1062 goto break_and_lose;
1064 /* send our challenge */
1065 cmdbuf = g_strdup_printf ("%s\r\n", challenge);
1067 d(fprintf (stderr, "sending : %s", cmdbuf));
1068 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1074 /* get the server's response */
1075 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1076 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1079 /* check that the server says we are authenticated */
1080 if (!respbuf || strncmp (respbuf, "235", 3)) {
1081 if (respbuf && auth_challenge && !strncmp (respbuf, "334", 3)) {
1082 /* broken server, but lets try and work around it anyway... */
1083 goto broken_smtp_server;
1090 camel_object_unref (sasl);
1091 camel_operation_end (NULL);
1096 /* Get the server out of "waiting for continuation data" mode. */
1097 d(fprintf (stderr, "sending : *\n"));
1098 camel_stream_write (transport->ostream, "*\r\n", 3);
1099 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1100 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1104 if (!camel_exception_is_set (ex)) {
1105 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1106 _("Bad authentication response from server.\n"));
1109 camel_object_unref (sasl);
1110 camel_operation_end (NULL);
1116 smtp_mail (CamelSmtpTransport *transport, const char *sender, gboolean has_8bit_parts, CamelException *ex)
1118 /* we gotta tell the smtp server who we are. (our email addy) */
1119 char *cmdbuf, *respbuf = NULL;
1121 if (transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME && has_8bit_parts)
1122 cmdbuf = g_strdup_printf ("MAIL FROM:<%s> BODY=8BITMIME\r\n", sender);
1124 cmdbuf = g_strdup_printf ("MAIL FROM:<%s>\r\n", sender);
1126 d(fprintf (stderr, "sending : %s", cmdbuf));
1128 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1130 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1131 _("MAIL FROM command failed: %s: mail not sent"),
1132 g_strerror (errno));
1134 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1141 /* Check for "250 Sender OK..." */
1143 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1145 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1147 if (!respbuf || strncmp (respbuf, "250", 3)) {
1148 smtp_set_exception (transport, TRUE, respbuf, _("MAIL FROM command failed"), ex);
1152 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1159 smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex)
1161 /* we gotta tell the smtp server who we are going to be sending
1163 char *cmdbuf, *respbuf = NULL;
1165 cmdbuf = g_strdup_printf ("RCPT TO:<%s>\r\n", recipient);
1167 d(fprintf (stderr, "sending : %s", cmdbuf));
1169 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1171 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1172 _("RCPT TO command failed: %s: mail not sent"),
1173 g_strerror (errno));
1175 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1182 /* Check for "250 Recipient OK..." */
1184 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1186 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1188 if (!respbuf || strncmp (respbuf, "250", 3)) {
1191 message = g_strdup_printf (_("RCPT TO <%s> failed"), recipient);
1192 smtp_set_exception (transport, TRUE, respbuf, message, ex);
1197 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1204 smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex)
1206 CamelBestencEncoding enctype = CAMEL_BESTENC_8BIT;
1207 struct _camel_header_raw *header, *savedbcc, *n, *tail;
1208 char *cmdbuf, *respbuf = NULL;
1209 CamelStreamFilter *filtered_stream;
1210 CamelMimeFilter *crlffilter;
1213 /* If the server doesn't support 8BITMIME, set our required encoding to be 7bit */
1214 if (!(transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME))
1215 enctype = CAMEL_BESTENC_7BIT;
1217 /* FIXME: should we get the best charset too?? */
1218 /* Changes the encoding of all mime parts to fit within our required
1219 encoding type and also force any text parts with long lines (longer
1220 than 998 octets) to wrap by QP or base64 encoding them. */
1221 camel_mime_message_set_best_encoding (message, CAMEL_BESTENC_GET_ENCODING, enctype);
1223 cmdbuf = g_strdup ("DATA\r\n");
1225 d(fprintf (stderr, "sending : %s", cmdbuf));
1227 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1229 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1230 _("DATA command failed: %s: mail not sent"),
1231 g_strerror (errno));
1233 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1239 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1241 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1243 if (!respbuf || strncmp (respbuf, "354", 3)) {
1244 /* we should have gotten instructions on how to use the DATA command:
1245 * 354 Enter mail, end with "." on a line by itself
1247 smtp_set_exception (transport, TRUE, respbuf, _("DATA command failed"), ex);
1255 /* setup stream filtering */
1256 crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
1257 filtered_stream = camel_stream_filter_new_with_stream (transport->ostream);
1258 camel_stream_filter_add (filtered_stream, CAMEL_MIME_FILTER (crlffilter));
1259 camel_object_unref (crlffilter);
1261 /* unlink the bcc headers */
1263 tail = (struct _camel_header_raw *) &savedbcc;
1265 header = (struct _camel_header_raw *) &CAMEL_MIME_PART (message)->headers;
1268 if (!g_ascii_strcasecmp (n->name, "Bcc")) {
1269 header->next = n->next;
1280 /* write the message */
1281 ret = camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), CAMEL_STREAM (filtered_stream));
1283 /* restore the bcc headers */
1284 header->next = savedbcc;
1287 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1288 _("DATA command failed: %s: mail not sent"),
1289 g_strerror (errno));
1291 camel_object_unref (filtered_stream);
1293 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1298 camel_stream_flush (CAMEL_STREAM (filtered_stream));
1299 camel_object_unref (filtered_stream);
1301 /* terminate the message body */
1303 d(fprintf (stderr, "sending : \\r\\n.\\r\\n\n"));
1305 if (camel_stream_write (transport->ostream, "\r\n.\r\n", 5) == -1) {
1306 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1307 _("DATA command failed: %s: mail not sent"),
1308 g_strerror (errno));
1310 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1316 /* Check for "250 Sender OK..." */
1318 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1320 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1322 if (!respbuf || strncmp (respbuf, "250", 3)) {
1323 smtp_set_exception (transport, TRUE, respbuf, _("DATA command failed"), ex);
1327 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1334 smtp_rset (CamelSmtpTransport *transport, CamelException *ex)
1336 /* we are going to reset the smtp server (just to be nice) */
1337 char *cmdbuf, *respbuf = NULL;
1339 cmdbuf = g_strdup ("RSET\r\n");
1341 d(fprintf (stderr, "sending : %s", cmdbuf));
1343 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1345 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1346 _("RSET command failed: %s"), g_strerror (errno));
1348 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1355 /* Check for "250" */
1357 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1359 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1361 if (!respbuf || strncmp (respbuf, "250", 3)) {
1362 smtp_set_exception (transport, TRUE, respbuf, _("RSET command failed"), ex);
1366 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1373 smtp_quit (CamelSmtpTransport *transport, CamelException *ex)
1375 /* we are going to reset the smtp server (just to be nice) */
1376 char *cmdbuf, *respbuf = NULL;
1378 cmdbuf = g_strdup ("QUIT\r\n");
1380 d(fprintf (stderr, "sending : %s", cmdbuf));
1382 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1384 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1385 _("QUIT command failed: %s"), g_strerror (errno));
1392 /* Check for "221" */
1394 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1396 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1398 if (!respbuf || strncmp (respbuf, "221", 3)) {
1399 smtp_set_exception (transport, FALSE, respbuf, _("QUIT command failed"), ex);
1403 } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */