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"
58 #include "string-utils.h"
61 extern int camel_verbose_debug;
62 #define d(x) (camel_verbose_debug ? (x) : 0)
64 /* Specified in RFC 821 */
67 /* camel smtp transport class prototypes */
68 static gboolean smtp_send_to (CamelTransport *transport, CamelMimeMessage *message,
69 CamelAddress *from, CamelAddress *recipients, CamelException *ex);
71 /* support prototypes */
72 static void smtp_construct (CamelService *service, CamelSession *session,
73 CamelProvider *provider, CamelURL *url,
75 static gboolean smtp_connect (CamelService *service, CamelException *ex);
76 static gboolean smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex);
77 static GHashTable *esmtp_get_authtypes (const unsigned char *buffer);
78 static GList *query_auth_types (CamelService *service, CamelException *ex);
79 static char *get_name (CamelService *service, gboolean brief);
81 static gboolean smtp_helo (CamelSmtpTransport *transport, CamelException *ex);
82 static gboolean smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex);
83 static gboolean smtp_mail (CamelSmtpTransport *transport, const char *sender,
84 gboolean has_8bit_parts, CamelException *ex);
85 static gboolean smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex);
86 static gboolean smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex);
87 static gboolean smtp_rset (CamelSmtpTransport *transport, CamelException *ex);
88 static gboolean smtp_quit (CamelSmtpTransport *transport, CamelException *ex);
90 static void smtp_set_exception (CamelSmtpTransport *transport, const char *respbuf,
91 const char *message, CamelException *ex);
93 /* private data members */
94 static CamelTransportClass *parent_class = NULL;
97 camel_smtp_transport_class_init (CamelSmtpTransportClass *camel_smtp_transport_class)
99 CamelTransportClass *camel_transport_class =
100 CAMEL_TRANSPORT_CLASS (camel_smtp_transport_class);
101 CamelServiceClass *camel_service_class =
102 CAMEL_SERVICE_CLASS (camel_smtp_transport_class);
104 parent_class = CAMEL_TRANSPORT_CLASS (camel_type_get_global_classfuncs (camel_transport_get_type ()));
106 /* virtual method overload */
107 camel_service_class->construct = smtp_construct;
108 camel_service_class->connect = smtp_connect;
109 camel_service_class->disconnect = smtp_disconnect;
110 camel_service_class->query_auth_types = query_auth_types;
111 camel_service_class->get_name = get_name;
113 camel_transport_class->send_to = smtp_send_to;
117 camel_smtp_transport_init (gpointer object)
119 CamelSmtpTransport *smtp = CAMEL_SMTP_TRANSPORT (object);
122 smtp->connected = FALSE;
126 camel_smtp_transport_get_type (void)
128 static CamelType type = CAMEL_INVALID_TYPE;
130 if (type == CAMEL_INVALID_TYPE) {
131 type = camel_type_register (CAMEL_TRANSPORT_TYPE,
132 "CamelSmtpTransport",
133 sizeof (CamelSmtpTransport),
134 sizeof (CamelSmtpTransportClass),
135 (CamelObjectClassInitFunc) camel_smtp_transport_class_init,
137 (CamelObjectInitFunc) camel_smtp_transport_init,
145 smtp_construct (CamelService *service, CamelSession *session,
146 CamelProvider *provider, CamelURL *url,
149 CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (service);
152 CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
154 if ((use_ssl = camel_url_get_param (url, "use_ssl"))) {
155 /* Note: previous versions would use "" to toggle use_ssl to 'on' */
156 if (!*use_ssl || !strcmp (use_ssl, "always"))
157 smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS;
158 else if (!strcmp (use_ssl, "when-possible"))
159 smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE;
164 smtp_error_string (int error)
166 /* SMTP error codes grabbed from rfc821 */
169 /* looks like a read problem, check errno */
171 return g_strerror (errno);
175 return _("Syntax error, command unrecognized");
177 return _("Syntax error in parameters or arguments");
179 return _("Command not implemented");
181 return _("Command parameter not implemented");
183 return _("System status, or system help reply");
185 return _("Help message");
187 return _("Service ready");
189 return _("Service closing transmission channel");
191 return _("Service not available, closing transmission channel");
193 return _("Requested mail action okay, completed");
195 return _("User not local; will forward to <forward-path>");
197 return _("Requested mail action not taken: mailbox unavailable");
199 return _("Requested action not taken: mailbox unavailable");
201 return _("Requested action aborted: error in processing");
203 return _("User not local; please try <forward-path>");
205 return _("Requested action not taken: insufficient system storage");
207 return _("Requested mail action aborted: exceeded storage allocation");
209 return _("Requested action not taken: mailbox name not allowed");
211 return _("Start mail input; end with <CRLF>.<CRLF>");
213 return _("Transaction failed");
215 /* AUTH error codes: */
217 return _("A password transition is needed");
219 return _("Authentication mechanism is too weak");
221 return _("Encryption required for requested authentication mechanism");
223 return _("Temporary authentication failure");
225 return _("Authentication required");
232 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
233 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
236 connect_to_server (CamelService *service, int try_starttls, CamelException *ex)
238 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
239 CamelStream *tcp_stream;
240 char *respbuf = NULL;
244 if (!CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex))
247 h = camel_service_gethost (service, ex);
251 /* set some smtp transport defaults */
252 transport->flags &= CAMEL_SMTP_TRANSPORT_USE_SSL; /* reset all but ssl flags */
253 transport->authtypes = NULL;
255 port = service->url->port ? service->url->port : SMTP_PORT;
258 if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL) {
260 tcp_stream = camel_tcp_stream_ssl_new_raw (service, service->url->host, STARTTLS_FLAGS);
262 port = service->url->port ? service->url->port : 465;
263 tcp_stream = camel_tcp_stream_ssl_new (service, service->url->host, SSL_PORT_FLAGS);
266 tcp_stream = camel_tcp_stream_raw_new ();
269 tcp_stream = camel_tcp_stream_raw_new ();
270 #endif /* HAVE_SSL */
272 ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
275 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
276 _("Could not connect to %s (port %d): %s"),
277 service->url->host, port,
280 camel_object_unref (CAMEL_OBJECT (tcp_stream));
285 transport->connected = TRUE;
287 /* get the localaddr - needed later by smtp_helo */
288 transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream));
290 transport->ostream = tcp_stream;
291 transport->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
293 /* Read the greeting, note whether the server is ESMTP or not. */
295 /* Check for "220" */
297 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
298 if (!respbuf || strncmp (respbuf, "220", 3)) {
299 smtp_set_exception (transport, respbuf, _("Welcome response error"), ex);
303 if (strstr (respbuf, "ESMTP"))
304 transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
305 } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
308 /* send EHLO (or HELO, depending on the service type) */
309 if (!(transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP)) {
310 /* If we did not auto-detect ESMTP, we should still send EHLO */
311 transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
312 if (!smtp_helo (transport, ex)) {
313 if (!transport->connected)
316 /* Okay, apprently this server doesn't support ESMTP */
317 camel_exception_clear (ex);
318 transport->flags &= ~CAMEL_SMTP_TRANSPORT_IS_ESMTP;
319 if (!smtp_helo (transport, ex) && !transport->connected)
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 (CAMEL_OBJECT (transport->istream));
397 transport->istream = NULL;
398 camel_object_unref (CAMEL_OBJECT (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 (CAMEL_OBJECT (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 (CAMEL_OBJECT (transport->istream));
584 transport->istream = NULL;
587 if (transport->ostream) {
588 camel_object_unref (CAMEL_OBJECT (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++) {
713 if (!camel_internet_address_get (cia, i, NULL, &addr)) {
714 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
715 _("Cannot send message: one or more invalid recipients"));
716 camel_operation_end (NULL);
720 if (!smtp_rcpt (smtp_transport, addr, ex)) {
721 camel_operation_end (NULL);
726 if (!smtp_data (smtp_transport, message, ex)) {
727 camel_operation_end (NULL);
731 /* reset the service for our next transfer session */
732 smtp_rset (smtp_transport, ex);
734 camel_operation_end (NULL);
740 smtp_next_token (const char *buf)
742 const unsigned char *token;
744 token = (const unsigned char *) buf;
745 while (*token && !isspace ((int) *token))
748 while (*token && isspace ((int) *token))
751 return (const char *) token;
754 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : (c) - 'A' + 10)
758 * 5.1.1 Mailbox "nosuchuser" does not exist
760 * The human-readable status code is what we want. Since this text
761 * could possibly be encoded, we must decode it.
763 * "xtext" is formally defined as follows:
765 * xtext = *( xchar / hexchar / linear-white-space / comment )
767 * xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
768 * except for "+", "\" and "(".
770 * "hexchar"s are intended to encode octets that cannot be represented
771 * as plain text, either because they are reserved, or because they are
772 * non-printable. However, any octet value may be represented by a
775 * hexchar = ASCII "+" immediately followed by two upper case
779 smtp_decode_status_code (const char *in, size_t len)
781 unsigned char *inptr, *outptr;
782 const unsigned char *inend;
785 outptr = outbuf = g_malloc (len + 1);
787 inptr = (unsigned char *) in;
789 while (inptr < inend) {
791 if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
792 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
795 *outptr++ = *inptr++;
797 *outptr++ = *inptr++;
806 smtp_set_exception (CamelSmtpTransport *transport, const char *respbuf, const char *message, CamelException *ex)
808 const char *token, *rbuf = respbuf;
813 if (!respbuf || !(transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES)) {
815 error = respbuf ? atoi (respbuf) : 0;
816 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s", message,
817 smtp_error_string (error));
819 string = g_string_new ("");
821 token = smtp_next_token (rbuf + 4);
822 if (*token == '\0') {
824 g_string_free (string, TRUE);
825 goto fake_status_code;
828 g_string_append (string, token);
829 if (*(rbuf + 3) == '-') {
831 buffer = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
832 g_string_append_c (string, '\n');
841 buffer = smtp_decode_status_code (string->str, string->len);
842 g_string_free (string, TRUE);
844 goto fake_status_code;
846 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
847 "%s: %s", message, buffer);
853 /* we got disconnected */
854 transport->connected = FALSE;
859 smtp_helo (CamelSmtpTransport *transport, CamelException *ex)
861 /* say hello to the server */
862 char *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
863 struct hostent *host;
868 /* these are flags that we set, so unset them in case we
869 are being called a second time (ie, after a STARTTLS) */
870 transport->flags &= ~(CAMEL_SMTP_TRANSPORT_8BITMIME |
871 CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES |
872 CAMEL_SMTP_TRANSPORT_STARTTLS);
874 if (transport->authtypes) {
875 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
876 g_hash_table_destroy (transport->authtypes);
877 transport->authtypes = NULL;
880 camel_operation_start_transient (NULL, _("SMTP Greeting"));
882 /* get the local host name */
883 camel_exception_init (&err);
885 af = transport->localaddr->family == CAMEL_TCP_ADDRESS_IPv6 ? AF_INET6 : AF_INET;
889 host = camel_gethostbyaddr ((char *) &transport->localaddr->address,
890 transport->localaddr->length, af, &err);
892 camel_exception_clear (&err);
894 if (host && host->h_name && *host->h_name) {
895 name = g_strdup (host->h_name);
898 char ip[MAXHOSTNAMELEN + 1];
900 name = g_strdup_printf ("[%s]", inet_ntop (af, transport->localaddr->address, ip, MAXHOSTNAMELEN));
902 /* We *could* use inet_ntoa() here, but it's probably
903 not worth it since we would have to worry about
904 some systems not having inet_ntoa() */
905 name = g_strdup_printf ("[%d.%d.%d.%d]",
906 transport->localaddr->address[0],
907 transport->localaddr->address[1],
908 transport->localaddr->address[2],
909 transport->localaddr->address[3]);
914 camel_free_host (host);
916 /* hiya server! how are you today? */
917 if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP)
918 cmdbuf = g_strdup_printf ("EHLO %s\r\n", name);
920 cmdbuf = g_strdup_printf ("HELO %s\r\n", name);
923 d(fprintf (stderr, "sending : %s", cmdbuf));
924 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
926 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
927 _("HELO request timed out: %s"),
929 camel_operation_end (NULL);
931 transport->connected = FALSE;
932 camel_object_unref (transport->istream);
933 transport->istream = NULL;
934 camel_object_unref (transport->ostream);
935 transport->ostream = NULL;
942 /* Check for "250" */
944 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
946 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
948 if (!respbuf || strncmp (respbuf, "250", 3)) {
949 smtp_set_exception (transport, respbuf, _("HELO response error"), ex);
950 camel_operation_end (NULL);
958 if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) {
959 if (!strncmp (token, "8BITMIME", 8)) {
960 d(fprintf (stderr, "This server supports 8bit MIME\n"));
961 transport->flags |= CAMEL_SMTP_TRANSPORT_8BITMIME;
962 } else if (!strncmp (token, "ENHANCEDSTATUSCODES", 19)) {
963 d(fprintf (stderr, "This server supports enhanced status codes\n"));
964 transport->flags |= CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES;
965 } else if (!strncmp (token, "STARTTLS", 8)) {
966 d(fprintf (stderr, "This server supports STARTTLS\n"));
967 transport->flags |= CAMEL_SMTP_TRANSPORT_STARTTLS;
968 } else if (!strncmp (token, "AUTH", 4)) {
969 if (!transport->authtypes || transport->flags & CAMEL_SMTP_TRANSPORT_AUTH_EQUAL) {
970 /* Don't bother parsing any authtypes if we already have a list.
971 * Some servers will list AUTH twice, once the standard way and
972 * once the way Microsoft Outlook requires them to be:
974 * 250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-MD5
975 * 250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-MD5
977 * Since they can come in any order, parse each list that we get
978 * until we parse an authtype list that does not use the AUTH=
979 * format. We want to let the standard way have priority over the
984 transport->flags |= CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
986 transport->flags &= ~CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
988 /* parse for supported AUTH types */
991 if (transport->authtypes) {
992 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
993 g_hash_table_destroy (transport->authtypes);
996 transport->authtypes = esmtp_get_authtypes (token);
1000 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1003 camel_operation_end (NULL);
1009 smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex)
1011 char *cmdbuf, *respbuf = NULL, *challenge;
1012 gboolean auth_challenge = FALSE;
1013 CamelSasl *sasl = NULL;
1015 camel_operation_start_transient (NULL, _("SMTP Authentication"));
1017 sasl = camel_sasl_new ("smtp", mech, CAMEL_SERVICE (transport));
1019 camel_operation_end (NULL);
1020 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1021 _("Error creating SASL authentication object."));
1025 challenge = camel_sasl_challenge_base64 (sasl, NULL, ex);
1027 auth_challenge = TRUE;
1028 cmdbuf = g_strdup_printf ("AUTH %s %s\r\n", mech, challenge);
1031 cmdbuf = g_strdup_printf ("AUTH %s\r\n", mech);
1034 d(fprintf (stderr, "sending : %s", cmdbuf));
1035 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1037 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1038 _("AUTH request timed out: %s"),
1039 g_strerror (errno));
1044 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1045 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1047 while (!camel_sasl_authenticated (sasl)) {
1049 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1050 _("AUTH request timed out: %s"),
1051 g_strerror (errno));
1055 /* the server challenge/response should follow a 334 code */
1056 if (strncmp (respbuf, "334", 3)) {
1058 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1059 _("AUTH request failed."));
1065 d(fprintf (stderr, "Your SMTP server's implementation of the %s SASL\n"
1066 "authentication mechanism is broken. Please report this to the\n"
1067 "appropriate vendor and suggest that they re-read rfc2222 again\n"
1068 "for the first time (specifically Section 4, paragraph 2).\n",
1073 for (challenge = respbuf + 4; isspace (*challenge); challenge++);
1075 challenge = camel_sasl_challenge_base64 (sasl, challenge, ex);
1077 if (challenge == NULL)
1078 goto break_and_lose;
1080 /* send our challenge */
1081 cmdbuf = g_strdup_printf ("%s\r\n", challenge);
1083 d(fprintf (stderr, "sending : %s", cmdbuf));
1084 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1090 /* get the server's response */
1091 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1092 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1095 /* check that the server says we are authenticated */
1096 if (!respbuf || strncmp (respbuf, "235", 3)) {
1097 if (respbuf && auth_challenge && !strncmp (respbuf, "334", 3)) {
1098 /* broken server, but lets try and work around it anyway... */
1099 goto broken_smtp_server;
1105 camel_object_unref (CAMEL_OBJECT (sasl));
1106 camel_operation_end (NULL);
1111 /* Get the server out of "waiting for continuation data" mode. */
1112 d(fprintf (stderr, "sending : *\n"));
1113 camel_stream_write (transport->ostream, "*\r\n", 3);
1114 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1115 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1118 if (!camel_exception_is_set (ex)) {
1119 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1120 _("Bad authentication response from server.\n"));
1123 camel_object_unref (CAMEL_OBJECT (sasl));
1124 camel_operation_end (NULL);
1130 smtp_mail (CamelSmtpTransport *transport, const char *sender, gboolean has_8bit_parts, CamelException *ex)
1132 /* we gotta tell the smtp server who we are. (our email addy) */
1133 char *cmdbuf, *respbuf = NULL;
1135 if (transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME && has_8bit_parts)
1136 cmdbuf = g_strdup_printf ("MAIL FROM:<%s> BODY=8BITMIME\r\n", sender);
1138 cmdbuf = g_strdup_printf ("MAIL FROM:<%s>\r\n", sender);
1140 d(fprintf (stderr, "sending : %s", cmdbuf));
1142 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1144 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1145 _("MAIL FROM request timed out: %s: mail not sent"),
1146 g_strerror (errno));
1148 camel_object_unref (transport->istream);
1149 transport->istream = NULL;
1150 camel_object_unref (transport->ostream);
1151 transport->ostream = NULL;
1158 /* Check for "250 Sender OK..." */
1160 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1162 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1164 if (!respbuf || strncmp (respbuf, "250", 3)) {
1165 smtp_set_exception (transport, respbuf, _("MAIL FROM response error"), ex);
1169 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1176 smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex)
1178 /* we gotta tell the smtp server who we are going to be sending
1180 char *cmdbuf, *respbuf = NULL;
1182 cmdbuf = g_strdup_printf ("RCPT TO:<%s>\r\n", recipient);
1184 d(fprintf (stderr, "sending : %s", cmdbuf));
1186 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1188 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1189 _("RCPT TO request timed out: %s: mail not sent"),
1190 g_strerror (errno));
1192 camel_object_unref (transport->istream);
1193 transport->istream = NULL;
1194 camel_object_unref (transport->ostream);
1195 transport->ostream = NULL;
1202 /* Check for "250 Recipient OK..." */
1204 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1206 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1208 if (!respbuf || strncmp (respbuf, "250", 3)) {
1211 message = g_strdup_printf (_("RCPT TO <%s> failed"), recipient);
1212 smtp_set_exception (transport, respbuf, message, ex);
1217 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1224 smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex)
1226 CamelBestencEncoding enctype = CAMEL_BESTENC_8BIT;
1227 struct _header_raw *header, *savedbcc, *n, *tail;
1228 char *cmdbuf, *respbuf = NULL;
1229 CamelStreamFilter *filtered_stream;
1230 CamelMimeFilter *crlffilter;
1233 /* If the server doesn't support 8BITMIME, set our required encoding to be 7bit */
1234 if (!(transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME))
1235 enctype = CAMEL_BESTENC_7BIT;
1237 /* FIXME: should we get the best charset too?? */
1238 /* Changes the encoding of all mime parts to fit within our required
1239 encoding type and also force any text parts with long lines (longer
1240 than 998 octets) to wrap by QP or base64 encoding them. */
1241 camel_mime_message_set_best_encoding (message, CAMEL_BESTENC_GET_ENCODING, enctype);
1243 cmdbuf = g_strdup ("DATA\r\n");
1245 d(fprintf (stderr, "sending : %s", cmdbuf));
1247 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1249 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1250 _("DATA request timed out: %s: mail not sent"),
1251 g_strerror (errno));
1253 camel_object_unref (transport->istream);
1254 transport->istream = NULL;
1255 camel_object_unref (transport->ostream);
1256 transport->ostream = NULL;
1262 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1264 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1266 if (!respbuf || strncmp (respbuf, "354", 3)) {
1267 /* we should have gotten instructions on how to use the DATA command:
1268 * 354 Enter mail, end with "." on a line by itself
1270 smtp_set_exception (transport, respbuf, _("DATA response error"), ex);
1278 /* setup stream filtering */
1279 crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
1280 filtered_stream = camel_stream_filter_new_with_stream (transport->ostream);
1281 camel_stream_filter_add (filtered_stream, CAMEL_MIME_FILTER (crlffilter));
1282 camel_object_unref (CAMEL_OBJECT (crlffilter));
1284 /* unlink the bcc headers */
1286 tail = (struct _header_raw *) &savedbcc;
1288 header = (struct _header_raw *) &CAMEL_MIME_PART (message)->headers;
1291 if (!strcasecmp (n->name, "Bcc")) {
1292 header->next = n->next;
1303 /* write the message */
1304 ret = camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), CAMEL_STREAM (filtered_stream));
1306 /* restore the bcc headers */
1307 header->next = savedbcc;
1310 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1311 _("DATA send timed out: message termination: "
1312 "%s: mail not sent"),
1313 g_strerror (errno));
1315 camel_object_unref (CAMEL_OBJECT (filtered_stream));
1317 camel_object_unref (transport->istream);
1318 transport->istream = NULL;
1319 camel_object_unref (transport->ostream);
1320 transport->ostream = NULL;
1325 camel_stream_flush (CAMEL_STREAM (filtered_stream));
1326 camel_object_unref (CAMEL_OBJECT (filtered_stream));
1328 /* terminate the message body */
1330 d(fprintf (stderr, "sending : \\r\\n.\\r\\n\n"));
1332 if (camel_stream_write (transport->ostream, "\r\n.\r\n", 5) == -1) {
1333 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1334 _("DATA send timed out: message termination: "
1335 "%s: mail not sent"),
1336 g_strerror (errno));
1338 camel_object_unref (transport->istream);
1339 transport->istream = NULL;
1340 camel_object_unref (transport->ostream);
1341 transport->ostream = NULL;
1347 /* Check for "250 Sender OK..." */
1349 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1351 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1353 if (!respbuf || strncmp (respbuf, "250", 3)) {
1354 smtp_set_exception (transport, respbuf, _("DATA termination response error"), ex);
1358 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1365 smtp_rset (CamelSmtpTransport *transport, CamelException *ex)
1367 /* we are going to reset the smtp server (just to be nice) */
1368 char *cmdbuf, *respbuf = NULL;
1370 cmdbuf = g_strdup ("RSET\r\n");
1372 d(fprintf (stderr, "sending : %s", cmdbuf));
1374 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1376 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1377 _("RSET request timed out: %s"),
1378 g_strerror (errno));
1380 camel_object_unref (transport->istream);
1381 transport->istream = NULL;
1382 camel_object_unref (transport->ostream);
1383 transport->ostream = NULL;
1390 /* Check for "250" */
1392 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1394 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1396 if (!respbuf || strncmp (respbuf, "250", 3)) {
1397 smtp_set_exception (transport, respbuf, _("RSET response error"), ex);
1401 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1408 smtp_quit (CamelSmtpTransport *transport, CamelException *ex)
1410 /* we are going to reset the smtp server (just to be nice) */
1411 char *cmdbuf, *respbuf = NULL;
1413 cmdbuf = g_strdup ("QUIT\r\n");
1415 d(fprintf (stderr, "sending : %s", cmdbuf));
1417 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1419 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1420 _("QUIT request timed out: %s"),
1421 g_strerror (errno));
1423 camel_object_unref (transport->istream);
1424 transport->istream = NULL;
1425 camel_object_unref (transport->ostream);
1426 transport->ostream = NULL;
1433 /* Check for "221" */
1435 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1437 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1439 if (!respbuf || strncmp (respbuf, "221", 3)) {
1440 smtp_set_exception (transport, respbuf, _("QUIT response error"), ex);
1444 } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */