updated changelog
[platform/upstream/evolution-data-server.git] / camel / providers / smtp / camel-smtp-transport.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-smtp-transport.c : class for a smtp transport */
3
4 /*
5  * Authors: Jeffrey Stedfast <fejj@ximian.com>
6  *
7  * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
8  *
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.
12  *
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
16  *for more details.
17  *
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/>.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <ctype.h>
27 #include <errno.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/param.h>
33 #include <sys/types.h>
34
35 #include <glib/gi18n-lib.h>
36
37 #include "camel-smtp-settings.h"
38 #include "camel-smtp-transport.h"
39
40 #ifdef G_OS_WIN32
41 #include <winsock2.h>
42 #include <ws2tcpip.h>
43 #endif
44
45 #undef MIN
46 #undef MAX
47
48 #define d(x) (camel_debug ("smtp") ? (x) : 0)
49
50 /* Specified in RFC 821 */
51 #define SMTP_PORT  25
52 #define SMTPS_PORT 465
53
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)
58
59 /* set if we are using authtypes from a broken AUTH= */
60 #define CAMEL_SMTP_TRANSPORT_AUTH_EQUAL             (1 << 4)
61
62 enum {
63         PROP_0,
64         PROP_CONNECTABLE,
65         PROP_HOST_REACHABLE
66 };
67
68 /* support prototypes */
69 static GHashTable *     esmtp_get_authtypes     (const guchar *buffer);
70 static gboolean         smtp_helo               (CamelSmtpTransport *transport,
71                                                  GCancellable *cancellable,
72                                                  GError **error);
73 static gboolean         smtp_mail               (CamelSmtpTransport *transport,
74                                                  const gchar *sender,
75                                                  gboolean has_8bit_parts,
76                                                  GCancellable *cancellable,
77                                                  GError **error);
78 static gboolean         smtp_rcpt               (CamelSmtpTransport *transport,
79                                                  const gchar *recipient,
80                                                  GCancellable *cancellable,
81                                                  GError **error);
82 static gboolean         smtp_data               (CamelSmtpTransport *transport,
83                                                  CamelMimeMessage *message,
84                                                  GCancellable *cancellable,
85                                                  GError **error);
86 static gboolean         smtp_rset               (CamelSmtpTransport *transport,
87                                                  GCancellable *cancellable,
88                                                  GError **error);
89 static gboolean         smtp_quit               (CamelSmtpTransport *transport,
90                                                  GCancellable *cancellable,
91                                                  GError **error);
92 static void             smtp_set_error          (CamelSmtpTransport *transport,
93                                                  const gchar *respbuf,
94                                                  GCancellable *cancellable,
95                                                  GError **error);
96
97 /* Forward Declarations */
98 static void camel_network_service_init (CamelNetworkServiceInterface *iface);
99
100 G_DEFINE_TYPE_WITH_CODE (
101         CamelSmtpTransport,
102         camel_smtp_transport,
103         CAMEL_TYPE_TRANSPORT,
104         G_IMPLEMENT_INTERFACE (
105                 CAMEL_TYPE_NETWORK_SERVICE,
106                 camel_network_service_init))
107
108 static gboolean
109 connect_to_server (CamelService *service,
110                    GCancellable *cancellable,
111                    GError **error)
112 {
113         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
114         CamelNetworkSettings *network_settings;
115         CamelNetworkSecurityMethod method;
116         CamelSettings *settings;
117         CamelStream *stream;
118         GIOStream *base_stream;
119         GIOStream *tls_stream;
120         gchar *respbuf = NULL;
121         gboolean success = TRUE;
122         gchar *host;
123
124         if (!CAMEL_SERVICE_CLASS (camel_smtp_transport_parent_class)->
125                 connect_sync (service, cancellable, error))
126                 return FALSE;
127
128         /* set some smtp transport defaults */
129         transport->flags = 0;
130         transport->authtypes = NULL;
131
132         settings = camel_service_ref_settings (service);
133
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);
137
138         g_object_unref (settings);
139
140         base_stream = camel_network_service_connect_sync (
141                 CAMEL_NETWORK_SERVICE (service), cancellable, error);
142
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);
148
149                 stream = camel_stream_new (base_stream);
150                 g_object_unref (base_stream);
151         } else {
152                 success = FALSE;
153                 goto exit;
154         }
155
156         transport->connected = TRUE;
157
158         transport->ostream = stream;
159         transport->istream = camel_stream_buffer_new (
160                 stream, CAMEL_STREAM_BUFFER_READ);
161
162         /* Read the greeting, note whether the server is ESMTP or not. */
163         do {
164                 /* Check for "220" */
165                 g_free (respbuf);
166                 respbuf = camel_stream_buffer_read_line (
167                         CAMEL_STREAM_BUFFER (transport->istream),
168                         cancellable, error);
169                 if (respbuf == NULL) {
170                         g_prefix_error (error, _("Welcome response error: "));
171                         transport->connected = FALSE;
172                         success = FALSE;
173                         goto exit;
174                 }
175                 if (strncmp (respbuf, "220", 3)) {
176                         smtp_set_error (
177                                 transport, respbuf, cancellable, error);
178                         g_prefix_error (error, _("Welcome response error: "));
179                         g_free (respbuf);
180                         success = FALSE;
181                         goto exit;
182                 }
183         } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
184         g_free (respbuf);
185
186         /* Try sending EHLO */
187         transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
188         if (!smtp_helo (transport, cancellable, error)) {
189                 if (!transport->connected) {
190                         success = FALSE;
191                         goto exit;
192                 }
193
194                 /* Fall back to HELO */
195                 g_clear_error (error);
196                 transport->flags &= ~CAMEL_SMTP_TRANSPORT_IS_ESMTP;
197
198                 if (!smtp_helo (transport, cancellable, error)) {
199                         success = FALSE;
200                         goto exit;
201                 }
202         }
203
204         /* Clear any EHLO/HELO exception and assume that
205          * any SMTP errors encountered were non-fatal. */
206         g_clear_error (error);
207
208         if (method != CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT)
209                 goto exit;  /* we're done */
210
211         if (!(transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS)) {
212                 g_set_error (
213                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
214                         _("Failed to connect to SMTP server %s in secure mode: %s"),
215                         host, _("STARTTLS not supported"));
216
217                 success = FALSE;
218                 goto exit;
219         }
220
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: "));
225                 success = FALSE;
226                 goto exit;
227         }
228
229         respbuf = NULL;
230
231         do {
232                 /* Check for "220 Ready for TLS" */
233                 g_free (respbuf);
234                 respbuf = camel_stream_buffer_read_line (
235                         CAMEL_STREAM_BUFFER (transport->istream),
236                         cancellable, error);
237                 if (respbuf == NULL) {
238                         g_prefix_error (error, _("STARTTLS command failed: "));
239                         transport->connected = FALSE;
240                         success = FALSE;
241                         goto exit;
242                 }
243                 if (strncmp (respbuf, "220", 3) != 0) {
244                         smtp_set_error (
245                                 transport, respbuf, cancellable, error);
246                         g_prefix_error (error, _("STARTTLS command failed: "));
247                         g_free (respbuf);
248                         success = FALSE;
249                         goto exit;
250                 }
251         } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
252
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);
258
259         if (tls_stream != NULL) {
260                 camel_stream_set_base_stream (stream, tls_stream);
261                 g_object_unref (tls_stream);
262         } else {
263                 g_prefix_error (
264                         error,
265                         _("Failed to connect to SMTP server %s in secure mode: "),
266                         host);
267                 success = FALSE;
268                 goto exit;
269         }
270
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)) {
274                 success = FALSE;
275         }
276
277 exit:
278         g_free (host);
279
280         if (!success) {
281                 transport->connected = FALSE;
282                 g_clear_object (&transport->istream);
283                 g_clear_object (&transport->ostream);
284         }
285
286         return success;
287 }
288
289 static void
290 authtypes_free (gpointer key,
291                 gpointer value,
292                 gpointer data)
293 {
294         g_free (value);
295 }
296
297 static void
298 smtp_transport_set_property (GObject *object,
299                              guint property_id,
300                              const GValue *value,
301                              GParamSpec *pspec)
302 {
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));
308                         return;
309         }
310
311         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
312 }
313
314 static void
315 smtp_transport_get_property (GObject *object,
316                              guint property_id,
317                              GValue *value,
318                              GParamSpec *pspec)
319 {
320         switch (property_id) {
321                 case PROP_CONNECTABLE:
322                         g_value_take_object (
323                                 value,
324                                 camel_network_service_ref_connectable (
325                                 CAMEL_NETWORK_SERVICE (object)));
326                         return;
327
328                 case PROP_HOST_REACHABLE:
329                         g_value_set_boolean (
330                                 value,
331                                 camel_network_service_get_host_reachable (
332                                 CAMEL_NETWORK_SERVICE (object)));
333                         return;
334         }
335
336         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
337 }
338
339 static gchar *
340 smtp_transport_get_name (CamelService *service,
341                          gboolean brief)
342 {
343         CamelNetworkSettings *network_settings;
344         CamelSettings *settings;
345         gchar *host;
346         gchar *name;
347
348         settings = camel_service_ref_settings (service);
349
350         network_settings = CAMEL_NETWORK_SETTINGS (settings);
351         host = camel_network_settings_dup_host (network_settings);
352
353         g_object_unref (settings);
354
355         if (brief)
356                 name = g_strdup_printf (
357                         _("SMTP server %s"), host);
358         else
359                 name = g_strdup_printf (
360                         _("SMTP mail delivery via %s"), host);
361
362         g_free (host);
363
364         return name;
365 }
366
367 static gboolean
368 smtp_transport_connect_sync (CamelService *service,
369                              GCancellable *cancellable,
370                              GError **error)
371 {
372         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
373         CamelNetworkSettings *network_settings;
374         CamelSettings *settings;
375         gchar *host;
376         gchar *mechanism;
377         gboolean auth_required;
378         gboolean success = TRUE;
379
380         settings = camel_service_ref_settings (service);
381
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);
385
386         g_object_unref (settings);
387
388         /* We (probably) need to check popb4smtp before we connect ... */
389         if (g_strcmp0 (mechanism, "POPB4SMTP") == 0) {
390                 GByteArray *chal;
391                 CamelSasl *sasl;
392
393                 sasl = camel_sasl_new ("smtp", "POPB4SMTP", service);
394                 chal = camel_sasl_challenge_sync (sasl, NULL, cancellable, error);
395                 if (chal != NULL)
396                         g_byte_array_free (chal, TRUE);
397
398                 if (camel_sasl_get_authenticated (sasl))
399                         success = connect_to_server (
400                                 service, cancellable, error);
401                 else
402                         success = FALSE;
403
404                 g_object_unref (sasl);
405
406                 goto exit;
407         }
408
409         success = connect_to_server (service, cancellable, error);
410
411         if (!success)
412                 return FALSE;
413
414         /* check to see if AUTH is required, if so...then AUTH ourselves */
415         auth_required =
416                 (mechanism != NULL) &&
417                 (transport->authtypes != NULL) &&
418                 (g_hash_table_size (transport->authtypes) > 0) &&
419                 (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP);
420
421         if (auth_required) {
422                 CamelSession *session;
423
424                 session = camel_service_ref_session (service);
425
426                 if (g_hash_table_lookup (transport->authtypes, mechanism)) {
427                         success = camel_session_authenticate_sync (
428                                 session, service, mechanism,
429                                 cancellable, error);
430                 } else {
431                         g_set_error (
432                                 error, CAMEL_SERVICE_ERROR,
433                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
434                                 _("SMTP server %s does not support %s "
435                                 "authentication"), host, mechanism);
436                         success = FALSE;
437                 }
438
439                 g_object_unref (session);
440
441                 if (!success)
442                         transport->connected = FALSE;
443         }
444
445 exit:
446         g_free (host);
447         g_free (mechanism);
448
449         return success;
450 }
451
452 static gboolean
453 smtp_transport_disconnect_sync (CamelService *service,
454                                 gboolean clean,
455                                 GCancellable *cancellable,
456                                 GError **error)
457 {
458         CamelServiceClass *service_class;
459         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
460
461         /*if (!service->connected)
462          *      return TRUE;
463          */
464
465         if (transport->connected && clean) {
466                 /* send the QUIT command to the SMTP server */
467                 smtp_quit (transport, cancellable, NULL);
468         }
469
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))
473                 return FALSE;
474
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;
479         }
480
481         g_clear_object (&transport->istream);
482         g_clear_object (&transport->ostream);
483         g_clear_object (&transport->local_address);
484
485         transport->connected = FALSE;
486
487         return TRUE;
488 }
489
490 static CamelAuthenticationResult
491 smtp_transport_authenticate_sync (CamelService *service,
492                                   const gchar *mechanism,
493                                   GCancellable *cancellable,
494                                   GError **error)
495 {
496         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
497         CamelAuthenticationResult result;
498         CamelSasl *sasl;
499         gchar *cmdbuf, *respbuf = NULL, *challenge;
500         gboolean auth_challenge = FALSE;
501
502         if (mechanism == NULL) {
503                 g_set_error (
504                         error, CAMEL_SERVICE_ERROR,
505                         CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
506                         _("No SASL mechanism was specified"));
507                 return CAMEL_AUTHENTICATION_ERROR;
508         }
509
510         sasl = camel_sasl_new ("smtp", mechanism, service);
511         if (sasl == NULL) {
512                 g_set_error (
513                         error, CAMEL_SERVICE_ERROR,
514                         CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
515                         _("No support for %s authentication"), mechanism);
516                 return CAMEL_AUTHENTICATION_ERROR;
517         }
518
519         challenge = camel_sasl_challenge_base64_sync (
520                 sasl, NULL, cancellable, error);
521         if (challenge) {
522                 auth_challenge = TRUE;
523                 cmdbuf = g_strdup_printf (
524                         "AUTH %s %s\r\n", mechanism, challenge);
525                 g_free (challenge);
526         } else {
527                 cmdbuf = g_strdup_printf (
528                         "AUTH %s\r\n", mechanism);
529         }
530
531         d (fprintf (stderr, "sending : %s", cmdbuf));
532         if (camel_stream_write_string (
533                 transport->ostream, cmdbuf,
534                 cancellable, error) == -1) {
535                 g_free (cmdbuf);
536                 g_prefix_error (error, _("AUTH command failed: "));
537                 goto lose;
538         }
539         g_free (cmdbuf);
540
541         respbuf = camel_stream_buffer_read_line (
542                 CAMEL_STREAM_BUFFER (transport->istream),
543                 cancellable, error);
544
545         while (!camel_sasl_get_authenticated (sasl)) {
546                 if (!respbuf) {
547                         g_prefix_error (error, _("AUTH command failed: "));
548                         transport->connected = FALSE;
549                         goto lose;
550                 }
551
552                 /* the server may have accepted our initial response */
553                 if (strncmp (respbuf, "235", 3) == 0)
554                         break;
555
556                 /* the server challenge/response should follow a 334 code */
557                 if (strncmp (respbuf, "334", 3) != 0) {
558                         smtp_set_error (
559                                 transport, respbuf, cancellable, error);
560                         g_prefix_error (error, _("AUTH command failed: "));
561                         goto lose;
562                 }
563
564                 if (FALSE) {
565                 broken_smtp_server:
566                         d (fprintf (
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",
573                                 mechanism));
574                 }
575
576                 /* eat whtspc */
577                 for (challenge = respbuf + 4; isspace (*challenge); challenge++);
578
579                 challenge = camel_sasl_challenge_base64_sync (
580                         sasl, challenge, cancellable, error);
581                 if (challenge == NULL)
582                         goto break_and_lose;
583
584                 g_free (respbuf);
585
586                 /* send our challenge */
587                 cmdbuf = g_strdup_printf ("%s\r\n", challenge);
588                 g_free (challenge);
589                 d (fprintf (stderr, "sending : %s", cmdbuf));
590                 if (camel_stream_write_string (
591                         transport->ostream, cmdbuf,
592                         cancellable, error) == -1) {
593                         g_free (cmdbuf);
594                         goto lose;
595                 }
596                 g_free (cmdbuf);
597
598                 /* get the server's response */
599                 respbuf = camel_stream_buffer_read_line (
600                         CAMEL_STREAM_BUFFER (transport->istream),
601                         cancellable, error);
602         }
603
604         if (respbuf == NULL)
605                 goto lose;
606
607         /* Work around broken SASL implementations. */
608         if (auth_challenge && strncmp (respbuf, "334", 3) == 0)
609                 goto broken_smtp_server;
610
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. */
618         else {
619                 g_set_error (
620                         error, CAMEL_SERVICE_ERROR,
621                         CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
622                         _("Bad authentication response from server."));
623                 goto lose;
624         }
625
626         goto exit;
627
628 break_and_lose:
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)"));
635
636 lose:
637         result = CAMEL_AUTHENTICATION_ERROR;
638
639 exit:
640         g_object_unref (sasl);
641         g_free (respbuf);
642
643         return result;
644 }
645
646 static GList *
647 smtp_transport_query_auth_types_sync (CamelService *service,
648                                       GCancellable *cancellable,
649                                       GError **error)
650 {
651         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
652         CamelServiceAuthType *authtype;
653         CamelProvider *provider;
654         GList *types, *t, *next;
655
656         if (!connect_to_server (service, cancellable, error))
657                 return NULL;
658
659         if (!transport->authtypes) {
660                 smtp_transport_disconnect_sync (
661                         service, TRUE, cancellable, NULL);
662                 return NULL;
663         }
664
665         provider = camel_service_get_provider (service);
666         types = g_list_copy (provider->authtypes);
667
668         for (t = types; t; t = next) {
669                 authtype = t->data;
670                 next = t->next;
671
672                 if (!g_hash_table_lookup (transport->authtypes, authtype->authproto)) {
673                         types = g_list_remove_link (types, t);
674                         g_list_free_1 (t);
675                 }
676         }
677
678         smtp_transport_disconnect_sync (service, TRUE, cancellable, NULL);
679
680         return types;
681 }
682
683 static gboolean
684 smtp_transport_send_to_sync (CamelTransport *transport,
685                              CamelMimeMessage *message,
686                              CamelAddress *from,
687                              CamelAddress *recipients,
688                              GCancellable *cancellable,
689                              GError **error)
690 {
691         CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (transport);
692         CamelInternetAddress *cia;
693         gboolean has_8bit_parts;
694         const gchar *addr;
695         gint i, len;
696
697         if (!smtp_transport->connected) {
698                 g_set_error (
699                         error, CAMEL_SERVICE_ERROR,
700                         CAMEL_SERVICE_ERROR_NOT_CONNECTED,
701                         _("Cannot send message: service not connected."));
702                 return FALSE;
703         }
704
705         if (!camel_internet_address_get (CAMEL_INTERNET_ADDRESS (from), 0, NULL, &addr)) {
706                 g_set_error (
707                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
708                         _("Cannot send message: sender address not valid."));
709                 return FALSE;
710         }
711
712         camel_operation_push_message (cancellable, _("Sending message"));
713
714         /* find out if the message has 8bit mime parts */
715         has_8bit_parts = camel_mime_message_has_8bit_parts (message);
716
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);
721                 return FALSE;
722         }
723         smtp_transport->need_rset = FALSE;
724
725         /* rfc1652 (8BITMIME) requires that you notify the ESMTP daemon that
726          * you'll be sending an 8bit mime message at "MAIL FROM:" time. */
727         if (!smtp_mail (
728                 smtp_transport, addr, has_8bit_parts, cancellable, error)) {
729                 camel_operation_pop_message (cancellable);
730                 return FALSE;
731         }
732
733         len = camel_address_length (recipients);
734         if (len == 0) {
735                 g_set_error (
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;
740                 return FALSE;
741         }
742
743         cia = CAMEL_INTERNET_ADDRESS (recipients);
744         for (i = 0; i < len; i++) {
745                 gchar *enc;
746
747                 if (!camel_internet_address_get (cia, i, NULL, &addr)) {
748                         g_set_error (
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;
754                         return FALSE;
755                 }
756
757                 enc = camel_internet_address_encode_address (NULL, NULL, addr);
758                 if (!smtp_rcpt (smtp_transport, enc, cancellable, error)) {
759                         g_free (enc);
760                         camel_operation_pop_message (cancellable);
761                         smtp_transport->need_rset = TRUE;
762                         return FALSE;
763                 }
764                 g_free (enc);
765         }
766
767         if (!smtp_data (smtp_transport, message, cancellable, error)) {
768                 camel_operation_pop_message (cancellable);
769                 smtp_transport->need_rset = TRUE;
770                 return FALSE;
771         }
772
773         camel_operation_pop_message (cancellable);
774
775         return TRUE;
776 }
777
778 static const gchar *
779 smtp_transport_get_service_name (CamelNetworkService *service,
780                                  CamelNetworkSecurityMethod method)
781 {
782         const gchar *service_name;
783
784         switch (method) {
785                 case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
786                         service_name = "smtps";
787                         break;
788
789                 default:
790                         service_name = "smtp";
791                         break;
792         }
793
794         return service_name;
795 }
796
797 static guint16
798 smtp_transport_get_default_port (CamelNetworkService *service,
799                                  CamelNetworkSecurityMethod method)
800 {
801         guint16 default_port;
802
803         switch (method) {
804                 case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
805                         default_port = SMTPS_PORT;
806                         break;
807
808                 default:
809                         default_port = SMTP_PORT;
810                         break;
811         }
812
813         return default_port;
814 }
815
816 static void
817 camel_smtp_transport_class_init (CamelSmtpTransportClass *class)
818 {
819         GObjectClass *object_class;
820         CamelServiceClass *service_class;
821         CamelTransportClass *transport_class;
822
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;
826
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;
834
835         transport_class = CAMEL_TRANSPORT_CLASS (class);
836         transport_class->send_to_sync = smtp_transport_send_to_sync;
837
838         /* Inherited from CamelNetworkService. */
839         g_object_class_override_property (
840                 object_class,
841                 PROP_CONNECTABLE,
842                 "connectable");
843
844         /* Inherited from CamelNetworkService. */
845         g_object_class_override_property (
846                 object_class,
847                 PROP_HOST_REACHABLE,
848                 "host-reachable");
849 }
850
851 static void
852 camel_network_service_init (CamelNetworkServiceInterface *iface)
853 {
854         iface->get_service_name = smtp_transport_get_service_name;
855         iface->get_default_port = smtp_transport_get_default_port;
856 }
857
858 static void
859 camel_smtp_transport_init (CamelSmtpTransport *smtp)
860 {
861         smtp->flags = 0;
862         smtp->connected = FALSE;
863 }
864
865 static const gchar *
866 smtp_error_string (gint error)
867 {
868         /* SMTP error codes grabbed from rfc821 */
869         switch (error) {
870         case 500:
871                 return _("Syntax error, command unrecognized");
872         case 501:
873                 return _("Syntax error in parameters or arguments");
874         case 502:
875                 return _("Command not implemented");
876         case 504:
877                 return _("Command parameter not implemented");
878         case 211:
879                 return _("System status, or system help reply");
880         case 214:
881                 return _("Help message");
882         case 220:
883                 return _("Service ready");
884         case 221:
885                 return _("Service closing transmission channel");
886         case 421:
887                 return _("Service not available, closing transmission channel");
888         case 250:
889                 return _("Requested mail action okay, completed");
890         case 251:
891                 return _("User not local; will forward to <forward-path>");
892         case 450:
893                 return _("Requested mail action not taken: mailbox unavailable");
894         case 550:
895                 return _("Requested action not taken: mailbox unavailable");
896         case 451:
897                 return _("Requested action aborted: error in processing");
898         case 551:
899                 return _("User not local; please try <forward-path>");
900         case 452:
901                 return _("Requested action not taken: insufficient system storage");
902         case 552:
903                 return _("Requested mail action aborted: exceeded storage allocation");
904         case 553:
905                 return _("Requested action not taken: mailbox name not allowed");
906         case 354:
907                 return _("Start mail input; end with <CRLF>.<CRLF>");
908         case 554:
909                 return _("Transaction failed");
910
911         /* AUTH error codes: */
912         case 432:
913                 return _("A password transition is needed");
914         case 534:
915                 return _("Authentication mechanism is too weak");
916         case 538:
917                 return _("Encryption required for requested authentication mechanism");
918         case 454:
919                 return _("Temporary authentication failure");
920         case 530:
921                 return _("Authentication required");
922
923         default:
924                 return _("Unknown");
925         }
926 }
927
928 static GHashTable *
929 esmtp_get_authtypes (const guchar *buffer)
930 {
931         const guchar *start, *end;
932         GHashTable *table = NULL;
933
934         start = buffer;
935
936         /* make sure there is at least one delimiter
937          * character in the AUTH response */
938         if (!isspace ((gint) *start) && *start != '=')
939                 return NULL;
940
941         /* advance to the first token */
942         while (isspace ((gint) *start) || *start == '=')
943                 start++;
944
945         if (!*start)
946                 return NULL;
947
948         table = g_hash_table_new (g_str_hash, g_str_equal);
949
950         for (; *start; ) {
951                 gchar *type;
952
953                 /* advance to the end of the token */
954                 end = start;
955                 while (*end && !isspace ((gint) *end))
956                         end++;
957
958                 type = g_strndup ((gchar *) start, end - start);
959                 g_hash_table_insert (table, type, type);
960
961                 /* advance to the next token */
962                 start = end;
963                 while (isspace ((gint) *start))
964                         start++;
965         }
966
967         return table;
968 }
969
970 static const gchar *
971 smtp_next_token (const gchar *buf)
972 {
973         const guchar *token;
974
975         token = (const guchar *) buf;
976         while (*token && !isspace ((gint) *token))
977                 token++;
978
979         while (*token && isspace ((gint) *token))
980                 token++;
981
982         return (const gchar *) token;
983 }
984
985 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : (c) - 'A' + 10)
986
987 /*
988  * example (rfc2034):
989  * 5.1.1 Mailbox "nosuchuser" does not exist
990  *
991  * The human-readable status code is what we want. Since this text
992  * could possibly be encoded, we must decode it.
993  *
994  * "xtext" is formally defined as follows:
995  *
996  *   xtext = *( xchar / hexchar / linear-white-space / comment )
997  *
998  *   xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
999  *        except for "+", "\" and "(".
1000  *
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
1004  * "hexchar".
1005  *
1006  *   hexchar = ASCII "+" immediately followed by two upper case
1007  *        hexadecimal digits
1008  */
1009 static gchar *
1010 smtp_decode_status_code (const gchar *in,
1011                          gsize len)
1012 {
1013         guchar *inptr, *outptr;
1014         const guchar *inend;
1015         gchar *outbuf;
1016
1017         outbuf = (gchar *) g_malloc (len + 1);
1018         outptr = (guchar *) outbuf;
1019
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]);
1026                                 inptr += 3;
1027                         } else
1028                                 *outptr++ = *inptr++;
1029                 } else
1030                         *outptr++ = *inptr++;
1031         }
1032
1033         *outptr = '\0';
1034
1035         return outbuf;
1036 }
1037
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
1042  * negative value.
1043 */
1044 static void
1045 convert_to_local (GString *str)
1046 {
1047         gchar *buf;
1048
1049         buf = g_locale_from_utf8 (str->str, str->len, NULL, NULL, NULL);
1050
1051         if (!buf) {
1052                 gint i;
1053                 gchar c;
1054                 GString *s = g_string_new_len (str->str, str->len);
1055
1056                 g_string_truncate (str, 0);
1057
1058                 for (i = 0; i < s->len; i++) {
1059                         c = s->str[i];
1060
1061                         if (c < 32 && c != '\n' && c != '\r' && c != '\t')
1062                                 g_string_append_printf (str, "<%X%X>", (c >> 4) & 0xF, c & 0xF);
1063                         else
1064                                 g_string_append_c (str, c);
1065                 }
1066
1067                 g_string_free (s, TRUE);
1068         } else {
1069                 g_string_truncate (str, 0);
1070                 g_string_append (str, buf);
1071
1072                 g_free (buf);
1073         }
1074 }
1075
1076 static void
1077 smtp_set_error (CamelSmtpTransport *transport,
1078                 const gchar *respbuf,
1079                 GCancellable *cancellable,
1080                 GError **error)
1081 {
1082         const gchar *token, *rbuf = respbuf;
1083         gchar *buffer = NULL;
1084         GString *string;
1085
1086         g_return_if_fail (respbuf != NULL);
1087
1088         string = g_string_new ("");
1089         do {
1090                 if (transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES)
1091                         token = smtp_next_token (rbuf + 4);
1092                 else
1093                         token = rbuf + 4;
1094
1095                 if (*token == '\0') {
1096                         g_free (buffer);
1097                         g_string_free (string, TRUE);
1098                         goto fake_status_code;
1099                 }
1100
1101                 g_string_append (string, token);
1102                 if (*(rbuf + 3) == '-') {
1103                         g_free (buffer);
1104                         buffer = camel_stream_buffer_read_line (
1105                                 CAMEL_STREAM_BUFFER (transport->istream),
1106                                 cancellable, NULL);
1107                         g_string_append_c (string, '\n');
1108                 } else {
1109                         g_free (buffer);
1110                         buffer = NULL;
1111                 }
1112
1113                 rbuf = buffer;
1114         } while (rbuf);
1115
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);
1120
1121                 if (!string->len) {
1122                         g_string_free (string, TRUE);
1123                         goto fake_status_code;
1124                 }
1125
1126                 g_set_error (
1127                         error, CAMEL_ERROR,
1128                         CAMEL_ERROR_GENERIC,
1129                         "%s", string->str);
1130
1131                 g_string_free (string, TRUE);
1132         } else {
1133                 buffer = smtp_decode_status_code (string->str, string->len);
1134                 g_string_free (string, TRUE);
1135                 if (!buffer)
1136                         goto fake_status_code;
1137
1138                 g_set_error (
1139                         error, CAMEL_ERROR,
1140                         CAMEL_ERROR_GENERIC,
1141                         "%s", buffer);
1142
1143                 g_free (buffer);
1144         }
1145
1146         return;
1147
1148 fake_status_code:
1149         g_set_error (
1150                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1151                 "%s", smtp_error_string (atoi (respbuf)));
1152 }
1153
1154 static gboolean
1155 smtp_helo (CamelSmtpTransport *transport,
1156            GCancellable *cancellable,
1157            GError **error)
1158 {
1159         gchar *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
1160         const gchar *token;
1161         GResolver *resolver;
1162         GInetAddress *address;
1163         GError *local_error = NULL;
1164
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);
1170
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;
1175         }
1176
1177         resolver = g_resolver_get_default ();
1178         address = g_inet_socket_address_get_address (
1179                 G_INET_SOCKET_ADDRESS (transport->local_address));
1180
1181         name = g_resolver_lookup_by_address (
1182                 resolver, address, cancellable, &local_error);
1183
1184         /* Sanity check. */
1185         g_return_val_if_fail (
1186                 ((name != NULL) && (local_error == NULL)) ||
1187                 ((name == NULL) && (local_error != NULL)), FALSE);
1188
1189         if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
1190                 return FALSE;
1191
1192         g_clear_error (&local_error);
1193
1194         if (name == NULL) {
1195                 GSocketFamily family;
1196                 gchar *string;
1197
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);
1202                 else
1203                         name = g_strdup_printf ("[%s]", string);
1204                 g_free (string);
1205         }
1206
1207         camel_operation_push_message (cancellable, _("SMTP Greeting"));
1208
1209         token = (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) ? "EHLO" : "HELO";
1210         cmdbuf = g_strdup_printf ("%s %s\r\n", token, name);
1211         g_free (name);
1212
1213         d (fprintf (stderr, "sending : %s", cmdbuf));
1214         if (camel_stream_write_string (
1215                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1216                 g_free (cmdbuf);
1217                 g_prefix_error (error, _("HELO command failed: "));
1218                 camel_operation_pop_message (cancellable);
1219
1220                 return FALSE;
1221         }
1222         g_free (cmdbuf);
1223
1224         do {
1225                 /* Check for "250" */
1226                 g_free (respbuf);
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);
1234                         return FALSE;
1235                 }
1236                 if (strncmp (respbuf, "250", 3)) {
1237                         smtp_set_error (
1238                                 transport, respbuf, cancellable, error);
1239                         g_prefix_error (error, _("HELO command failed: "));
1240                         camel_operation_pop_message (cancellable);
1241                         g_free (respbuf);
1242                         return FALSE;
1243                 }
1244
1245                 token = respbuf + 4;
1246
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:
1259                                          *
1260                                          * 250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-MD5
1261                                          * 250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-MD5
1262                                          *
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
1266                                          * broken way.
1267                                          **/
1268
1269                                         if (token[4] == '=')
1270                                                 transport->flags |= CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
1271                                         else
1272                                                 transport->flags &= ~CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
1273
1274                                         /* parse for supported AUTH types */
1275                                         token += 4;
1276
1277                                         if (transport->authtypes) {
1278                                                 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
1279                                                 g_hash_table_destroy (transport->authtypes);
1280                                         }
1281
1282                                         transport->authtypes = esmtp_get_authtypes ((const guchar *) token);
1283                                 }
1284                         }
1285                 }
1286         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1287         g_free (respbuf);
1288
1289         camel_operation_pop_message (cancellable);
1290
1291         return TRUE;
1292 }
1293
1294 static gboolean
1295 smtp_mail (CamelSmtpTransport *transport,
1296            const gchar *sender,
1297            gboolean has_8bit_parts,
1298            GCancellable *cancellable,
1299            GError **error)
1300 {
1301         /* we gotta tell the smtp server who we are. (our email addy) */
1302         gchar *cmdbuf, *respbuf = NULL;
1303
1304         if (transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME && has_8bit_parts)
1305                 cmdbuf = g_strdup_printf ("MAIL FROM:<%s> BODY=8BITMIME\r\n", sender);
1306         else
1307                 cmdbuf = g_strdup_printf ("MAIL FROM:<%s>\r\n", sender);
1308
1309         d (fprintf (stderr, "sending : %s", cmdbuf));
1310
1311         if (camel_stream_write_string (
1312                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1313                 g_free (cmdbuf);
1314                 g_prefix_error (error, _("MAIL FROM command failed: "));
1315                 camel_service_disconnect_sync (
1316                         CAMEL_SERVICE (transport),
1317                         FALSE, cancellable, NULL);
1318                 return FALSE;
1319         }
1320         g_free (cmdbuf);
1321
1322         do {
1323                 /* Check for "250 Sender OK..." */
1324                 g_free (respbuf);
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);
1333                         return FALSE;
1334                 }
1335                 if (strncmp (respbuf, "250", 3)) {
1336                         smtp_set_error (
1337                                 transport, respbuf, cancellable, error);
1338                         g_prefix_error (
1339                                 error, _("MAIL FROM command failed: "));
1340                         g_free (respbuf);
1341                         return FALSE;
1342                 }
1343         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1344         g_free (respbuf);
1345
1346         return TRUE;
1347 }
1348
1349 static gboolean
1350 smtp_rcpt (CamelSmtpTransport *transport,
1351            const gchar *recipient,
1352            GCancellable *cancellable,
1353            GError **error)
1354 {
1355         /* we gotta tell the smtp server who we are going to be sending
1356          * our email to */
1357         gchar *cmdbuf, *respbuf = NULL;
1358
1359         cmdbuf = g_strdup_printf ("RCPT TO:<%s>\r\n", recipient);
1360
1361         d (fprintf (stderr, "sending : %s", cmdbuf));
1362
1363         if (camel_stream_write_string (
1364                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1365                 g_free (cmdbuf);
1366                 g_prefix_error (error, _("RCPT TO command failed: "));
1367                 camel_service_disconnect_sync (
1368                         CAMEL_SERVICE (transport),
1369                         FALSE, cancellable, NULL);
1370
1371                 return FALSE;
1372         }
1373         g_free (cmdbuf);
1374
1375         do {
1376                 /* Check for "250 Recipient OK..." */
1377                 g_free (respbuf);
1378                 respbuf = camel_stream_buffer_read_line (
1379                         CAMEL_STREAM_BUFFER (transport->istream),
1380                         cancellable, error);
1381                 if (respbuf == NULL) {
1382                         g_prefix_error (
1383                                 error, _("RCPT TO <%s> failed: "), recipient);
1384                         camel_service_disconnect_sync (
1385                                 CAMEL_SERVICE (transport),
1386                                 FALSE, cancellable, NULL);
1387                         return FALSE;
1388                 }
1389                 if (strncmp (respbuf, "250", 3)) {
1390                         smtp_set_error (
1391                                 transport, respbuf, cancellable, error);
1392                         g_prefix_error (
1393                                 error, _("RCPT TO <%s> failed: "), recipient);
1394                         g_free (respbuf);
1395
1396                         return FALSE;
1397                 }
1398         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1399         g_free (respbuf);
1400
1401         return TRUE;
1402 }
1403
1404 static gboolean
1405 smtp_data (CamelSmtpTransport *transport,
1406            CamelMimeMessage *message,
1407            GCancellable *cancellable,
1408            GError **error)
1409 {
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;
1416         gint ret;
1417
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;
1421
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);
1428
1429         cmdbuf = g_strdup ("DATA\r\n");
1430
1431         d (fprintf (stderr, "sending : %s", cmdbuf));
1432
1433         if (camel_stream_write_string (
1434                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1435                 g_free (cmdbuf);
1436                 g_prefix_error (error, _("DATA command failed: "));
1437                 camel_service_disconnect_sync (
1438                         CAMEL_SERVICE (transport),
1439                         FALSE, cancellable, NULL);
1440                 return FALSE;
1441         }
1442         g_free (cmdbuf);
1443
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);
1451                 return FALSE;
1452         }
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
1456                  */
1457                 smtp_set_error (transport, respbuf, cancellable, error);
1458                 g_prefix_error (error, _("DATA command failed: "));
1459                 g_free (respbuf);
1460                 return FALSE;
1461         }
1462
1463         g_free (respbuf);
1464         respbuf = NULL;
1465
1466         /* unlink the bcc headers */
1467         savedbcc = NULL;
1468         tail = (struct _camel_header_raw *) &savedbcc;
1469
1470         header = (struct _camel_header_raw *) &CAMEL_MIME_PART (message)->headers;
1471         n = header->next;
1472         while (n != NULL) {
1473                 if (!g_ascii_strcasecmp (n->name, "Bcc")) {
1474                         header->next = n->next;
1475                         tail->next = n;
1476                         n->next = NULL;
1477                         tail = n;
1478                 } else {
1479                         header = n;
1480                 }
1481
1482                 n = header->next;
1483         }
1484
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);
1490
1491         filtered_stream = camel_stream_filter_new (transport->ostream);
1492
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);
1499
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);
1507
1508         /* write the message */
1509         ret = camel_data_wrapper_write_to_stream_sync (
1510                 CAMEL_DATA_WRAPPER (message),
1511                 filtered_stream, cancellable, error);
1512
1513         /* restore the bcc headers */
1514         header->next = savedbcc;
1515
1516         if (ret == -1) {
1517                 g_prefix_error (error, _("DATA command failed: "));
1518
1519                 g_object_unref (filtered_stream);
1520
1521                 camel_service_disconnect_sync (
1522                         CAMEL_SERVICE (transport),
1523                         FALSE, cancellable, NULL);
1524                 return FALSE;
1525         }
1526
1527         camel_stream_flush (filtered_stream, cancellable, NULL);
1528         g_object_unref (filtered_stream);
1529
1530         /* terminate the message body */
1531
1532         d (fprintf (stderr, "sending : \\r\\n.\\r\\n\n"));
1533
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);
1541                 return FALSE;
1542         }
1543
1544         do {
1545                 /* Check for "250 Sender OK..." */
1546                 g_free (respbuf);
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);
1555                         return FALSE;
1556                 }
1557                 if (strncmp (respbuf, "250", 3) != 0) {
1558                         smtp_set_error (
1559                                 transport, respbuf, cancellable, error);
1560                         g_prefix_error (error, _("DATA command failed: "));
1561                         g_free (respbuf);
1562                         return FALSE;
1563                 }
1564         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1565         g_free (respbuf);
1566
1567         return TRUE;
1568 }
1569
1570 static gboolean
1571 smtp_rset (CamelSmtpTransport *transport,
1572            GCancellable *cancellable,
1573            GError **error)
1574 {
1575         /* we are going to reset the smtp server (just to be nice) */
1576         gchar *cmdbuf, *respbuf = NULL;
1577
1578         cmdbuf = g_strdup ("RSET\r\n");
1579
1580         d (fprintf (stderr, "sending : %s", cmdbuf));
1581
1582         if (camel_stream_write_string (
1583                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1584                 g_free (cmdbuf);
1585                 g_prefix_error (error, _("RSET command failed: "));
1586                 camel_service_disconnect_sync (
1587                         CAMEL_SERVICE (transport),
1588                         FALSE, cancellable, NULL);
1589                 return FALSE;
1590         }
1591         g_free (cmdbuf);
1592
1593         do {
1594                 /* Check for "250" */
1595                 g_free (respbuf);
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);
1604                         return FALSE;
1605                 }
1606                 if (strncmp (respbuf, "250", 3) != 0) {
1607                         smtp_set_error (
1608                                 transport, respbuf, cancellable, error);
1609                         g_prefix_error (error, _("RSET command failed: "));
1610                         g_free (respbuf);
1611                         return FALSE;
1612                 }
1613         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1614         g_free (respbuf);
1615
1616         return TRUE;
1617 }
1618
1619 static gboolean
1620 smtp_quit (CamelSmtpTransport *transport,
1621            GCancellable *cancellable,
1622            GError **error)
1623 {
1624         /* we are going to reset the smtp server (just to be nice) */
1625         gchar *cmdbuf, *respbuf = NULL;
1626
1627         cmdbuf = g_strdup ("QUIT\r\n");
1628
1629         d (fprintf (stderr, "sending : %s", cmdbuf));
1630
1631         if (camel_stream_write_string (
1632                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1633                 g_free (cmdbuf);
1634                 g_prefix_error (error, _("QUIT command failed: "));
1635                 return FALSE;
1636         }
1637         g_free (cmdbuf);
1638
1639         do {
1640                 /* Check for "221" */
1641                 g_free (respbuf);
1642                 respbuf = camel_stream_buffer_read_line (
1643                         CAMEL_STREAM_BUFFER (transport->istream),
1644                         cancellable, error);
1645
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;
1650                         return FALSE;
1651                 }
1652                 if (strncmp (respbuf, "221", 3) != 0) {
1653                         smtp_set_error (
1654                                 transport, respbuf, cancellable, error);
1655                         g_prefix_error (error, _("QUIT command failed: "));
1656                         g_free (respbuf);
1657                         return FALSE;
1658                 }
1659         } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */
1660
1661         g_free (respbuf);
1662
1663         return TRUE;
1664 }