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 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 General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
29 #include <sys/param.h>
30 #include <sys/types.h>
31 #include <sys/socket.h>
32 #include <netinet/in.h>
33 #include <arpa/inet.h>
42 #include "camel-mime-filter-crlf.h"
43 #include "camel-stream-filter.h"
44 #include "camel-smtp-transport.h"
45 #include "camel-mime-message.h"
46 #include "camel-multipart.h"
47 #include "camel-mime-part.h"
48 #include "camel-operation.h"
49 #include "camel-stream-buffer.h"
50 #include "camel-tcp-stream.h"
51 #include "camel-tcp-stream-raw.h"
53 #include "camel-tcp-stream-ssl.h"
55 #include "camel-session.h"
56 #include "camel-exception.h"
57 #include "camel-sasl.h"
60 extern int camel_verbose_debug;
61 #define d(x) (camel_verbose_debug ? (x) : 0)
63 /* Specified in RFC 821 */
66 /* camel smtp transport class prototypes */
67 static gboolean smtp_send_to (CamelTransport *transport, CamelMimeMessage *message,
68 CamelAddress *from, CamelAddress *recipients, CamelException *ex);
70 /* support prototypes */
71 static void smtp_construct (CamelService *service, CamelSession *session,
72 CamelProvider *provider, CamelURL *url,
74 static gboolean smtp_connect (CamelService *service, CamelException *ex);
75 static gboolean smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex);
76 static GHashTable *esmtp_get_authtypes (const unsigned char *buffer);
77 static GList *query_auth_types (CamelService *service, CamelException *ex);
78 static char *get_name (CamelService *service, gboolean brief);
80 static gboolean smtp_helo (CamelSmtpTransport *transport, CamelException *ex);
81 static gboolean smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex);
82 static gboolean smtp_mail (CamelSmtpTransport *transport, const char *sender,
83 gboolean has_8bit_parts, CamelException *ex);
84 static gboolean smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex);
85 static gboolean smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex);
86 static gboolean smtp_rset (CamelSmtpTransport *transport, CamelException *ex);
87 static gboolean smtp_quit (CamelSmtpTransport *transport, CamelException *ex);
89 static void smtp_set_exception (CamelSmtpTransport *transport, const char *respbuf,
90 const char *message, CamelException *ex);
92 /* private data members */
93 static CamelTransportClass *parent_class = NULL;
96 camel_smtp_transport_class_init (CamelSmtpTransportClass *camel_smtp_transport_class)
98 CamelTransportClass *camel_transport_class =
99 CAMEL_TRANSPORT_CLASS (camel_smtp_transport_class);
100 CamelServiceClass *camel_service_class =
101 CAMEL_SERVICE_CLASS (camel_smtp_transport_class);
103 parent_class = CAMEL_TRANSPORT_CLASS (camel_type_get_global_classfuncs (camel_transport_get_type ()));
105 /* virtual method overload */
106 camel_service_class->construct = smtp_construct;
107 camel_service_class->connect = smtp_connect;
108 camel_service_class->disconnect = smtp_disconnect;
109 camel_service_class->query_auth_types = query_auth_types;
110 camel_service_class->get_name = get_name;
112 camel_transport_class->send_to = smtp_send_to;
116 camel_smtp_transport_init (gpointer object)
118 CamelSmtpTransport *smtp = CAMEL_SMTP_TRANSPORT (object);
121 smtp->connected = FALSE;
125 camel_smtp_transport_get_type (void)
127 static CamelType type = CAMEL_INVALID_TYPE;
129 if (type == CAMEL_INVALID_TYPE) {
130 type = camel_type_register (CAMEL_TRANSPORT_TYPE,
131 "CamelSmtpTransport",
132 sizeof (CamelSmtpTransport),
133 sizeof (CamelSmtpTransportClass),
134 (CamelObjectClassInitFunc) camel_smtp_transport_class_init,
136 (CamelObjectInitFunc) camel_smtp_transport_init,
144 smtp_construct (CamelService *service, CamelSession *session,
145 CamelProvider *provider, CamelURL *url,
148 CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (service);
151 CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
153 if ((use_ssl = camel_url_get_param (url, "use_ssl"))) {
154 /* Note: previous versions would use "" to toggle use_ssl to 'on' */
155 if (!*use_ssl || !strcmp (use_ssl, "always"))
156 smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS;
157 else if (!strcmp (use_ssl, "when-possible"))
158 smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE;
163 smtp_error_string (int error)
165 /* SMTP error codes grabbed from rfc821 */
168 /* looks like a read problem, check errno */
170 return g_strerror (errno);
174 return _("Syntax error, command unrecognized");
176 return _("Syntax error in parameters or arguments");
178 return _("Command not implemented");
180 return _("Command parameter not implemented");
182 return _("System status, or system help reply");
184 return _("Help message");
186 return _("Service ready");
188 return _("Service closing transmission channel");
190 return _("Service not available, closing transmission channel");
192 return _("Requested mail action okay, completed");
194 return _("User not local; will forward to <forward-path>");
196 return _("Requested mail action not taken: mailbox unavailable");
198 return _("Requested action not taken: mailbox unavailable");
200 return _("Requested action aborted: error in processing");
202 return _("User not local; please try <forward-path>");
204 return _("Requested action not taken: insufficient system storage");
206 return _("Requested mail action aborted: exceeded storage allocation");
208 return _("Requested action not taken: mailbox name not allowed");
210 return _("Start mail input; end with <CRLF>.<CRLF>");
212 return _("Transaction failed");
214 /* AUTH error codes: */
216 return _("A password transition is needed");
218 return _("Authentication mechanism is too weak");
220 return _("Encryption required for requested authentication mechanism");
222 return _("Temporary authentication failure");
224 return _("Authentication required");
231 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
232 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
235 connect_to_server (CamelService *service, int try_starttls, CamelException *ex)
237 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
238 CamelStream *tcp_stream;
239 char *respbuf = NULL;
243 if (!CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex))
246 h = camel_service_gethost (service, ex);
250 /* set some smtp transport defaults */
251 transport->flags &= CAMEL_SMTP_TRANSPORT_USE_SSL; /* reset all but ssl flags */
252 transport->authtypes = NULL;
254 port = service->url->port ? service->url->port : SMTP_PORT;
256 if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL) {
259 tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
261 port = service->url->port ? service->url->port : 465;
262 tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
266 port = service->url->port ? service->url->port : 465;
268 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
269 _("Could not connect to %s (port %d): %s"),
270 service->url->host, port,
271 _("SSL unavailable"));
276 #endif /* HAVE_SSL */
278 tcp_stream = camel_tcp_stream_raw_new ();
281 ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
284 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
285 _("Could not connect to %s (port %d): %s"),
286 service->url->host, port,
289 camel_object_unref (tcp_stream);
294 transport->connected = TRUE;
296 /* get the localaddr - needed later by smtp_helo */
297 transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream));
299 transport->ostream = tcp_stream;
300 transport->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
302 /* Read the greeting, note whether the server is ESMTP or not. */
304 /* Check for "220" */
306 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
307 if (!respbuf || strncmp (respbuf, "220", 3)) {
308 smtp_set_exception (transport, respbuf, _("Welcome response error"), ex);
312 } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
315 /* Try sending EHLO */
316 transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
317 if (!smtp_helo (transport, ex)) {
318 if (!transport->connected)
321 /* Fall back to HELO */
322 camel_exception_clear (ex);
323 transport->flags &= ~CAMEL_SMTP_TRANSPORT_IS_ESMTP;
324 if (!smtp_helo (transport, ex) && !transport->connected)
328 /* clear any EHLO/HELO exception and assume that any SMTP errors encountered were non-fatal */
329 camel_exception_clear (ex);
332 if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE) {
333 /* try_starttls is always TRUE here */
334 if (transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS)
336 } else if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS) {
338 if (transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS) {
341 /* server doesn't support STARTTLS, abort */
342 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
343 _("Failed to connect to SMTP server %s in secure mode: %s"),
344 service->url->host, _("server does not appear to support SSL"));
345 goto exception_cleanup;
349 #endif /* HAVE_SSL */
355 d(fprintf (stderr, "sending : STARTTLS\r\n"));
356 if (camel_stream_write (tcp_stream, "STARTTLS\r\n", 10) == -1) {
357 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
358 _("STARTTLS request timed out: %s"),
360 goto exception_cleanup;
366 /* Check for "220 Ready for TLS" */
368 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
370 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
372 if (!respbuf || strncmp (respbuf, "220", 3)) {
373 smtp_set_exception (transport, respbuf, _("STARTTLS response error"), ex);
375 goto exception_cleanup;
377 } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
379 /* Okay, now toggle SSL/TLS mode */
380 if (camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)) == -1) {
381 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
382 _("Failed to connect to SMTP server %s in secure mode: %s"),
383 service->url->host, g_strerror (errno));
384 goto exception_cleanup;
387 /* We are supposed to re-EHLO after a successful STARTTLS to
388 re-fetch any supported extensions. */
389 if (!smtp_helo (transport, ex) && !transport->connected)
396 camel_object_unref (transport->istream);
397 transport->istream = NULL;
398 camel_object_unref (transport->ostream);
399 transport->ostream = NULL;
401 transport->connected = FALSE;
404 #endif /* HAVE_SSL */
408 connect_to_server_wrapper (CamelService *service, CamelException *ex)
411 CamelSmtpTransport *transport = (CamelSmtpTransport *) service;
413 if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS) {
414 /* First try connecting to the SSL port */
415 if (!connect_to_server (service, FALSE, ex)) {
416 if (camel_exception_get_id (ex) == CAMEL_EXCEPTION_SERVICE_UNAVAILABLE) {
417 /* Seems the SSL port is unavailable, lets try STARTTLS */
418 camel_exception_clear (ex);
419 return connect_to_server (service, TRUE, ex);
426 } else if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE) {
427 /* If the server supports STARTTLS, use it */
428 return connect_to_server (service, TRUE, ex);
430 /* User doesn't care about SSL */
431 return connect_to_server (service, FALSE, ex);
434 return connect_to_server (service, FALSE, ex);
439 smtp_connect (CamelService *service, CamelException *ex)
441 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
442 gboolean has_authtypes;
444 /* We (probably) need to check popb4smtp before we connect ... */
445 if (service->url->authmech && !strcmp (service->url->authmech, "POPB4SMTP")) {
450 sasl = camel_sasl_new ("smtp", "POPB4SMTP", service);
451 chal = camel_sasl_challenge (sasl, NULL, ex);
452 truth = camel_sasl_authenticated (sasl);
454 g_byte_array_free (chal, TRUE);
455 camel_object_unref (sasl);
460 return connect_to_server_wrapper (service, ex);
463 if (!connect_to_server_wrapper (service, ex))
466 /* check to see if AUTH is required, if so...then AUTH ourselves */
467 has_authtypes = transport->authtypes ? g_hash_table_size (transport->authtypes) > 0 : FALSE;
468 if (service->url->authmech && (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) && has_authtypes) {
469 CamelSession *session = camel_service_get_session (service);
470 CamelServiceAuthType *authtype;
471 gboolean authenticated = FALSE;
474 if (!g_hash_table_lookup (transport->authtypes, service->url->authmech)) {
475 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
476 _("SMTP server %s does not support requested "
477 "authentication type %s."),
478 service->url->host, service->url->authmech);
479 camel_service_disconnect (service, TRUE, NULL);
483 authtype = camel_sasl_authtype (service->url->authmech);
485 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
486 _("No support for authentication type %s"),
487 service->url->authmech);
488 camel_service_disconnect (service, TRUE, NULL);
492 if (!authtype->need_password) {
493 /* authentication mechanism doesn't need a password,
494 so if it fails there's nothing we can do */
495 authenticated = smtp_auth (transport, authtype->authproto, ex);
496 if (!authenticated) {
497 camel_service_disconnect (service, TRUE, NULL);
502 /* keep trying to login until either we succeed or the user cancels */
503 while (!authenticated) {
505 /* We need to un-cache the password before prompting again */
506 camel_session_forget_password (session, service, "password", NULL);
507 g_free (service->url->passwd);
508 service->url->passwd = NULL;
511 if (!service->url->passwd) {
514 prompt = g_strdup_printf (_("%sPlease enter the SMTP password for %s@%s"),
515 errbuf ? errbuf : "", service->url->user,
518 service->url->passwd = camel_session_get_password (session, prompt, FALSE, TRUE,
519 service, "password", ex);
525 if (!service->url->passwd) {
526 camel_service_disconnect (service, TRUE, NULL);
531 authenticated = smtp_auth (transport, authtype->authproto, ex);
532 if (!authenticated) {
533 errbuf = g_strdup_printf (_("Unable to authenticate "
534 "to SMTP server.\n%s\n\n"),
535 camel_exception_get_description (ex));
536 camel_exception_clear (ex);
540 /* The spec says we have to re-EHLO, but some servers
541 * we won't bother to name don't want you to... so ignore
544 if (!smtp_helo (transport, ex) && !transport->connected)
547 camel_exception_clear (ex);
554 authtypes_free (gpointer key, gpointer value, gpointer data)
560 smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex)
562 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
564 /*if (!service->connected)
568 if (transport->connected && clean) {
569 /* send the QUIT command to the SMTP server */
570 smtp_quit (transport, ex);
573 if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
576 if (transport->authtypes) {
577 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
578 g_hash_table_destroy (transport->authtypes);
579 transport->authtypes = NULL;
582 if (transport->istream) {
583 camel_object_unref (transport->istream);
584 transport->istream = NULL;
587 if (transport->ostream) {
588 camel_object_unref (transport->ostream);
589 transport->ostream = NULL;
592 camel_tcp_address_free (transport->localaddr);
593 transport->localaddr = NULL;
595 transport->connected = FALSE;
601 esmtp_get_authtypes (const unsigned char *buffer)
603 const unsigned char *start, *end;
604 GHashTable *table = NULL;
606 /* advance to the first token */
608 while (isspace ((int) *start) || *start == '=')
614 table = g_hash_table_new (g_str_hash, g_str_equal);
619 /* advance to the end of the token */
621 while (*end && !isspace ((int) *end))
624 type = g_strndup (start, end - start);
625 g_hash_table_insert (table, type, type);
627 /* advance to the next token */
629 while (isspace ((int) *start))
637 query_auth_types (CamelService *service, CamelException *ex)
639 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
640 CamelServiceAuthType *authtype;
641 GList *types, *t, *next;
643 if (!connect_to_server_wrapper (service, ex))
646 types = g_list_copy (service->provider->authtypes);
647 for (t = types; t; t = next) {
651 if (!g_hash_table_lookup (transport->authtypes, authtype->authproto)) {
652 types = g_list_remove_link (types, t);
657 smtp_disconnect (service, TRUE, NULL);
663 get_name (CamelService *service, gboolean brief)
666 return g_strdup_printf (_("SMTP server %s"), service->url->host);
668 return g_strdup_printf (_("SMTP mail delivery via %s"),
674 smtp_send_to (CamelTransport *transport, CamelMimeMessage *message,
675 CamelAddress *from, CamelAddress *recipients,
678 CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (transport);
679 const CamelInternetAddress *cia;
680 gboolean has_8bit_parts;
684 if (!camel_internet_address_get (CAMEL_INTERNET_ADDRESS (from), 0, NULL, &addr)) {
685 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
686 _("Cannot send message: "
687 "sender address not valid."));
691 camel_operation_start (NULL, _("Sending message"));
693 /* find out if the message has 8bit mime parts */
694 has_8bit_parts = camel_mime_message_has_8bit_parts (message);
696 /* rfc1652 (8BITMIME) requires that you notify the ESMTP daemon that
697 you'll be sending an 8bit mime message at "MAIL FROM:" time. */
698 if (!smtp_mail (smtp_transport, addr, has_8bit_parts, ex)) {
699 camel_operation_end (NULL);
703 len = camel_address_length (recipients);
705 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
706 _("Cannot send message: no recipients defined."));
707 camel_operation_end (NULL);
711 cia = CAMEL_INTERNET_ADDRESS (recipients);
712 for (i = 0; i < len; i++) {
715 if (!camel_internet_address_get (cia, i, NULL, &addr)) {
716 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
717 _("Cannot send message: one or more invalid recipients"));
718 camel_operation_end (NULL);
722 enc = camel_internet_address_encode_address(NULL, NULL, addr);
723 if (!smtp_rcpt (smtp_transport, enc, ex)) {
725 camel_operation_end (NULL);
731 if (!smtp_data (smtp_transport, message, ex)) {
732 camel_operation_end (NULL);
736 /* reset the service for our next transfer session */
737 smtp_rset (smtp_transport, ex);
739 camel_operation_end (NULL);
745 smtp_next_token (const char *buf)
747 const unsigned char *token;
749 token = (const unsigned char *) buf;
750 while (*token && !isspace ((int) *token))
753 while (*token && isspace ((int) *token))
756 return (const char *) token;
759 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : (c) - 'A' + 10)
763 * 5.1.1 Mailbox "nosuchuser" does not exist
765 * The human-readable status code is what we want. Since this text
766 * could possibly be encoded, we must decode it.
768 * "xtext" is formally defined as follows:
770 * xtext = *( xchar / hexchar / linear-white-space / comment )
772 * xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
773 * except for "+", "\" and "(".
775 * "hexchar"s are intended to encode octets that cannot be represented
776 * as plain text, either because they are reserved, or because they are
777 * non-printable. However, any octet value may be represented by a
780 * hexchar = ASCII "+" immediately followed by two upper case
784 smtp_decode_status_code (const char *in, size_t len)
786 unsigned char *inptr, *outptr;
787 const unsigned char *inend;
790 outptr = outbuf = g_malloc (len + 1);
792 inptr = (unsigned char *) in;
794 while (inptr < inend) {
796 if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
797 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
800 *outptr++ = *inptr++;
802 *outptr++ = *inptr++;
811 smtp_set_exception (CamelSmtpTransport *transport, const char *respbuf, const char *message, CamelException *ex)
813 const char *token, *rbuf = respbuf;
818 if (!respbuf || !(transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES)) {
820 error = respbuf ? atoi (respbuf) : 0;
821 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s", message,
822 smtp_error_string (error));
824 string = g_string_new ("");
826 token = smtp_next_token (rbuf + 4);
827 if (*token == '\0') {
829 g_string_free (string, TRUE);
830 goto fake_status_code;
833 g_string_append (string, token);
834 if (*(rbuf + 3) == '-') {
836 buffer = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
837 g_string_append_c (string, '\n');
846 buffer = smtp_decode_status_code (string->str, string->len);
847 g_string_free (string, TRUE);
849 goto fake_status_code;
851 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
852 "%s: %s", message, buffer);
858 /* we got disconnected */
859 transport->connected = FALSE;
864 smtp_helo (CamelSmtpTransport *transport, CamelException *ex)
866 /* say hello to the server */
867 char *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
868 struct hostent *host;
873 /* these are flags that we set, so unset them in case we
874 are being called a second time (ie, after a STARTTLS) */
875 transport->flags &= ~(CAMEL_SMTP_TRANSPORT_8BITMIME |
876 CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES |
877 CAMEL_SMTP_TRANSPORT_STARTTLS);
879 if (transport->authtypes) {
880 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
881 g_hash_table_destroy (transport->authtypes);
882 transport->authtypes = NULL;
885 camel_operation_start_transient (NULL, _("SMTP Greeting"));
887 /* get the local host name */
888 camel_exception_init (&err);
890 af = transport->localaddr->family == CAMEL_TCP_ADDRESS_IPv6 ? AF_INET6 : AF_INET;
894 host = camel_gethostbyaddr ((char *) &transport->localaddr->address,
895 transport->localaddr->length, af, &err);
897 camel_exception_clear (&err);
899 if (host && host->h_name && *host->h_name) {
900 name = g_strdup (host->h_name);
903 char ip[MAXHOSTNAMELEN + 1];
906 proto = transport->localaddr->family == CAMEL_TCP_ADDRESS_IPv6 ? "IPv6:" : "";
907 name = g_strdup_printf ("[%s%s]", proto, inet_ntop (af, transport->localaddr->address, ip, MAXHOSTNAMELEN));
909 /* We *could* use inet_ntoa() here, but it's probably
910 not worth it since we would have to worry about
911 some systems not having inet_ntoa() */
912 name = g_strdup_printf ("[%d.%d.%d.%d]",
913 transport->localaddr->address[0],
914 transport->localaddr->address[1],
915 transport->localaddr->address[2],
916 transport->localaddr->address[3]);
921 camel_free_host (host);
923 /* hiya server! how are you today? */
924 if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP)
925 cmdbuf = g_strdup_printf ("EHLO %s\r\n", name);
927 cmdbuf = g_strdup_printf ("HELO %s\r\n", name);
930 d(fprintf (stderr, "sending : %s", cmdbuf));
931 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
933 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
934 _("HELO request timed out: %s"),
936 camel_operation_end (NULL);
938 transport->connected = FALSE;
939 camel_object_unref (transport->istream);
940 transport->istream = NULL;
941 camel_object_unref (transport->ostream);
942 transport->ostream = NULL;
949 /* Check for "250" */
951 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
953 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
955 if (!respbuf || strncmp (respbuf, "250", 3)) {
956 smtp_set_exception (transport, respbuf, _("HELO response error"), ex);
957 camel_operation_end (NULL);
965 if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) {
966 if (!strncmp (token, "8BITMIME", 8)) {
967 d(fprintf (stderr, "This server supports 8bit MIME\n"));
968 transport->flags |= CAMEL_SMTP_TRANSPORT_8BITMIME;
969 } else if (!strncmp (token, "ENHANCEDSTATUSCODES", 19)) {
970 d(fprintf (stderr, "This server supports enhanced status codes\n"));
971 transport->flags |= CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES;
972 } else if (!strncmp (token, "STARTTLS", 8)) {
973 d(fprintf (stderr, "This server supports STARTTLS\n"));
974 transport->flags |= CAMEL_SMTP_TRANSPORT_STARTTLS;
975 } else if (!strncmp (token, "AUTH", 4)) {
976 if (!transport->authtypes || transport->flags & CAMEL_SMTP_TRANSPORT_AUTH_EQUAL) {
977 /* Don't bother parsing any authtypes if we already have a list.
978 * Some servers will list AUTH twice, once the standard way and
979 * once the way Microsoft Outlook requires them to be:
981 * 250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-MD5
982 * 250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-MD5
984 * Since they can come in any order, parse each list that we get
985 * until we parse an authtype list that does not use the AUTH=
986 * format. We want to let the standard way have priority over the
991 transport->flags |= CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
993 transport->flags &= ~CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
995 /* parse for supported AUTH types */
998 if (transport->authtypes) {
999 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
1000 g_hash_table_destroy (transport->authtypes);
1003 transport->authtypes = esmtp_get_authtypes (token);
1007 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1010 camel_operation_end (NULL);
1016 smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex)
1018 char *cmdbuf, *respbuf = NULL, *challenge;
1019 gboolean auth_challenge = FALSE;
1020 CamelSasl *sasl = NULL;
1022 camel_operation_start_transient (NULL, _("SMTP Authentication"));
1024 sasl = camel_sasl_new ("smtp", mech, CAMEL_SERVICE (transport));
1026 camel_operation_end (NULL);
1027 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1028 _("Error creating SASL authentication object."));
1032 challenge = camel_sasl_challenge_base64 (sasl, NULL, ex);
1034 auth_challenge = TRUE;
1035 cmdbuf = g_strdup_printf ("AUTH %s %s\r\n", mech, challenge);
1038 cmdbuf = g_strdup_printf ("AUTH %s\r\n", mech);
1041 d(fprintf (stderr, "sending : %s", cmdbuf));
1042 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1044 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1045 _("AUTH request timed out: %s"),
1046 g_strerror (errno));
1051 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1052 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1054 while (!camel_sasl_authenticated (sasl)) {
1056 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1057 _("AUTH request timed out: %s"),
1058 g_strerror (errno));
1062 /* the server challenge/response should follow a 334 code */
1063 if (strncmp (respbuf, "334", 3)) {
1065 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1066 _("AUTH request failed."));
1072 d(fprintf (stderr, "Your SMTP server's implementation of the %s SASL\n"
1073 "authentication mechanism is broken. Please report this to the\n"
1074 "appropriate vendor and suggest that they re-read rfc2554 again\n"
1075 "for the first time (specifically Section 4).\n",
1080 for (challenge = respbuf + 4; isspace (*challenge); challenge++);
1082 challenge = camel_sasl_challenge_base64 (sasl, challenge, ex);
1084 if (challenge == NULL)
1085 goto break_and_lose;
1087 /* send our challenge */
1088 cmdbuf = g_strdup_printf ("%s\r\n", challenge);
1090 d(fprintf (stderr, "sending : %s", cmdbuf));
1091 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1097 /* get the server's response */
1098 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1099 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1102 /* check that the server says we are authenticated */
1103 if (!respbuf || strncmp (respbuf, "235", 3)) {
1104 if (respbuf && auth_challenge && !strncmp (respbuf, "334", 3)) {
1105 /* broken server, but lets try and work around it anyway... */
1106 goto broken_smtp_server;
1112 camel_object_unref (sasl);
1113 camel_operation_end (NULL);
1118 /* Get the server out of "waiting for continuation data" mode. */
1119 d(fprintf (stderr, "sending : *\n"));
1120 camel_stream_write (transport->ostream, "*\r\n", 3);
1121 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1122 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1125 if (!camel_exception_is_set (ex)) {
1126 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1127 _("Bad authentication response from server.\n"));
1130 camel_object_unref (sasl);
1131 camel_operation_end (NULL);
1137 smtp_mail (CamelSmtpTransport *transport, const char *sender, gboolean has_8bit_parts, CamelException *ex)
1139 /* we gotta tell the smtp server who we are. (our email addy) */
1140 char *cmdbuf, *respbuf = NULL;
1142 if (transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME && has_8bit_parts)
1143 cmdbuf = g_strdup_printf ("MAIL FROM:<%s> BODY=8BITMIME\r\n", sender);
1145 cmdbuf = g_strdup_printf ("MAIL FROM:<%s>\r\n", sender);
1147 d(fprintf (stderr, "sending : %s", cmdbuf));
1149 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1151 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1152 _("MAIL FROM request timed out: %s: mail not sent"),
1153 g_strerror (errno));
1155 camel_object_unref (transport->istream);
1156 transport->istream = NULL;
1157 camel_object_unref (transport->ostream);
1158 transport->ostream = NULL;
1165 /* Check for "250 Sender OK..." */
1167 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1169 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1171 if (!respbuf || strncmp (respbuf, "250", 3)) {
1172 smtp_set_exception (transport, respbuf, _("MAIL FROM response error"), ex);
1176 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1183 smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex)
1185 /* we gotta tell the smtp server who we are going to be sending
1187 char *cmdbuf, *respbuf = NULL;
1189 cmdbuf = g_strdup_printf ("RCPT TO:<%s>\r\n", recipient);
1191 d(fprintf (stderr, "sending : %s", cmdbuf));
1193 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1195 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1196 _("RCPT TO request timed out: %s: mail not sent"),
1197 g_strerror (errno));
1199 camel_object_unref (transport->istream);
1200 transport->istream = NULL;
1201 camel_object_unref (transport->ostream);
1202 transport->ostream = NULL;
1209 /* Check for "250 Recipient OK..." */
1211 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1213 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1215 if (!respbuf || strncmp (respbuf, "250", 3)) {
1218 message = g_strdup_printf (_("RCPT TO <%s> failed"), recipient);
1219 smtp_set_exception (transport, respbuf, message, ex);
1224 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1231 smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex)
1233 CamelBestencEncoding enctype = CAMEL_BESTENC_8BIT;
1234 struct _camel_header_raw *header, *savedbcc, *n, *tail;
1235 char *cmdbuf, *respbuf = NULL;
1236 CamelStreamFilter *filtered_stream;
1237 CamelMimeFilter *crlffilter;
1240 /* If the server doesn't support 8BITMIME, set our required encoding to be 7bit */
1241 if (!(transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME))
1242 enctype = CAMEL_BESTENC_7BIT;
1244 /* FIXME: should we get the best charset too?? */
1245 /* Changes the encoding of all mime parts to fit within our required
1246 encoding type and also force any text parts with long lines (longer
1247 than 998 octets) to wrap by QP or base64 encoding them. */
1248 camel_mime_message_set_best_encoding (message, CAMEL_BESTENC_GET_ENCODING, enctype);
1250 cmdbuf = g_strdup ("DATA\r\n");
1252 d(fprintf (stderr, "sending : %s", cmdbuf));
1254 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1256 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1257 _("DATA request timed out: %s: mail not sent"),
1258 g_strerror (errno));
1260 camel_object_unref (transport->istream);
1261 transport->istream = NULL;
1262 camel_object_unref (transport->ostream);
1263 transport->ostream = NULL;
1269 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1271 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1273 if (!respbuf || strncmp (respbuf, "354", 3)) {
1274 /* we should have gotten instructions on how to use the DATA command:
1275 * 354 Enter mail, end with "." on a line by itself
1277 smtp_set_exception (transport, respbuf, _("DATA response error"), ex);
1285 /* setup stream filtering */
1286 crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
1287 filtered_stream = camel_stream_filter_new_with_stream (transport->ostream);
1288 camel_stream_filter_add (filtered_stream, CAMEL_MIME_FILTER (crlffilter));
1289 camel_object_unref (crlffilter);
1291 /* unlink the bcc headers */
1293 tail = (struct _camel_header_raw *) &savedbcc;
1295 header = (struct _camel_header_raw *) &CAMEL_MIME_PART (message)->headers;
1298 if (!strcasecmp (n->name, "Bcc")) {
1299 header->next = n->next;
1310 /* write the message */
1311 ret = camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), CAMEL_STREAM (filtered_stream));
1313 /* restore the bcc headers */
1314 header->next = savedbcc;
1317 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1318 _("DATA send timed out: message termination: "
1319 "%s: mail not sent"),
1320 g_strerror (errno));
1322 camel_object_unref (filtered_stream);
1324 camel_object_unref (transport->istream);
1325 transport->istream = NULL;
1326 camel_object_unref (transport->ostream);
1327 transport->ostream = NULL;
1332 camel_stream_flush (CAMEL_STREAM (filtered_stream));
1333 camel_object_unref (filtered_stream);
1335 /* terminate the message body */
1337 d(fprintf (stderr, "sending : \\r\\n.\\r\\n\n"));
1339 if (camel_stream_write (transport->ostream, "\r\n.\r\n", 5) == -1) {
1340 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1341 _("DATA send timed out: message termination: "
1342 "%s: mail not sent"),
1343 g_strerror (errno));
1345 camel_object_unref (transport->istream);
1346 transport->istream = NULL;
1347 camel_object_unref (transport->ostream);
1348 transport->ostream = NULL;
1354 /* Check for "250 Sender OK..." */
1356 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1358 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1360 if (!respbuf || strncmp (respbuf, "250", 3)) {
1361 smtp_set_exception (transport, respbuf, _("DATA termination response error"), ex);
1365 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1372 smtp_rset (CamelSmtpTransport *transport, CamelException *ex)
1374 /* we are going to reset the smtp server (just to be nice) */
1375 char *cmdbuf, *respbuf = NULL;
1377 cmdbuf = g_strdup ("RSET\r\n");
1379 d(fprintf (stderr, "sending : %s", cmdbuf));
1381 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1383 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1384 _("RSET request timed out: %s"),
1385 g_strerror (errno));
1387 camel_object_unref (transport->istream);
1388 transport->istream = NULL;
1389 camel_object_unref (transport->ostream);
1390 transport->ostream = NULL;
1397 /* Check for "250" */
1399 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1401 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1403 if (!respbuf || strncmp (respbuf, "250", 3)) {
1404 smtp_set_exception (transport, respbuf, _("RSET response error"), ex);
1408 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1415 smtp_quit (CamelSmtpTransport *transport, CamelException *ex)
1417 /* we are going to reset the smtp server (just to be nice) */
1418 char *cmdbuf, *respbuf = NULL;
1420 cmdbuf = g_strdup ("QUIT\r\n");
1422 d(fprintf (stderr, "sending : %s", cmdbuf));
1424 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1426 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1427 _("QUIT request timed out: %s"),
1428 g_strerror (errno));
1430 camel_object_unref (transport->istream);
1431 transport->istream = NULL;
1432 camel_object_unref (transport->ostream);
1433 transport->ostream = NULL;
1440 /* Check for "221" */
1442 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1444 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1446 if (!respbuf || strncmp (respbuf, "221", 3)) {
1447 smtp_set_exception (transport, respbuf, _("QUIT response error"), ex);
1451 } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */