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) 1999-2008 Novell, Inc. (www.novell.com)
9 * This library is free software you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation.
13 * This library is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, see <http://www.gnu.org/licenses/>.
32 #include <sys/param.h>
33 #include <sys/types.h>
35 #include <glib/gi18n-lib.h>
37 #include "camel-smtp-settings.h"
38 #include "camel-smtp-transport.h"
48 #define d(x) (camel_debug ("smtp") ? (x) : 0)
50 /* Specified in RFC 821 */
52 #define SMTPS_PORT 465
54 #define CAMEL_SMTP_TRANSPORT_IS_ESMTP (1 << 0)
55 #define CAMEL_SMTP_TRANSPORT_8BITMIME (1 << 1)
56 #define CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES (1 << 2)
57 #define CAMEL_SMTP_TRANSPORT_STARTTLS (1 << 3)
59 /* set if we are using authtypes from a broken AUTH= */
60 #define CAMEL_SMTP_TRANSPORT_AUTH_EQUAL (1 << 4)
68 /* support prototypes */
69 static GHashTable * esmtp_get_authtypes (const guchar *buffer);
70 static gboolean smtp_helo (CamelSmtpTransport *transport,
71 GCancellable *cancellable,
73 static gboolean smtp_mail (CamelSmtpTransport *transport,
75 gboolean has_8bit_parts,
76 GCancellable *cancellable,
78 static gboolean smtp_rcpt (CamelSmtpTransport *transport,
79 const gchar *recipient,
80 GCancellable *cancellable,
82 static gboolean smtp_data (CamelSmtpTransport *transport,
83 CamelMimeMessage *message,
84 GCancellable *cancellable,
86 static gboolean smtp_rset (CamelSmtpTransport *transport,
87 GCancellable *cancellable,
89 static gboolean smtp_quit (CamelSmtpTransport *transport,
90 GCancellable *cancellable,
92 static void smtp_set_error (CamelSmtpTransport *transport,
94 GCancellable *cancellable,
97 /* Forward Declarations */
98 static void camel_network_service_init (CamelNetworkServiceInterface *iface);
100 G_DEFINE_TYPE_WITH_CODE (
102 camel_smtp_transport,
103 CAMEL_TYPE_TRANSPORT,
104 G_IMPLEMENT_INTERFACE (
105 CAMEL_TYPE_NETWORK_SERVICE,
106 camel_network_service_init))
109 connect_to_server (CamelService *service,
110 GCancellable *cancellable,
113 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
114 CamelNetworkSettings *network_settings;
115 CamelNetworkSecurityMethod method;
116 CamelSettings *settings;
118 GIOStream *base_stream;
119 GIOStream *tls_stream;
120 gchar *respbuf = NULL;
121 gboolean success = TRUE;
124 if (!CAMEL_SERVICE_CLASS (camel_smtp_transport_parent_class)->
125 connect_sync (service, cancellable, error))
128 /* set some smtp transport defaults */
129 transport->flags = 0;
130 transport->authtypes = NULL;
132 settings = camel_service_ref_settings (service);
134 network_settings = CAMEL_NETWORK_SETTINGS (settings);
135 host = camel_network_settings_dup_host (network_settings);
136 method = camel_network_settings_get_security_method (network_settings);
138 g_object_unref (settings);
140 base_stream = camel_network_service_connect_sync (
141 CAMEL_NETWORK_SERVICE (service), cancellable, error);
143 if (base_stream != NULL) {
144 /* get the localaddr - needed later by smtp_helo */
145 transport->local_address =
146 g_socket_connection_get_local_address (
147 G_SOCKET_CONNECTION (base_stream), NULL);
149 stream = camel_stream_new (base_stream);
150 g_object_unref (base_stream);
156 transport->connected = TRUE;
158 transport->ostream = stream;
159 transport->istream = camel_stream_buffer_new (
160 stream, CAMEL_STREAM_BUFFER_READ);
162 /* Read the greeting, note whether the server is ESMTP or not. */
164 /* Check for "220" */
166 respbuf = camel_stream_buffer_read_line (
167 CAMEL_STREAM_BUFFER (transport->istream),
169 if (respbuf == NULL) {
170 g_prefix_error (error, _("Welcome response error: "));
171 transport->connected = FALSE;
175 if (strncmp (respbuf, "220", 3)) {
177 transport, respbuf, cancellable, error);
178 g_prefix_error (error, _("Welcome response error: "));
183 } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
186 /* Try sending EHLO */
187 transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
188 if (!smtp_helo (transport, cancellable, error)) {
189 if (!transport->connected) {
194 /* Fall back to HELO */
195 g_clear_error (error);
196 transport->flags &= ~CAMEL_SMTP_TRANSPORT_IS_ESMTP;
198 if (!smtp_helo (transport, cancellable, error)) {
204 /* Clear any EHLO/HELO exception and assume that
205 * any SMTP errors encountered were non-fatal. */
206 g_clear_error (error);
208 if (method != CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT)
209 goto exit; /* we're done */
211 if (!(transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS)) {
213 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
214 _("Failed to connect to SMTP server %s in secure mode: %s"),
215 host, _("STARTTLS not supported"));
221 d (fprintf (stderr, "sending : STARTTLS\r\n"));
222 if (camel_stream_write (
223 stream, "STARTTLS\r\n", 10, cancellable, error) == -1) {
224 g_prefix_error (error, _("STARTTLS command failed: "));
232 /* Check for "220 Ready for TLS" */
234 respbuf = camel_stream_buffer_read_line (
235 CAMEL_STREAM_BUFFER (transport->istream),
237 if (respbuf == NULL) {
238 g_prefix_error (error, _("STARTTLS command failed: "));
239 transport->connected = FALSE;
243 if (strncmp (respbuf, "220", 3) != 0) {
245 transport, respbuf, cancellable, error);
246 g_prefix_error (error, _("STARTTLS command failed: "));
251 } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
253 /* Okay, now toggle SSL/TLS mode */
254 base_stream = camel_stream_ref_base_stream (stream);
255 tls_stream = camel_network_service_starttls (
256 CAMEL_NETWORK_SERVICE (service), base_stream, error);
257 g_object_unref (base_stream);
259 if (tls_stream != NULL) {
260 camel_stream_set_base_stream (stream, tls_stream);
261 g_object_unref (tls_stream);
265 _("Failed to connect to SMTP server %s in secure mode: "),
271 /* We are supposed to re-EHLO after a successful STARTTLS to
272 * re-fetch any supported extensions. */
273 if (!smtp_helo (transport, cancellable, error)) {
281 transport->connected = FALSE;
282 g_clear_object (&transport->istream);
283 g_clear_object (&transport->ostream);
290 authtypes_free (gpointer key,
298 smtp_transport_set_property (GObject *object,
303 switch (property_id) {
304 case PROP_CONNECTABLE:
305 camel_network_service_set_connectable (
306 CAMEL_NETWORK_SERVICE (object),
307 g_value_get_object (value));
311 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
315 smtp_transport_get_property (GObject *object,
320 switch (property_id) {
321 case PROP_CONNECTABLE:
322 g_value_take_object (
324 camel_network_service_ref_connectable (
325 CAMEL_NETWORK_SERVICE (object)));
328 case PROP_HOST_REACHABLE:
329 g_value_set_boolean (
331 camel_network_service_get_host_reachable (
332 CAMEL_NETWORK_SERVICE (object)));
336 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
340 smtp_transport_get_name (CamelService *service,
343 CamelNetworkSettings *network_settings;
344 CamelSettings *settings;
348 settings = camel_service_ref_settings (service);
350 network_settings = CAMEL_NETWORK_SETTINGS (settings);
351 host = camel_network_settings_dup_host (network_settings);
353 g_object_unref (settings);
356 name = g_strdup_printf (
357 _("SMTP server %s"), host);
359 name = g_strdup_printf (
360 _("SMTP mail delivery via %s"), host);
368 smtp_transport_connect_sync (CamelService *service,
369 GCancellable *cancellable,
372 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
373 CamelNetworkSettings *network_settings;
374 CamelSettings *settings;
377 gboolean auth_required;
378 gboolean success = TRUE;
380 settings = camel_service_ref_settings (service);
382 network_settings = CAMEL_NETWORK_SETTINGS (settings);
383 host = camel_network_settings_dup_host (network_settings);
384 mechanism = camel_network_settings_dup_auth_mechanism (network_settings);
386 g_object_unref (settings);
388 /* We (probably) need to check popb4smtp before we connect ... */
389 if (g_strcmp0 (mechanism, "POPB4SMTP") == 0) {
393 sasl = camel_sasl_new ("smtp", "POPB4SMTP", service);
394 chal = camel_sasl_challenge_sync (sasl, NULL, cancellable, error);
396 g_byte_array_free (chal, TRUE);
398 if (camel_sasl_get_authenticated (sasl))
399 success = connect_to_server (
400 service, cancellable, error);
404 g_object_unref (sasl);
409 success = connect_to_server (service, cancellable, error);
414 /* check to see if AUTH is required, if so...then AUTH ourselves */
416 (mechanism != NULL) &&
417 (transport->authtypes != NULL) &&
418 (g_hash_table_size (transport->authtypes) > 0) &&
419 (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP);
422 CamelSession *session;
424 session = camel_service_ref_session (service);
426 if (g_hash_table_lookup (transport->authtypes, mechanism)) {
427 success = camel_session_authenticate_sync (
428 session, service, mechanism,
432 error, CAMEL_SERVICE_ERROR,
433 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
434 _("SMTP server %s does not support %s "
435 "authentication"), host, mechanism);
439 g_object_unref (session);
442 transport->connected = FALSE;
453 smtp_transport_disconnect_sync (CamelService *service,
455 GCancellable *cancellable,
458 CamelServiceClass *service_class;
459 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
461 /*if (!service->connected)
465 if (transport->connected && clean) {
466 /* send the QUIT command to the SMTP server */
467 smtp_quit (transport, cancellable, NULL);
470 /* Chain up to parent's disconnect() method. */
471 service_class = CAMEL_SERVICE_CLASS (camel_smtp_transport_parent_class);
472 if (!service_class->disconnect_sync (service, clean, cancellable, error))
475 if (transport->authtypes) {
476 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
477 g_hash_table_destroy (transport->authtypes);
478 transport->authtypes = NULL;
481 g_clear_object (&transport->istream);
482 g_clear_object (&transport->ostream);
483 g_clear_object (&transport->local_address);
485 transport->connected = FALSE;
490 static CamelAuthenticationResult
491 smtp_transport_authenticate_sync (CamelService *service,
492 const gchar *mechanism,
493 GCancellable *cancellable,
496 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
497 CamelAuthenticationResult result;
499 gchar *cmdbuf, *respbuf = NULL, *challenge;
500 gboolean auth_challenge = FALSE;
502 if (mechanism == NULL) {
504 error, CAMEL_SERVICE_ERROR,
505 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
506 _("No SASL mechanism was specified"));
507 return CAMEL_AUTHENTICATION_ERROR;
510 sasl = camel_sasl_new ("smtp", mechanism, service);
513 error, CAMEL_SERVICE_ERROR,
514 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
515 _("No support for %s authentication"), mechanism);
516 return CAMEL_AUTHENTICATION_ERROR;
519 challenge = camel_sasl_challenge_base64_sync (
520 sasl, NULL, cancellable, error);
522 auth_challenge = TRUE;
523 cmdbuf = g_strdup_printf (
524 "AUTH %s %s\r\n", mechanism, challenge);
527 cmdbuf = g_strdup_printf (
528 "AUTH %s\r\n", mechanism);
531 d (fprintf (stderr, "sending : %s", cmdbuf));
532 if (camel_stream_write_string (
533 transport->ostream, cmdbuf,
534 cancellable, error) == -1) {
536 g_prefix_error (error, _("AUTH command failed: "));
541 respbuf = camel_stream_buffer_read_line (
542 CAMEL_STREAM_BUFFER (transport->istream),
545 while (!camel_sasl_get_authenticated (sasl)) {
547 g_prefix_error (error, _("AUTH command failed: "));
548 transport->connected = FALSE;
552 /* the server may have accepted our initial response */
553 if (strncmp (respbuf, "235", 3) == 0)
556 /* the server challenge/response should follow a 334 code */
557 if (strncmp (respbuf, "334", 3) != 0) {
559 transport, respbuf, cancellable, error);
560 g_prefix_error (error, _("AUTH command failed: "));
567 stderr, "Your SMTP server's implementation "
568 "of the %s SASL\nauthentication mechanism is "
569 "broken. Please report this to the\n"
570 "appropriate vendor and suggest that they "
571 "re-read rfc2554 again\nfor the first time "
572 "(specifically Section 4).\n",
577 for (challenge = respbuf + 4; isspace (*challenge); challenge++);
579 challenge = camel_sasl_challenge_base64_sync (
580 sasl, challenge, cancellable, error);
581 if (challenge == NULL)
586 /* send our challenge */
587 cmdbuf = g_strdup_printf ("%s\r\n", challenge);
589 d (fprintf (stderr, "sending : %s", cmdbuf));
590 if (camel_stream_write_string (
591 transport->ostream, cmdbuf,
592 cancellable, error) == -1) {
598 /* get the server's response */
599 respbuf = camel_stream_buffer_read_line (
600 CAMEL_STREAM_BUFFER (transport->istream),
607 /* Work around broken SASL implementations. */
608 if (auth_challenge && strncmp (respbuf, "334", 3) == 0)
609 goto broken_smtp_server;
611 /* If our authentication data was rejected, destroy the
612 * password so that the user gets prompted to try again. */
613 if (strncmp (respbuf, "535", 3) == 0)
614 result = CAMEL_AUTHENTICATION_REJECTED;
615 else if (strncmp (respbuf, "235", 3) == 0)
616 result = CAMEL_AUTHENTICATION_ACCEPTED;
617 /* Catch any other errors. */
620 error, CAMEL_SERVICE_ERROR,
621 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
622 _("Bad authentication response from server."));
629 /* Get the server out of "waiting for continuation data" mode. */
630 d (fprintf (stderr, "sending : *\n"));
631 camel_stream_write (transport->ostream, "*\r\n", 3, cancellable, NULL);
632 respbuf = camel_stream_buffer_read_line (
633 CAMEL_STREAM_BUFFER (transport->istream), cancellable, NULL);
634 d (fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
637 result = CAMEL_AUTHENTICATION_ERROR;
640 g_object_unref (sasl);
647 smtp_transport_query_auth_types_sync (CamelService *service,
648 GCancellable *cancellable,
651 CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
652 CamelServiceAuthType *authtype;
653 CamelProvider *provider;
654 GList *types, *t, *next;
656 if (!connect_to_server (service, cancellable, error))
659 if (!transport->authtypes) {
660 smtp_transport_disconnect_sync (
661 service, TRUE, cancellable, NULL);
665 provider = camel_service_get_provider (service);
666 types = g_list_copy (provider->authtypes);
668 for (t = types; t; t = next) {
672 if (!g_hash_table_lookup (transport->authtypes, authtype->authproto)) {
673 types = g_list_remove_link (types, t);
678 smtp_transport_disconnect_sync (service, TRUE, cancellable, NULL);
684 smtp_transport_send_to_sync (CamelTransport *transport,
685 CamelMimeMessage *message,
687 CamelAddress *recipients,
688 GCancellable *cancellable,
691 CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (transport);
692 CamelInternetAddress *cia;
693 gboolean has_8bit_parts;
697 if (!smtp_transport->connected) {
699 error, CAMEL_SERVICE_ERROR,
700 CAMEL_SERVICE_ERROR_NOT_CONNECTED,
701 _("Cannot send message: service not connected."));
705 if (!camel_internet_address_get (CAMEL_INTERNET_ADDRESS (from), 0, NULL, &addr)) {
707 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
708 _("Cannot send message: sender address not valid."));
712 camel_operation_push_message (cancellable, _("Sending message"));
714 /* find out if the message has 8bit mime parts */
715 has_8bit_parts = camel_mime_message_has_8bit_parts (message);
717 /* If the connection needs a ReSET, then do so */
718 if (smtp_transport->need_rset &&
719 !smtp_rset (smtp_transport, cancellable, error)) {
720 camel_operation_pop_message (cancellable);
723 smtp_transport->need_rset = FALSE;
725 /* rfc1652 (8BITMIME) requires that you notify the ESMTP daemon that
726 * you'll be sending an 8bit mime message at "MAIL FROM:" time. */
728 smtp_transport, addr, has_8bit_parts, cancellable, error)) {
729 camel_operation_pop_message (cancellable);
733 len = camel_address_length (recipients);
736 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
737 _("Cannot send message: no recipients defined."));
738 camel_operation_pop_message (cancellable);
739 smtp_transport->need_rset = TRUE;
743 cia = CAMEL_INTERNET_ADDRESS (recipients);
744 for (i = 0; i < len; i++) {
747 if (!camel_internet_address_get (cia, i, NULL, &addr)) {
749 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
750 _("Cannot send message: "
751 "one or more invalid recipients"));
752 camel_operation_pop_message (cancellable);
753 smtp_transport->need_rset = TRUE;
757 enc = camel_internet_address_encode_address (NULL, NULL, addr);
758 if (!smtp_rcpt (smtp_transport, enc, cancellable, error)) {
760 camel_operation_pop_message (cancellable);
761 smtp_transport->need_rset = TRUE;
767 if (!smtp_data (smtp_transport, message, cancellable, error)) {
768 camel_operation_pop_message (cancellable);
769 smtp_transport->need_rset = TRUE;
773 camel_operation_pop_message (cancellable);
779 smtp_transport_get_service_name (CamelNetworkService *service,
780 CamelNetworkSecurityMethod method)
782 const gchar *service_name;
785 case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
786 service_name = "smtps";
790 service_name = "smtp";
798 smtp_transport_get_default_port (CamelNetworkService *service,
799 CamelNetworkSecurityMethod method)
801 guint16 default_port;
804 case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
805 default_port = SMTPS_PORT;
809 default_port = SMTP_PORT;
817 camel_smtp_transport_class_init (CamelSmtpTransportClass *class)
819 GObjectClass *object_class;
820 CamelServiceClass *service_class;
821 CamelTransportClass *transport_class;
823 object_class = G_OBJECT_CLASS (class);
824 object_class->set_property = smtp_transport_set_property;
825 object_class->get_property = smtp_transport_get_property;
827 service_class = CAMEL_SERVICE_CLASS (class);
828 service_class->settings_type = CAMEL_TYPE_SMTP_SETTINGS;
829 service_class->get_name = smtp_transport_get_name;
830 service_class->connect_sync = smtp_transport_connect_sync;
831 service_class->disconnect_sync = smtp_transport_disconnect_sync;
832 service_class->authenticate_sync = smtp_transport_authenticate_sync;
833 service_class->query_auth_types_sync = smtp_transport_query_auth_types_sync;
835 transport_class = CAMEL_TRANSPORT_CLASS (class);
836 transport_class->send_to_sync = smtp_transport_send_to_sync;
838 /* Inherited from CamelNetworkService. */
839 g_object_class_override_property (
844 /* Inherited from CamelNetworkService. */
845 g_object_class_override_property (
852 camel_network_service_init (CamelNetworkServiceInterface *iface)
854 iface->get_service_name = smtp_transport_get_service_name;
855 iface->get_default_port = smtp_transport_get_default_port;
859 camel_smtp_transport_init (CamelSmtpTransport *smtp)
862 smtp->connected = FALSE;
866 smtp_error_string (gint error)
868 /* SMTP error codes grabbed from rfc821 */
871 return _("Syntax error, command unrecognized");
873 return _("Syntax error in parameters or arguments");
875 return _("Command not implemented");
877 return _("Command parameter not implemented");
879 return _("System status, or system help reply");
881 return _("Help message");
883 return _("Service ready");
885 return _("Service closing transmission channel");
887 return _("Service not available, closing transmission channel");
889 return _("Requested mail action okay, completed");
891 return _("User not local; will forward to <forward-path>");
893 return _("Requested mail action not taken: mailbox unavailable");
895 return _("Requested action not taken: mailbox unavailable");
897 return _("Requested action aborted: error in processing");
899 return _("User not local; please try <forward-path>");
901 return _("Requested action not taken: insufficient system storage");
903 return _("Requested mail action aborted: exceeded storage allocation");
905 return _("Requested action not taken: mailbox name not allowed");
907 return _("Start mail input; end with <CRLF>.<CRLF>");
909 return _("Transaction failed");
911 /* AUTH error codes: */
913 return _("A password transition is needed");
915 return _("Authentication mechanism is too weak");
917 return _("Encryption required for requested authentication mechanism");
919 return _("Temporary authentication failure");
921 return _("Authentication required");
929 esmtp_get_authtypes (const guchar *buffer)
931 const guchar *start, *end;
932 GHashTable *table = NULL;
936 /* make sure there is at least one delimiter
937 * character in the AUTH response */
938 if (!isspace ((gint) *start) && *start != '=')
941 /* advance to the first token */
942 while (isspace ((gint) *start) || *start == '=')
948 table = g_hash_table_new (g_str_hash, g_str_equal);
953 /* advance to the end of the token */
955 while (*end && !isspace ((gint) *end))
958 type = g_strndup ((gchar *) start, end - start);
959 g_hash_table_insert (table, type, type);
961 /* advance to the next token */
963 while (isspace ((gint) *start))
971 smtp_next_token (const gchar *buf)
975 token = (const guchar *) buf;
976 while (*token && !isspace ((gint) *token))
979 while (*token && isspace ((gint) *token))
982 return (const gchar *) token;
985 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : (c) - 'A' + 10)
989 * 5.1.1 Mailbox "nosuchuser" does not exist
991 * The human-readable status code is what we want. Since this text
992 * could possibly be encoded, we must decode it.
994 * "xtext" is formally defined as follows:
996 * xtext = *( xchar / hexchar / linear-white-space / comment )
998 * xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
999 * except for "+", "\" and "(".
1001 * "hexchar"s are intended to encode octets that cannot be represented
1002 * as plain text, either because they are reserved, or because they are
1003 * non-printable. However, any octet value may be represented by a
1006 * hexchar = ASCII "+" immediately followed by two upper case
1007 * hexadecimal digits
1010 smtp_decode_status_code (const gchar *in,
1013 guchar *inptr, *outptr;
1014 const guchar *inend;
1017 outbuf = (gchar *) g_malloc (len + 1);
1018 outptr = (guchar *) outbuf;
1020 inptr = (guchar *) in;
1021 inend = inptr + len;
1022 while (inptr < inend) {
1023 if (*inptr == '+') {
1024 if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
1025 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
1028 *outptr++ = *inptr++;
1030 *outptr++ = *inptr++;
1038 /* converts string str to local encoding, thinking it's in utf8.
1039 * If fails, then converts all character greater than 127 to hex values.
1040 * Also those under 32, other than \n, \r, \t.
1041 * Note that the c is signed character, so all characters above 127 have
1045 convert_to_local (GString *str)
1049 buf = g_locale_from_utf8 (str->str, str->len, NULL, NULL, NULL);
1054 GString *s = g_string_new_len (str->str, str->len);
1056 g_string_truncate (str, 0);
1058 for (i = 0; i < s->len; i++) {
1061 if (c < 32 && c != '\n' && c != '\r' && c != '\t')
1062 g_string_append_printf (str, "<%X%X>", (c >> 4) & 0xF, c & 0xF);
1064 g_string_append_c (str, c);
1067 g_string_free (s, TRUE);
1069 g_string_truncate (str, 0);
1070 g_string_append (str, buf);
1077 smtp_set_error (CamelSmtpTransport *transport,
1078 const gchar *respbuf,
1079 GCancellable *cancellable,
1082 const gchar *token, *rbuf = respbuf;
1083 gchar *buffer = NULL;
1086 g_return_if_fail (respbuf != NULL);
1088 string = g_string_new ("");
1090 if (transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES)
1091 token = smtp_next_token (rbuf + 4);
1095 if (*token == '\0') {
1097 g_string_free (string, TRUE);
1098 goto fake_status_code;
1101 g_string_append (string, token);
1102 if (*(rbuf + 3) == '-') {
1104 buffer = camel_stream_buffer_read_line (
1105 CAMEL_STREAM_BUFFER (transport->istream),
1107 g_string_append_c (string, '\n');
1116 convert_to_local (string);
1117 if (!(transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES) && string->len) {
1118 string->str = g_strstrip (string->str);
1119 string->len = strlen (string->str);
1122 g_string_free (string, TRUE);
1123 goto fake_status_code;
1128 CAMEL_ERROR_GENERIC,
1131 g_string_free (string, TRUE);
1133 buffer = smtp_decode_status_code (string->str, string->len);
1134 g_string_free (string, TRUE);
1136 goto fake_status_code;
1140 CAMEL_ERROR_GENERIC,
1150 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1151 "%s", smtp_error_string (atoi (respbuf)));
1155 smtp_helo (CamelSmtpTransport *transport,
1156 GCancellable *cancellable,
1159 gchar *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
1161 GResolver *resolver;
1162 GInetAddress *address;
1163 GError *local_error = NULL;
1165 /* these are flags that we set, so unset them in case we
1166 * are being called a second time (ie, after a STARTTLS) */
1167 transport->flags &= ~(CAMEL_SMTP_TRANSPORT_8BITMIME |
1168 CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES |
1169 CAMEL_SMTP_TRANSPORT_STARTTLS);
1171 if (transport->authtypes) {
1172 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
1173 g_hash_table_destroy (transport->authtypes);
1174 transport->authtypes = NULL;
1177 resolver = g_resolver_get_default ();
1178 address = g_inet_socket_address_get_address (
1179 G_INET_SOCKET_ADDRESS (transport->local_address));
1181 name = g_resolver_lookup_by_address (
1182 resolver, address, cancellable, &local_error);
1185 g_return_val_if_fail (
1186 ((name != NULL) && (local_error == NULL)) ||
1187 ((name == NULL) && (local_error != NULL)), FALSE);
1189 if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1192 g_clear_error (&local_error);
1195 GSocketFamily family;
1198 string = g_inet_address_to_string (address);
1199 family = g_inet_address_get_family (address);
1200 if (family == G_SOCKET_FAMILY_IPV6)
1201 name = g_strdup_printf ("[IPv6:%s]", string);
1203 name = g_strdup_printf ("[%s]", string);
1207 camel_operation_push_message (cancellable, _("SMTP Greeting"));
1209 token = (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) ? "EHLO" : "HELO";
1210 cmdbuf = g_strdup_printf ("%s %s\r\n", token, name);
1213 d (fprintf (stderr, "sending : %s", cmdbuf));
1214 if (camel_stream_write_string (
1215 transport->ostream, cmdbuf, cancellable, error) == -1) {
1217 g_prefix_error (error, _("HELO command failed: "));
1218 camel_operation_pop_message (cancellable);
1225 /* Check for "250" */
1227 respbuf = camel_stream_buffer_read_line (
1228 CAMEL_STREAM_BUFFER (transport->istream),
1229 cancellable, error);
1230 if (respbuf == NULL) {
1231 g_prefix_error (error, _("HELO command failed: "));
1232 transport->connected = FALSE;
1233 camel_operation_pop_message (cancellable);
1236 if (strncmp (respbuf, "250", 3)) {
1238 transport, respbuf, cancellable, error);
1239 g_prefix_error (error, _("HELO command failed: "));
1240 camel_operation_pop_message (cancellable);
1245 token = respbuf + 4;
1247 if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) {
1248 if (!g_ascii_strncasecmp (token, "8BITMIME", 8)) {
1249 transport->flags |= CAMEL_SMTP_TRANSPORT_8BITMIME;
1250 } else if (!g_ascii_strncasecmp (token, "ENHANCEDSTATUSCODES", 19)) {
1251 transport->flags |= CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES;
1252 } else if (!g_ascii_strncasecmp (token, "STARTTLS", 8)) {
1253 transport->flags |= CAMEL_SMTP_TRANSPORT_STARTTLS;
1254 } else if (!g_ascii_strncasecmp (token, "AUTH", 4)) {
1255 if (!transport->authtypes || transport->flags & CAMEL_SMTP_TRANSPORT_AUTH_EQUAL) {
1256 /* Don't bother parsing any authtypes if we already have a list.
1257 * Some servers will list AUTH twice, once the standard way and
1258 * once the way Microsoft Outlook requires them to be:
1260 * 250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-MD5
1261 * 250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-MD5
1263 * Since they can come in any order, parse each list that we get
1264 * until we parse an authtype list that does not use the AUTH=
1265 * format. We want to let the standard way have priority over the
1269 if (token[4] == '=')
1270 transport->flags |= CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
1272 transport->flags &= ~CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
1274 /* parse for supported AUTH types */
1277 if (transport->authtypes) {
1278 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
1279 g_hash_table_destroy (transport->authtypes);
1282 transport->authtypes = esmtp_get_authtypes ((const guchar *) token);
1286 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1289 camel_operation_pop_message (cancellable);
1295 smtp_mail (CamelSmtpTransport *transport,
1296 const gchar *sender,
1297 gboolean has_8bit_parts,
1298 GCancellable *cancellable,
1301 /* we gotta tell the smtp server who we are. (our email addy) */
1302 gchar *cmdbuf, *respbuf = NULL;
1304 if (transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME && has_8bit_parts)
1305 cmdbuf = g_strdup_printf ("MAIL FROM:<%s> BODY=8BITMIME\r\n", sender);
1307 cmdbuf = g_strdup_printf ("MAIL FROM:<%s>\r\n", sender);
1309 d (fprintf (stderr, "sending : %s", cmdbuf));
1311 if (camel_stream_write_string (
1312 transport->ostream, cmdbuf, cancellable, error) == -1) {
1314 g_prefix_error (error, _("MAIL FROM command failed: "));
1315 camel_service_disconnect_sync (
1316 CAMEL_SERVICE (transport),
1317 FALSE, cancellable, NULL);
1323 /* Check for "250 Sender OK..." */
1325 respbuf = camel_stream_buffer_read_line (
1326 CAMEL_STREAM_BUFFER (transport->istream),
1327 cancellable, error);
1328 if (respbuf == NULL) {
1329 g_prefix_error (error, _("MAIL FROM command failed: "));
1330 camel_service_disconnect_sync (
1331 CAMEL_SERVICE (transport),
1332 FALSE, cancellable, NULL);
1335 if (strncmp (respbuf, "250", 3)) {
1337 transport, respbuf, cancellable, error);
1339 error, _("MAIL FROM command failed: "));
1343 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1350 smtp_rcpt (CamelSmtpTransport *transport,
1351 const gchar *recipient,
1352 GCancellable *cancellable,
1355 /* we gotta tell the smtp server who we are going to be sending
1357 gchar *cmdbuf, *respbuf = NULL;
1359 cmdbuf = g_strdup_printf ("RCPT TO:<%s>\r\n", recipient);
1361 d (fprintf (stderr, "sending : %s", cmdbuf));
1363 if (camel_stream_write_string (
1364 transport->ostream, cmdbuf, cancellable, error) == -1) {
1366 g_prefix_error (error, _("RCPT TO command failed: "));
1367 camel_service_disconnect_sync (
1368 CAMEL_SERVICE (transport),
1369 FALSE, cancellable, NULL);
1376 /* Check for "250 Recipient OK..." */
1378 respbuf = camel_stream_buffer_read_line (
1379 CAMEL_STREAM_BUFFER (transport->istream),
1380 cancellable, error);
1381 if (respbuf == NULL) {
1383 error, _("RCPT TO <%s> failed: "), recipient);
1384 camel_service_disconnect_sync (
1385 CAMEL_SERVICE (transport),
1386 FALSE, cancellable, NULL);
1389 if (strncmp (respbuf, "250", 3)) {
1391 transport, respbuf, cancellable, error);
1393 error, _("RCPT TO <%s> failed: "), recipient);
1398 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1405 smtp_data (CamelSmtpTransport *transport,
1406 CamelMimeMessage *message,
1407 GCancellable *cancellable,
1410 struct _camel_header_raw *header, *savedbcc, *n, *tail;
1411 CamelBestencEncoding enctype = CAMEL_BESTENC_8BIT;
1412 CamelStream *filtered_stream;
1413 gchar *cmdbuf, *respbuf = NULL;
1414 CamelMimeFilter *filter;
1415 CamelStreamNull *null;
1418 /* If the server doesn't support 8BITMIME, set our required encoding to be 7bit */
1419 if (!(transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME))
1420 enctype = CAMEL_BESTENC_7BIT;
1422 /* FIXME: should we get the best charset too?? */
1423 /* Changes the encoding of all mime parts to fit within our required
1424 * encoding type and also force any text parts with long lines (longer
1425 * than 998 octets) to wrap by QP or base64 encoding them. */
1426 camel_mime_message_set_best_encoding (
1427 message, CAMEL_BESTENC_GET_ENCODING, enctype);
1429 cmdbuf = g_strdup ("DATA\r\n");
1431 d (fprintf (stderr, "sending : %s", cmdbuf));
1433 if (camel_stream_write_string (
1434 transport->ostream, cmdbuf, cancellable, error) == -1) {
1436 g_prefix_error (error, _("DATA command failed: "));
1437 camel_service_disconnect_sync (
1438 CAMEL_SERVICE (transport),
1439 FALSE, cancellable, NULL);
1444 respbuf = camel_stream_buffer_read_line (
1445 CAMEL_STREAM_BUFFER (transport->istream), cancellable, error);
1446 if (respbuf == NULL) {
1447 g_prefix_error (error, _("DATA command failed: "));
1448 camel_service_disconnect_sync (
1449 CAMEL_SERVICE (transport),
1450 FALSE, cancellable, NULL);
1453 if (strncmp (respbuf, "354", 3) != 0) {
1454 /* We should have gotten instructions on how to use the DATA
1455 * command: 354 Enter mail, end with "." on a line by itself
1457 smtp_set_error (transport, respbuf, cancellable, error);
1458 g_prefix_error (error, _("DATA command failed: "));
1466 /* unlink the bcc headers */
1468 tail = (struct _camel_header_raw *) &savedbcc;
1470 header = (struct _camel_header_raw *) &CAMEL_MIME_PART (message)->headers;
1473 if (!g_ascii_strcasecmp (n->name, "Bcc")) {
1474 header->next = n->next;
1485 /* find out how large the message is... */
1486 null = CAMEL_STREAM_NULL (camel_stream_null_new ());
1487 camel_data_wrapper_write_to_stream_sync (
1488 CAMEL_DATA_WRAPPER (message),
1489 CAMEL_STREAM (null), NULL, NULL);
1491 filtered_stream = camel_stream_filter_new (transport->ostream);
1493 /* setup progress reporting for message sending... */
1494 filter = camel_mime_filter_progress_new (cancellable, null->written);
1495 camel_stream_filter_add (
1496 CAMEL_STREAM_FILTER (filtered_stream), filter);
1497 g_object_unref (filter);
1498 g_object_unref (null);
1500 /* setup LF->CRLF conversion */
1501 filter = camel_mime_filter_crlf_new (
1502 CAMEL_MIME_FILTER_CRLF_ENCODE,
1503 CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
1504 camel_stream_filter_add (
1505 CAMEL_STREAM_FILTER (filtered_stream), filter);
1506 g_object_unref (filter);
1508 /* write the message */
1509 ret = camel_data_wrapper_write_to_stream_sync (
1510 CAMEL_DATA_WRAPPER (message),
1511 filtered_stream, cancellable, error);
1513 /* restore the bcc headers */
1514 header->next = savedbcc;
1517 g_prefix_error (error, _("DATA command failed: "));
1519 g_object_unref (filtered_stream);
1521 camel_service_disconnect_sync (
1522 CAMEL_SERVICE (transport),
1523 FALSE, cancellable, NULL);
1527 camel_stream_flush (filtered_stream, cancellable, NULL);
1528 g_object_unref (filtered_stream);
1530 /* terminate the message body */
1532 d (fprintf (stderr, "sending : \\r\\n.\\r\\n\n"));
1534 if (camel_stream_write (
1535 transport->ostream, "\r\n.\r\n", 5,
1536 cancellable, error) == -1) {
1537 g_prefix_error (error, _("DATA command failed: "));
1538 camel_service_disconnect_sync (
1539 CAMEL_SERVICE (transport),
1540 FALSE, cancellable, NULL);
1545 /* Check for "250 Sender OK..." */
1547 respbuf = camel_stream_buffer_read_line (
1548 CAMEL_STREAM_BUFFER (transport->istream),
1549 cancellable, error);
1550 if (respbuf == NULL) {
1551 g_prefix_error (error, _("DATA command failed: "));
1552 camel_service_disconnect_sync (
1553 CAMEL_SERVICE (transport),
1554 FALSE, cancellable, NULL);
1557 if (strncmp (respbuf, "250", 3) != 0) {
1559 transport, respbuf, cancellable, error);
1560 g_prefix_error (error, _("DATA command failed: "));
1564 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1571 smtp_rset (CamelSmtpTransport *transport,
1572 GCancellable *cancellable,
1575 /* we are going to reset the smtp server (just to be nice) */
1576 gchar *cmdbuf, *respbuf = NULL;
1578 cmdbuf = g_strdup ("RSET\r\n");
1580 d (fprintf (stderr, "sending : %s", cmdbuf));
1582 if (camel_stream_write_string (
1583 transport->ostream, cmdbuf, cancellable, error) == -1) {
1585 g_prefix_error (error, _("RSET command failed: "));
1586 camel_service_disconnect_sync (
1587 CAMEL_SERVICE (transport),
1588 FALSE, cancellable, NULL);
1594 /* Check for "250" */
1596 respbuf = camel_stream_buffer_read_line (
1597 CAMEL_STREAM_BUFFER (transport->istream),
1598 cancellable, error);
1599 if (respbuf == NULL) {
1600 g_prefix_error (error, _("RSET command failed: "));
1601 camel_service_disconnect_sync (
1602 CAMEL_SERVICE (transport),
1603 FALSE, cancellable, NULL);
1606 if (strncmp (respbuf, "250", 3) != 0) {
1608 transport, respbuf, cancellable, error);
1609 g_prefix_error (error, _("RSET command failed: "));
1613 } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1620 smtp_quit (CamelSmtpTransport *transport,
1621 GCancellable *cancellable,
1624 /* we are going to reset the smtp server (just to be nice) */
1625 gchar *cmdbuf, *respbuf = NULL;
1627 cmdbuf = g_strdup ("QUIT\r\n");
1629 d (fprintf (stderr, "sending : %s", cmdbuf));
1631 if (camel_stream_write_string (
1632 transport->ostream, cmdbuf, cancellable, error) == -1) {
1634 g_prefix_error (error, _("QUIT command failed: "));
1640 /* Check for "221" */
1642 respbuf = camel_stream_buffer_read_line (
1643 CAMEL_STREAM_BUFFER (transport->istream),
1644 cancellable, error);
1646 d (fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1647 if (respbuf == NULL) {
1648 g_prefix_error (error, _("QUIT command failed: "));
1649 transport->connected = FALSE;
1652 if (strncmp (respbuf, "221", 3) != 0) {
1654 transport, respbuf, cancellable, error);
1655 g_prefix_error (error, _("QUIT command failed: "));
1659 } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */