Rename camel_service_get_settings().
[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 program is free software; you can redistribute it and/or
10  * modify it under the terms of version 2 of the GNU Lesser General Public
11  * License as published by the Free Software Foundation.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
21  * USA
22  */
23
24 #ifdef HAVE_CONFIG_H
25 #include <config.h>
26 #endif
27
28 #include <ctype.h>
29 #include <errno.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <sys/param.h>
35 #include <sys/types.h>
36
37 #include <glib/gi18n-lib.h>
38
39 #include "camel-smtp-settings.h"
40 #include "camel-smtp-transport.h"
41
42 #ifdef G_OS_WIN32
43 #include <winsock2.h>
44 #include <ws2tcpip.h>
45 #endif
46
47 #undef MIN
48 #undef MAX
49
50 extern gint camel_verbose_debug;
51
52 #define d(x) (camel_verbose_debug ? (x) : 0)
53
54 /* Specified in RFC 821 */
55 #define SMTP_PORT  25
56 #define SMTPS_PORT 465
57
58 /* support prototypes */
59 static GHashTable *     esmtp_get_authtypes     (const guchar *buffer);
60 static gboolean         smtp_helo               (CamelSmtpTransport *transport,
61                                                  GCancellable *cancellable,
62                                                  GError **error);
63 static gboolean         smtp_mail               (CamelSmtpTransport *transport,
64                                                  const gchar *sender,
65                                                  gboolean has_8bit_parts,
66                                                  GCancellable *cancellable,
67                                                  GError **error);
68 static gboolean         smtp_rcpt               (CamelSmtpTransport *transport,
69                                                  const gchar *recipient,
70                                                  GCancellable *cancellable,
71                                                  GError **error);
72 static gboolean         smtp_data               (CamelSmtpTransport *transport,
73                                                  CamelMimeMessage *message,
74                                                  GCancellable *cancellable,
75                                                  GError **error);
76 static gboolean         smtp_rset               (CamelSmtpTransport *transport,
77                                                  GCancellable *cancellable,
78                                                  GError **error);
79 static gboolean         smtp_quit               (CamelSmtpTransport *transport,
80                                                  GCancellable *cancellable,
81                                                  GError **error);
82 static void             smtp_set_error          (CamelSmtpTransport *transport,
83                                                  const gchar *respbuf,
84                                                  GCancellable *cancellable,
85                                                  GError **error);
86
87 /* Forward Declarations */
88 static void camel_network_service_init (CamelNetworkServiceInterface *interface);
89
90 G_DEFINE_TYPE_WITH_CODE (
91         CamelSmtpTransport,
92         camel_smtp_transport,
93         CAMEL_TYPE_TRANSPORT,
94         G_IMPLEMENT_INTERFACE (
95                 CAMEL_TYPE_NETWORK_SERVICE,
96                 camel_network_service_init))
97
98 static gboolean
99 connect_to_server (CamelService *service,
100                    GCancellable *cancellable,
101                    GError **error)
102 {
103         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
104         CamelNetworkSettings *network_settings;
105         CamelNetworkSecurityMethod method;
106         CamelSettings *settings;
107         CamelStream *tcp_stream;
108         gchar *respbuf = NULL;
109         gboolean success = TRUE;
110         gchar *host;
111
112         if (!CAMEL_SERVICE_CLASS (camel_smtp_transport_parent_class)->
113                 connect_sync (service, cancellable, error))
114                 return FALSE;
115
116         /* set some smtp transport defaults */
117         transport->flags = 0;
118         transport->authtypes = NULL;
119
120         settings = camel_service_ref_settings (service);
121
122         network_settings = CAMEL_NETWORK_SETTINGS (settings);
123         host = camel_network_settings_dup_host (network_settings);
124         method = camel_network_settings_get_security_method (network_settings);
125
126         g_object_unref (settings);
127
128         tcp_stream = camel_network_service_connect_sync (
129                 CAMEL_NETWORK_SERVICE (service), cancellable, error);
130
131         if (tcp_stream == NULL) {
132                 success = FALSE;
133                 goto exit;
134         }
135
136         transport->connected = TRUE;
137
138         /* get the localaddr - needed later by smtp_helo */
139         transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream), &transport->localaddrlen);
140
141         transport->ostream = tcp_stream;
142         transport->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
143
144         /* Read the greeting, note whether the server is ESMTP or not. */
145         do {
146                 /* Check for "220" */
147                 g_free (respbuf);
148                 respbuf = camel_stream_buffer_read_line (
149                         CAMEL_STREAM_BUFFER (transport->istream),
150                         cancellable, error);
151                 if (respbuf == NULL) {
152                         g_prefix_error (error, _("Welcome response error: "));
153                         transport->connected = FALSE;
154                         success = FALSE;
155                         goto exit;
156                 }
157                 if (strncmp (respbuf, "220", 3)) {
158                         smtp_set_error (
159                                 transport, respbuf, cancellable, error);
160                         g_prefix_error (error, _("Welcome response error: "));
161                         g_free (respbuf);
162                         success = FALSE;
163                         goto exit;
164                 }
165         } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
166         g_free (respbuf);
167
168         /* Try sending EHLO */
169         transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
170         if (!smtp_helo (transport, cancellable, error)) {
171                 if (!transport->connected) {
172                         success = FALSE;
173                         goto exit;
174                 }
175
176                 /* Fall back to HELO */
177                 g_clear_error (error);
178                 transport->flags &= ~CAMEL_SMTP_TRANSPORT_IS_ESMTP;
179
180                 if (!smtp_helo (transport, cancellable, error)) {
181                         camel_service_disconnect_sync (
182                                 CAMEL_SERVICE (transport),
183                                 TRUE, cancellable, NULL);
184                         success = FALSE;
185                         goto exit;
186                 }
187         }
188
189         /* clear any EHLO/HELO exception and assume that any SMTP errors encountered were non-fatal */
190         g_clear_error (error);
191
192         if (method != CAMEL_NETWORK_SECURITY_METHOD_STARTTLS_ON_STANDARD_PORT)
193                 goto exit;  /* we're done */
194
195         if (!(transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS)) {
196                 g_set_error (
197                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
198                         _("Failed to connect to SMTP server %s in secure mode: %s"),
199                         host, _("STARTTLS not supported"));
200
201                 goto exception_cleanup;
202         }
203
204         d(fprintf (stderr, "sending : STARTTLS\r\n"));
205         if (camel_stream_write (
206                 tcp_stream, "STARTTLS\r\n", 10, cancellable, error) == -1) {
207                 g_prefix_error (error, _("STARTTLS command failed: "));
208                 goto exception_cleanup;
209         }
210
211         respbuf = NULL;
212
213         do {
214                 /* Check for "220 Ready for TLS" */
215                 g_free (respbuf);
216                 respbuf = camel_stream_buffer_read_line (
217                         CAMEL_STREAM_BUFFER (transport->istream),
218                         cancellable, error);
219                 if (respbuf == NULL) {
220                         g_prefix_error (error, _("STARTTLS command failed: "));
221                         transport->connected = FALSE;
222                         goto exception_cleanup;
223                 }
224                 if (strncmp (respbuf, "220", 3) != 0) {
225                         smtp_set_error (
226                                 transport, respbuf, cancellable, error);
227                         g_prefix_error (error, _("STARTTLS command failed: "));
228                         g_free (respbuf);
229                         goto exception_cleanup;
230                 }
231         } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
232
233         /* Okay, now toggle SSL/TLS mode */
234         if (camel_tcp_stream_ssl_enable_ssl (
235                 CAMEL_TCP_STREAM_SSL (tcp_stream), cancellable, error) == -1) {
236                 g_prefix_error (
237                         error,
238                         _("Failed to connect to SMTP server %s in secure mode: "),
239                         host);
240                 goto exception_cleanup;
241         }
242
243         /* We are supposed to re-EHLO after a successful STARTTLS to
244          * re-fetch any supported extensions. */
245         if (!smtp_helo (transport, cancellable, error)) {
246                 camel_service_disconnect_sync (
247                         CAMEL_SERVICE (transport),
248                         TRUE, cancellable, NULL);
249                 success = FALSE;
250         }
251
252         goto exit;
253
254 exception_cleanup:
255
256         g_object_unref (transport->istream);
257         transport->istream = NULL;
258
259         g_object_unref (transport->ostream);
260         transport->ostream = NULL;
261
262         transport->connected = FALSE;
263
264         success = FALSE;
265
266 exit:
267         g_free (host);
268
269         return success;
270 }
271
272 static void
273 authtypes_free (gpointer key,
274                 gpointer value,
275                 gpointer data)
276 {
277         g_free (value);
278 }
279
280 static gchar *
281 smtp_transport_get_name (CamelService *service,
282                          gboolean brief)
283 {
284         CamelNetworkSettings *network_settings;
285         CamelSettings *settings;
286         gchar *host;
287         gchar *name;
288
289         settings = camel_service_ref_settings (service);
290
291         network_settings = CAMEL_NETWORK_SETTINGS (settings);
292         host = camel_network_settings_dup_host (network_settings);
293
294         g_object_unref (settings);
295
296         if (brief)
297                 name = g_strdup_printf (
298                         _("SMTP server %s"), host);
299         else
300                 name = g_strdup_printf (
301                         _("SMTP mail delivery via %s"), host);
302
303         g_free (host);
304
305         return name;
306 }
307
308 static gboolean
309 smtp_transport_connect_sync (CamelService *service,
310                              GCancellable *cancellable,
311                              GError **error)
312 {
313         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
314         CamelNetworkSettings *network_settings;
315         CamelSettings *settings;
316         gchar *host;
317         gchar *mechanism;
318         gboolean auth_required;
319         gboolean success = TRUE;
320
321         settings = camel_service_ref_settings (service);
322
323         network_settings = CAMEL_NETWORK_SETTINGS (settings);
324         host = camel_network_settings_dup_host (network_settings);
325         mechanism = camel_network_settings_dup_auth_mechanism (network_settings);
326
327         g_object_unref (settings);
328
329         /* We (probably) need to check popb4smtp before we connect ... */
330         if (g_strcmp0 (mechanism, "POPB4SMTP") == 0) {
331                 GByteArray *chal;
332                 CamelSasl *sasl;
333
334                 sasl = camel_sasl_new ("smtp", "POPB4SMTP", service);
335                 chal = camel_sasl_challenge_sync (sasl, NULL, cancellable, error);
336                 if (chal != NULL)
337                         g_byte_array_free (chal, TRUE);
338
339                 if (camel_sasl_get_authenticated (sasl))
340                         success = connect_to_server (
341                                 service, cancellable, error);
342                 else
343                         success = FALSE;
344
345                 g_object_unref (sasl);
346
347                 goto exit;
348         }
349
350         success = connect_to_server (service, cancellable, error);
351
352         if (!success)
353                 return FALSE;
354
355         /* check to see if AUTH is required, if so...then AUTH ourselves */
356         auth_required =
357                 (mechanism != NULL) &&
358                 (transport->authtypes != NULL) &&
359                 (g_hash_table_size (transport->authtypes) > 0) &&
360                 (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP);
361
362         if (auth_required) {
363                 CamelSession *session;
364
365                 session = camel_service_get_session (service);
366
367                 if (g_hash_table_lookup (transport->authtypes, mechanism)) {
368                         success = camel_session_authenticate_sync (
369                                 session, service, mechanism,
370                                 cancellable, error);
371                 } else {
372                         g_set_error (
373                                 error, CAMEL_SERVICE_ERROR,
374                                 CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
375                                 _("SMTP server %s does not support %s "
376                                   "authentication"), host, mechanism);
377                         success = FALSE;
378                 }
379
380                 if (!success)
381                         camel_service_disconnect_sync (
382                                 service, TRUE, cancellable, NULL);
383         }
384
385 exit:
386         g_free (host);
387         g_free (mechanism);
388
389         return success;
390 }
391
392 static gboolean
393 smtp_transport_disconnect_sync (CamelService *service,
394                                 gboolean clean,
395                                 GCancellable *cancellable,
396                                 GError **error)
397 {
398         CamelServiceClass *service_class;
399         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
400
401         /*if (!service->connected)
402          *      return TRUE;
403          */
404
405         if (transport->connected && clean) {
406                 /* send the QUIT command to the SMTP server */
407                 smtp_quit (transport, cancellable, NULL);
408         }
409
410         /* Chain up to parent's disconnect() method. */
411         service_class = CAMEL_SERVICE_CLASS (camel_smtp_transport_parent_class);
412         if (!service_class->disconnect_sync (service, clean, cancellable, error))
413                 return FALSE;
414
415         if (transport->authtypes) {
416                 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
417                 g_hash_table_destroy (transport->authtypes);
418                 transport->authtypes = NULL;
419         }
420
421         if (transport->istream) {
422                 g_object_unref (transport->istream);
423                 transport->istream = NULL;
424         }
425
426         if (transport->ostream) {
427                 g_object_unref (transport->ostream);
428                 transport->ostream = NULL;
429         }
430
431         g_free (transport->localaddr);
432         transport->localaddr = NULL;
433
434         transport->connected = FALSE;
435
436         return TRUE;
437 }
438
439 static CamelAuthenticationResult
440 smtp_transport_authenticate_sync (CamelService *service,
441                                   const gchar *mechanism,
442                                   GCancellable *cancellable,
443                                   GError **error)
444 {
445         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
446         CamelAuthenticationResult result;
447         CamelSasl *sasl;
448         gchar *cmdbuf, *respbuf = NULL, *challenge;
449         gboolean auth_challenge = FALSE;
450
451         if (mechanism == NULL) {
452                 g_set_error (
453                         error, CAMEL_SERVICE_ERROR,
454                         CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
455                         _("No SASL mechanism was specified"));
456                 return CAMEL_AUTHENTICATION_ERROR;
457         }
458
459         sasl = camel_sasl_new ("smtp", mechanism, service);
460         if (sasl == NULL) {
461                 g_set_error (
462                         error, CAMEL_SERVICE_ERROR,
463                         CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
464                         _("No support for %s authentication"), mechanism);
465                 return CAMEL_AUTHENTICATION_ERROR;
466         }
467
468         challenge = camel_sasl_challenge_base64_sync (
469                 sasl, NULL, cancellable, error);
470         if (challenge) {
471                 auth_challenge = TRUE;
472                 cmdbuf = g_strdup_printf (
473                         "AUTH %s %s\r\n", mechanism, challenge);
474                 g_free (challenge);
475         } else {
476                 cmdbuf = g_strdup_printf (
477                         "AUTH %s\r\n", mechanism);
478         }
479
480         d(fprintf (stderr, "sending : %s", cmdbuf));
481         if (camel_stream_write_string (
482                 transport->ostream, cmdbuf,
483                 cancellable, error) == -1) {
484                 g_free (cmdbuf);
485                 g_prefix_error (error, _("AUTH command failed: "));
486                 goto lose;
487         }
488         g_free (cmdbuf);
489
490         respbuf = camel_stream_buffer_read_line (
491                 CAMEL_STREAM_BUFFER (transport->istream),
492                 cancellable, error);
493
494         while (!camel_sasl_get_authenticated (sasl)) {
495                 if (!respbuf) {
496                         g_prefix_error (error, _("AUTH command failed: "));
497                         transport->connected = FALSE;
498                         goto lose;
499                 }
500
501                 /* the server may have accepted our initial response */
502                 if (strncmp (respbuf, "235", 3) == 0)
503                         break;
504
505                 /* the server challenge/response should follow a 334 code */
506                 if (strncmp (respbuf, "334", 3) != 0) {
507                         smtp_set_error (
508                                 transport, respbuf, cancellable, error);
509                         g_prefix_error (error, _("AUTH command failed: "));
510                         goto lose;
511                 }
512
513                 if (FALSE) {
514                 broken_smtp_server:
515                         d(fprintf (stderr, "Your SMTP server's implementation of the %s SASL\n"
516                                    "authentication mechanism is broken. Please report this to the\n"
517                                    "appropriate vendor and suggest that they re-read rfc2554 again\n"
518                                    "for the first time (specifically Section 4).\n",
519                                    mechanism));
520                 }
521
522                 /* eat whtspc */
523                 for (challenge = respbuf + 4; isspace (*challenge); challenge++);
524
525                 challenge = camel_sasl_challenge_base64_sync (
526                         sasl, challenge, cancellable, error);
527                 if (challenge == NULL)
528                         goto break_and_lose;
529
530                 g_free (respbuf);
531
532                 /* send our challenge */
533                 cmdbuf = g_strdup_printf ("%s\r\n", challenge);
534                 g_free (challenge);
535                 d(fprintf (stderr, "sending : %s", cmdbuf));
536                 if (camel_stream_write_string (
537                         transport->ostream, cmdbuf,
538                         cancellable, error) == -1) {
539                         g_free (cmdbuf);
540                         goto lose;
541                 }
542                 g_free (cmdbuf);
543
544                 /* get the server's response */
545                 respbuf = camel_stream_buffer_read_line (
546                         CAMEL_STREAM_BUFFER (transport->istream),
547                         cancellable, error);
548         }
549
550         if (respbuf == NULL)
551                 goto lose;
552
553         /* Work around broken SASL implementations. */
554         if (auth_challenge && strncmp (respbuf, "334", 3) == 0)
555                 goto broken_smtp_server;
556
557         /* If our authentication data was rejected, destroy the
558          * password so that the user gets prompted to try again. */
559         if (strncmp (respbuf, "535", 3) == 0)
560                 result = CAMEL_AUTHENTICATION_REJECTED;
561         else
562                 result = CAMEL_AUTHENTICATION_ACCEPTED;
563
564         /* Catch any other errors. */
565         if (strncmp (respbuf, "235", 3) != 0) {
566                 g_set_error (
567                         error, CAMEL_SERVICE_ERROR,
568                         CAMEL_SERVICE_ERROR_CANT_AUTHENTICATE,
569                         _("Bad authentication response from server."));
570                 goto lose;
571         }
572
573         goto exit;
574
575 break_and_lose:
576         /* Get the server out of "waiting for continuation data" mode. */
577         d(fprintf (stderr, "sending : *\n"));
578         camel_stream_write (transport->ostream, "*\r\n", 3, cancellable, NULL);
579         respbuf = camel_stream_buffer_read_line (
580                 CAMEL_STREAM_BUFFER (transport->istream), cancellable, NULL);
581         d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
582
583 lose:
584         result = CAMEL_AUTHENTICATION_ERROR;
585
586 exit:
587         g_object_unref (sasl);
588         g_free (respbuf);
589
590         return result;
591 }
592
593 static GList *
594 smtp_transport_query_auth_types_sync (CamelService *service,
595                                       GCancellable *cancellable,
596                                       GError **error)
597 {
598         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
599         CamelServiceAuthType *authtype;
600         CamelProvider *provider;
601         GList *types, *t, *next;
602
603         if (!connect_to_server (service, cancellable, error))
604                 return NULL;
605
606         if (!transport->authtypes) {
607                 smtp_transport_disconnect_sync (
608                         service, TRUE, cancellable, NULL);
609                 return NULL;
610         }
611
612         provider = camel_service_get_provider (service);
613         types = g_list_copy (provider->authtypes);
614
615         for (t = types; t; t = next) {
616                 authtype = t->data;
617                 next = t->next;
618
619                 if (!g_hash_table_lookup (transport->authtypes, authtype->authproto)) {
620                         types = g_list_remove_link (types, t);
621                         g_list_free_1 (t);
622                 }
623         }
624
625         smtp_transport_disconnect_sync (service, TRUE, cancellable, NULL);
626
627         return types;
628 }
629
630 static gboolean
631 smtp_transport_send_to_sync (CamelTransport *transport,
632                              CamelMimeMessage *message,
633                              CamelAddress *from,
634                              CamelAddress *recipients,
635                              GCancellable *cancellable,
636                              GError **error)
637 {
638         CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (transport);
639         CamelInternetAddress *cia;
640         gboolean has_8bit_parts;
641         const gchar *addr;
642         gint i, len;
643
644         if (!smtp_transport->connected) {
645                 g_set_error (
646                         error, CAMEL_SERVICE_ERROR,
647                         CAMEL_SERVICE_ERROR_NOT_CONNECTED,
648                         _("Cannot send message: service not connected."));
649                 return FALSE;
650         }
651
652         if (!camel_internet_address_get (CAMEL_INTERNET_ADDRESS (from), 0, NULL, &addr)) {
653                 g_set_error (
654                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
655                         _("Cannot send message: sender address not valid."));
656                 return FALSE;
657         }
658
659         camel_operation_push_message (cancellable, _("Sending message"));
660
661         /* find out if the message has 8bit mime parts */
662         has_8bit_parts = camel_mime_message_has_8bit_parts (message);
663
664         /* If the connection needs a ReSET, then do so */
665         if (smtp_transport->need_rset &&
666             !smtp_rset (smtp_transport, cancellable, error)) {
667                 camel_operation_pop_message (cancellable);
668                 return FALSE;
669         }
670         smtp_transport->need_rset = FALSE;
671
672         /* rfc1652 (8BITMIME) requires that you notify the ESMTP daemon that
673          * you'll be sending an 8bit mime message at "MAIL FROM:" time. */
674         if (!smtp_mail (
675                 smtp_transport, addr, has_8bit_parts, cancellable, error)) {
676                 camel_operation_pop_message (cancellable);
677                 return FALSE;
678         }
679
680         len = camel_address_length (recipients);
681         if (len == 0) {
682                 g_set_error (
683                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
684                         _("Cannot send message: no recipients defined."));
685                 camel_operation_pop_message (cancellable);
686                 smtp_transport->need_rset = TRUE;
687                 return FALSE;
688         }
689
690         cia = CAMEL_INTERNET_ADDRESS (recipients);
691         for (i = 0; i < len; i++) {
692                 gchar *enc;
693
694                 if (!camel_internet_address_get (cia, i, NULL, &addr)) {
695                         g_set_error (
696                                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
697                                 _("Cannot send message: "
698                                   "one or more invalid recipients"));
699                         camel_operation_pop_message (cancellable);
700                         smtp_transport->need_rset = TRUE;
701                         return FALSE;
702                 }
703
704                 enc = camel_internet_address_encode_address (NULL, NULL, addr);
705                 if (!smtp_rcpt (smtp_transport, enc, cancellable, error)) {
706                         g_free (enc);
707                         camel_operation_pop_message (cancellable);
708                         smtp_transport->need_rset = TRUE;
709                         return FALSE;
710                 }
711                 g_free (enc);
712         }
713
714         if (!smtp_data (smtp_transport, message, cancellable, error)) {
715                 camel_operation_pop_message (cancellable);
716                 smtp_transport->need_rset = TRUE;
717                 return FALSE;
718         }
719
720         camel_operation_pop_message (cancellable);
721
722         return TRUE;
723 }
724
725 static const gchar *
726 smtp_transport_get_service_name (CamelNetworkService *service,
727                                  CamelNetworkSecurityMethod method)
728 {
729         const gchar *service_name;
730
731         switch (method) {
732                 case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
733                         service_name = "smtps";
734                         break;
735
736                 default:
737                         service_name = "smtp";
738                         break;
739         }
740
741         return service_name;
742 }
743
744 static guint16
745 smtp_transport_get_default_port (CamelNetworkService *service,
746                                  CamelNetworkSecurityMethod method)
747 {
748         guint16 default_port;
749
750         switch (method) {
751                 case CAMEL_NETWORK_SECURITY_METHOD_SSL_ON_ALTERNATE_PORT:
752                         default_port = SMTPS_PORT;
753                         break;
754
755                 default:
756                         default_port = SMTP_PORT;
757                         break;
758         }
759
760         return default_port;
761 }
762
763 static void
764 camel_smtp_transport_class_init (CamelSmtpTransportClass *class)
765 {
766         CamelServiceClass *service_class;
767         CamelTransportClass *transport_class;
768
769         service_class = CAMEL_SERVICE_CLASS (class);
770         service_class->settings_type = CAMEL_TYPE_SMTP_SETTINGS;
771         service_class->get_name = smtp_transport_get_name;
772         service_class->connect_sync = smtp_transport_connect_sync;
773         service_class->disconnect_sync = smtp_transport_disconnect_sync;
774         service_class->authenticate_sync = smtp_transport_authenticate_sync;
775         service_class->query_auth_types_sync = smtp_transport_query_auth_types_sync;
776
777         transport_class = CAMEL_TRANSPORT_CLASS (class);
778         transport_class->send_to_sync = smtp_transport_send_to_sync;
779 }
780
781 static void
782 camel_network_service_init (CamelNetworkServiceInterface *interface)
783 {
784         interface->get_service_name = smtp_transport_get_service_name;
785         interface->get_default_port = smtp_transport_get_default_port;
786 }
787
788 static void
789 camel_smtp_transport_init (CamelSmtpTransport *smtp)
790 {
791         smtp->flags = 0;
792         smtp->connected = FALSE;
793 }
794
795 static const gchar *
796 smtp_error_string (gint error)
797 {
798         /* SMTP error codes grabbed from rfc821 */
799         switch (error) {
800         case 500:
801                 return _("Syntax error, command unrecognized");
802         case 501:
803                 return _("Syntax error in parameters or arguments");
804         case 502:
805                 return _("Command not implemented");
806         case 504:
807                 return _("Command parameter not implemented");
808         case 211:
809                 return _("System status, or system help reply");
810         case 214:
811                 return _("Help message");
812         case 220:
813                 return _("Service ready");
814         case 221:
815                 return _("Service closing transmission channel");
816         case 421:
817                 return _("Service not available, closing transmission channel");
818         case 250:
819                 return _("Requested mail action okay, completed");
820         case 251:
821                 return _("User not local; will forward to <forward-path>");
822         case 450:
823                 return _("Requested mail action not taken: mailbox unavailable");
824         case 550:
825                 return _("Requested action not taken: mailbox unavailable");
826         case 451:
827                 return _("Requested action aborted: error in processing");
828         case 551:
829                 return _("User not local; please try <forward-path>");
830         case 452:
831                 return _("Requested action not taken: insufficient system storage");
832         case 552:
833                 return _("Requested mail action aborted: exceeded storage allocation");
834         case 553:
835                 return _("Requested action not taken: mailbox name not allowed");
836         case 354:
837                 return _("Start mail input; end with <CRLF>.<CRLF>");
838         case 554:
839                 return _("Transaction failed");
840
841         /* AUTH error codes: */
842         case 432:
843                 return _("A password transition is needed");
844         case 534:
845                 return _("Authentication mechanism is too weak");
846         case 538:
847                 return _("Encryption required for requested authentication mechanism");
848         case 454:
849                 return _("Temporary authentication failure");
850         case 530:
851                 return _("Authentication required");
852
853         default:
854                 return _("Unknown");
855         }
856 }
857
858 static GHashTable *
859 esmtp_get_authtypes (const guchar *buffer)
860 {
861         const guchar *start, *end;
862         GHashTable *table = NULL;
863
864         start = buffer;
865
866         /* make sure there is at least one delimiter
867            character in the AUTH response */
868         if (!isspace ((gint) *start) && *start != '=')
869                 return NULL;
870
871         /* advance to the first token */
872         while (isspace ((gint) *start) || *start == '=')
873                 start++;
874
875         if (!*start)
876                 return NULL;
877
878         table = g_hash_table_new (g_str_hash, g_str_equal);
879
880         for (; *start; ) {
881                 gchar *type;
882
883                 /* advance to the end of the token */
884                 end = start;
885                 while (*end && !isspace ((gint) *end))
886                         end++;
887
888                 type = g_strndup ((gchar *) start, end - start);
889                 g_hash_table_insert (table, type, type);
890
891                 /* advance to the next token */
892                 start = end;
893                 while (isspace ((gint) *start))
894                         start++;
895         }
896
897         return table;
898 }
899
900 static const gchar *
901 smtp_next_token (const gchar *buf)
902 {
903         const guchar *token;
904
905         token = (const guchar *) buf;
906         while (*token && !isspace ((gint) *token))
907                 token++;
908
909         while (*token && isspace ((gint) *token))
910                 token++;
911
912         return (const gchar *) token;
913 }
914
915 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : (c) - 'A' + 10)
916
917 /*
918  * example (rfc2034):
919  * 5.1.1 Mailbox "nosuchuser" does not exist
920  *
921  * The human-readable status code is what we want. Since this text
922  * could possibly be encoded, we must decode it.
923  *
924  * "xtext" is formally defined as follows:
925  *
926  *   xtext = *( xchar / hexchar / linear-white-space / comment )
927  *
928  *   xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
929  *        except for "+", "\" and "(".
930  *
931  * "hexchar"s are intended to encode octets that cannot be represented
932  * as plain text, either because they are reserved, or because they are
933  * non-printable.  However, any octet value may be represented by a
934  * "hexchar".
935  *
936  *   hexchar = ASCII "+" immediately followed by two upper case
937  *        hexadecimal digits
938  */
939 static gchar *
940 smtp_decode_status_code (const gchar *in,
941                          gsize len)
942 {
943         guchar *inptr, *outptr;
944         const guchar *inend;
945         gchar *outbuf;
946
947         outbuf = (gchar *) g_malloc (len + 1);
948         outptr = (guchar *) outbuf;
949
950         inptr = (guchar *) in;
951         inend = inptr + len;
952         while (inptr < inend) {
953                 if (*inptr == '+') {
954                         if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
955                                 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
956                                 inptr += 3;
957                         } else
958                                 *outptr++ = *inptr++;
959                 } else
960                         *outptr++ = *inptr++;
961         }
962
963         *outptr = '\0';
964
965         return outbuf;
966 }
967
968 /* converts string str to local encoding, thinking it's in utf8.
969  * If fails, then converts all character greater than 127 to hex values.
970  * Also those under 32, other than \n, \r, \t.
971  * Note that the c is signed character, so all characters above 127 have
972  * negative value.
973 */
974 static void
975 convert_to_local (GString *str)
976 {
977         gchar *buf;
978
979         buf = g_locale_from_utf8 (str->str, str->len, NULL, NULL, NULL);
980
981         if (!buf) {
982                 gint i;
983                 gchar c;
984                 GString *s = g_string_new_len (str->str, str->len);
985
986                 g_string_truncate (str, 0);
987
988                 for (i = 0; i < s->len; i++) {
989                         c = s->str[i];
990
991                         if (c < 32 && c != '\n' && c != '\r' && c != '\t')
992                                 g_string_append_printf (str, "<%X%X>", (c >> 4) & 0xF, c & 0xF);
993                         else
994                                 g_string_append_c (str, c);
995                 }
996
997                 g_string_free (s, TRUE);
998         } else {
999                 g_string_truncate (str, 0);
1000                 g_string_append (str, buf);
1001
1002                 g_free (buf);
1003         }
1004 }
1005
1006 static void
1007 smtp_set_error (CamelSmtpTransport *transport,
1008                 const gchar *respbuf,
1009                 GCancellable *cancellable,
1010                 GError **error)
1011 {
1012         const gchar *token, *rbuf = respbuf;
1013         gchar *buffer = NULL;
1014         GString *string;
1015
1016         g_return_if_fail (respbuf != NULL);
1017
1018         string = g_string_new ("");
1019         do {
1020                 if (transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES)
1021                         token = smtp_next_token (rbuf + 4);
1022                 else
1023                         token = rbuf + 4;
1024
1025                 if (*token == '\0') {
1026                         g_free (buffer);
1027                         g_string_free (string, TRUE);
1028                         goto fake_status_code;
1029                 }
1030
1031                 g_string_append (string, token);
1032                 if (*(rbuf + 3) == '-') {
1033                         g_free (buffer);
1034                         buffer = camel_stream_buffer_read_line (
1035                                 CAMEL_STREAM_BUFFER (transport->istream),
1036                                 cancellable, NULL);
1037                         g_string_append_c (string, '\n');
1038                 } else {
1039                         g_free (buffer);
1040                         buffer = NULL;
1041                 }
1042
1043                 rbuf = buffer;
1044         } while (rbuf);
1045
1046         convert_to_local (string);
1047         if (!(transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES) && string->len) {
1048                 string->str = g_strstrip (string->str);
1049                 string->len = strlen (string->str);
1050
1051                 if (!string->len) {
1052                         g_string_free (string, TRUE);
1053                         goto fake_status_code;
1054                 }
1055
1056                 g_set_error (
1057                         error, CAMEL_ERROR,
1058                         CAMEL_ERROR_GENERIC,
1059                         "%s", string->str);
1060
1061                 g_string_free (string, TRUE);
1062         } else {
1063                 buffer = smtp_decode_status_code (string->str, string->len);
1064                 g_string_free (string, TRUE);
1065                 if (!buffer)
1066                         goto fake_status_code;
1067
1068                 g_set_error (
1069                         error, CAMEL_ERROR,
1070                         CAMEL_ERROR_GENERIC,
1071                         "%s", buffer);
1072
1073                 g_free (buffer);
1074         }
1075
1076         return;
1077
1078 fake_status_code:
1079         g_set_error (
1080                 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
1081                 "%s", smtp_error_string (atoi (respbuf)));
1082 }
1083
1084 static gboolean
1085 smtp_helo (CamelSmtpTransport *transport,
1086            GCancellable *cancellable,
1087            GError **error)
1088 {
1089         gchar *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
1090         const gchar *token, *numeric = NULL;
1091         struct sockaddr *addr;
1092         socklen_t addrlen;
1093
1094         /* these are flags that we set, so unset them in case we
1095          * are being called a second time (ie, after a STARTTLS) */
1096         transport->flags &= ~(CAMEL_SMTP_TRANSPORT_8BITMIME |
1097                               CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES |
1098                               CAMEL_SMTP_TRANSPORT_STARTTLS);
1099
1100         if (transport->authtypes) {
1101                 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
1102                 g_hash_table_destroy (transport->authtypes);
1103                 transport->authtypes = NULL;
1104         }
1105
1106         camel_operation_push_message (cancellable, _("SMTP Greeting"));
1107
1108         addr = transport->localaddr;
1109         addrlen = transport->localaddrlen;
1110
1111         if (camel_getnameinfo (
1112                 addr, addrlen, &name, NULL,
1113                 NI_NUMERICHOST, cancellable, NULL) != 0) {
1114                 name = g_strdup ("localhost.localdomain");
1115         } else {
1116                 if (addr->sa_family == AF_INET6)
1117                         numeric = "IPv6:";
1118                 else
1119                         numeric = "";
1120         }
1121
1122         token = (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) ? "EHLO" : "HELO";
1123         if (numeric)
1124                 cmdbuf = g_strdup_printf("%s [%s%s]\r\n", token, numeric, name);
1125         else
1126                 cmdbuf = g_strdup_printf("%s %s\r\n", token, name);
1127         g_free (name);
1128
1129         d(fprintf (stderr, "sending : %s", cmdbuf));
1130         if (camel_stream_write_string (
1131                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1132                 g_free (cmdbuf);
1133                 g_prefix_error (error, _("HELO command failed: "));
1134                 camel_operation_pop_message (cancellable);
1135
1136                 camel_service_disconnect_sync (
1137                         CAMEL_SERVICE (transport),
1138                         FALSE, cancellable, NULL);
1139
1140                 return FALSE;
1141         }
1142         g_free (cmdbuf);
1143
1144         do {
1145                 /* Check for "250" */
1146                 g_free (respbuf);
1147                 respbuf = camel_stream_buffer_read_line (
1148                         CAMEL_STREAM_BUFFER (transport->istream),
1149                         cancellable, error);
1150                 if (respbuf == NULL) {
1151                         g_prefix_error (error, _("HELO command failed: "));
1152                         transport->connected = FALSE;
1153                         camel_operation_pop_message (cancellable);
1154                         return FALSE;
1155                 }
1156                 if (strncmp (respbuf, "250", 3)) {
1157                         smtp_set_error (
1158                                 transport, respbuf, cancellable, error);
1159                         g_prefix_error (error, _("HELO command failed: "));
1160                         camel_operation_pop_message (cancellable);
1161                         g_free (respbuf);
1162                         return FALSE;
1163                 }
1164
1165                 token = respbuf + 4;
1166
1167                 if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) {
1168                         if (!g_ascii_strncasecmp (token, "8BITMIME", 8)) {
1169                                 transport->flags |= CAMEL_SMTP_TRANSPORT_8BITMIME;
1170                         } else if (!g_ascii_strncasecmp (token, "ENHANCEDSTATUSCODES", 19)) {
1171                                 transport->flags |= CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES;
1172                         } else if (!g_ascii_strncasecmp (token, "STARTTLS", 8)) {
1173                                 transport->flags |= CAMEL_SMTP_TRANSPORT_STARTTLS;
1174                         } else if (!g_ascii_strncasecmp (token, "AUTH", 4)) {
1175                                 if (!transport->authtypes || transport->flags & CAMEL_SMTP_TRANSPORT_AUTH_EQUAL) {
1176                                         /* Don't bother parsing any authtypes if we already have a list.
1177                                          * Some servers will list AUTH twice, once the standard way and
1178                                          * once the way Microsoft Outlook requires them to be:
1179                                          *
1180                                          * 250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-MD5
1181                                          * 250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-MD5
1182                                          *
1183                                          * Since they can come in any order, parse each list that we get
1184                                          * until we parse an authtype list that does not use the AUTH=
1185                                          * format. We want to let the standard way have priority over the
1186                                          * broken way.
1187                                          **/
1188
1189                                         if (token[4] == '=')
1190                                                 transport->flags |= CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
1191                                         else
1192                                                 transport->flags &= ~CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
1193
1194                                         /* parse for supported AUTH types */
1195                                         token += 4;
1196
1197                                         if (transport->authtypes) {
1198                                                 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
1199                                                 g_hash_table_destroy (transport->authtypes);
1200                                         }
1201
1202                                         transport->authtypes = esmtp_get_authtypes ((const guchar *) token);
1203                                 }
1204                         }
1205                 }
1206         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1207         g_free (respbuf);
1208
1209         camel_operation_pop_message (cancellable);
1210
1211         return TRUE;
1212 }
1213
1214 static gboolean
1215 smtp_mail (CamelSmtpTransport *transport,
1216            const gchar *sender,
1217            gboolean has_8bit_parts,
1218            GCancellable *cancellable,
1219            GError **error)
1220 {
1221         /* we gotta tell the smtp server who we are. (our email addy) */
1222         gchar *cmdbuf, *respbuf = NULL;
1223
1224         if (transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME && has_8bit_parts)
1225                 cmdbuf = g_strdup_printf ("MAIL FROM:<%s> BODY=8BITMIME\r\n", sender);
1226         else
1227                 cmdbuf = g_strdup_printf ("MAIL FROM:<%s>\r\n", sender);
1228
1229         d(fprintf (stderr, "sending : %s", cmdbuf));
1230
1231         if (camel_stream_write_string (
1232                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1233                 g_free (cmdbuf);
1234                 g_prefix_error (error, _("MAIL FROM command failed: "));
1235                 camel_service_disconnect_sync (
1236                         CAMEL_SERVICE (transport),
1237                         FALSE, cancellable, NULL);
1238                 return FALSE;
1239         }
1240         g_free (cmdbuf);
1241
1242         do {
1243                 /* Check for "250 Sender OK..." */
1244                 g_free (respbuf);
1245                 respbuf = camel_stream_buffer_read_line (
1246                         CAMEL_STREAM_BUFFER (transport->istream),
1247                         cancellable, error);
1248                 if (respbuf == NULL) {
1249                         g_prefix_error (error, _("MAIL FROM command failed: "));
1250                         camel_service_disconnect_sync (
1251                                 CAMEL_SERVICE (transport),
1252                                 FALSE, cancellable, NULL);
1253                         return FALSE;
1254                 }
1255                 if (strncmp (respbuf, "250", 3)) {
1256                         smtp_set_error (
1257                                 transport, respbuf, cancellable, error);
1258                         g_prefix_error (
1259                                 error, _("MAIL FROM command failed: "));
1260                         g_free (respbuf);
1261                         return FALSE;
1262                 }
1263         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1264         g_free (respbuf);
1265
1266         return TRUE;
1267 }
1268
1269 static gboolean
1270 smtp_rcpt (CamelSmtpTransport *transport,
1271            const gchar *recipient,
1272            GCancellable *cancellable,
1273            GError **error)
1274 {
1275         /* we gotta tell the smtp server who we are going to be sending
1276          * our email to */
1277         gchar *cmdbuf, *respbuf = NULL;
1278
1279         cmdbuf = g_strdup_printf ("RCPT TO:<%s>\r\n", recipient);
1280
1281         d(fprintf (stderr, "sending : %s", cmdbuf));
1282
1283         if (camel_stream_write_string (
1284                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1285                 g_free (cmdbuf);
1286                 g_prefix_error (error, _("RCPT TO command failed: "));
1287                 camel_service_disconnect_sync (
1288                         CAMEL_SERVICE (transport),
1289                         FALSE, cancellable, NULL);
1290
1291                 return FALSE;
1292         }
1293         g_free (cmdbuf);
1294
1295         do {
1296                 /* Check for "250 Recipient OK..." */
1297                 g_free (respbuf);
1298                 respbuf = camel_stream_buffer_read_line (
1299                         CAMEL_STREAM_BUFFER (transport->istream),
1300                         cancellable, error);
1301                 if (respbuf == NULL) {
1302                         g_prefix_error (
1303                                 error, _("RCPT TO <%s> failed: "), recipient);
1304                         camel_service_disconnect_sync (
1305                                 CAMEL_SERVICE (transport),
1306                                 FALSE, cancellable, NULL);
1307                         return FALSE;
1308                 }
1309                 if (strncmp (respbuf, "250", 3)) {
1310                         smtp_set_error (
1311                                 transport, respbuf, cancellable, error);
1312                         g_prefix_error (
1313                                 error, _("RCPT TO <%s> failed: "), recipient);
1314                         g_free (respbuf);
1315
1316                         return FALSE;
1317                 }
1318         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1319         g_free (respbuf);
1320
1321         return TRUE;
1322 }
1323
1324 static gboolean
1325 smtp_data (CamelSmtpTransport *transport,
1326            CamelMimeMessage *message,
1327            GCancellable *cancellable,
1328            GError **error)
1329 {
1330         struct _camel_header_raw *header, *savedbcc, *n, *tail;
1331         CamelBestencEncoding enctype = CAMEL_BESTENC_8BIT;
1332         CamelStream *filtered_stream;
1333         gchar *cmdbuf, *respbuf = NULL;
1334         CamelMimeFilter *filter;
1335         CamelStreamNull *null;
1336         gint ret;
1337
1338         /* If the server doesn't support 8BITMIME, set our required encoding to be 7bit */
1339         if (!(transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME))
1340                 enctype = CAMEL_BESTENC_7BIT;
1341
1342         /* FIXME: should we get the best charset too?? */
1343         /* Changes the encoding of all mime parts to fit within our required
1344          * encoding type and also force any text parts with long lines (longer
1345          * than 998 octets) to wrap by QP or base64 encoding them. */
1346         camel_mime_message_set_best_encoding (message, CAMEL_BESTENC_GET_ENCODING, enctype);
1347
1348         cmdbuf = g_strdup ("DATA\r\n");
1349
1350         d(fprintf (stderr, "sending : %s", cmdbuf));
1351
1352         if (camel_stream_write_string (
1353                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1354                 g_free (cmdbuf);
1355                 g_prefix_error (error, _("DATA command failed: "));
1356                 camel_service_disconnect_sync (
1357                         CAMEL_SERVICE (transport),
1358                         FALSE, cancellable, NULL);
1359                 return FALSE;
1360         }
1361         g_free (cmdbuf);
1362
1363         respbuf = camel_stream_buffer_read_line (
1364                 CAMEL_STREAM_BUFFER (transport->istream), cancellable, error);
1365         if (respbuf == NULL) {
1366                 g_prefix_error (error, _("DATA command failed: "));
1367                 camel_service_disconnect_sync (
1368                         CAMEL_SERVICE (transport),
1369                         FALSE, cancellable, NULL);
1370                 return FALSE;
1371         }
1372         if (strncmp (respbuf, "354", 3) != 0) {
1373                 /* We should have gotten instructions on how to use the DATA
1374                  * command: 354 Enter mail, end with "." on a line by itself
1375                  */
1376                 smtp_set_error (transport, respbuf, cancellable, error);
1377                 g_prefix_error (error, _("DATA command failed: "));
1378                 g_free (respbuf);
1379                 return FALSE;
1380         }
1381
1382         g_free (respbuf);
1383         respbuf = NULL;
1384
1385         /* unlink the bcc headers */
1386         savedbcc = NULL;
1387         tail = (struct _camel_header_raw *) &savedbcc;
1388
1389         header = (struct _camel_header_raw *) &CAMEL_MIME_PART (message)->headers;
1390         n = header->next;
1391         while (n != NULL) {
1392                 if (!g_ascii_strcasecmp (n->name, "Bcc")) {
1393                         header->next = n->next;
1394                         tail->next = n;
1395                         n->next = NULL;
1396                         tail = n;
1397                 } else {
1398                         header = n;
1399                 }
1400
1401                 n = header->next;
1402         }
1403
1404         /* find out how large the message is... */
1405         null = CAMEL_STREAM_NULL (camel_stream_null_new ());
1406         camel_data_wrapper_write_to_stream_sync (
1407                 CAMEL_DATA_WRAPPER (message),
1408                 CAMEL_STREAM (null), NULL, NULL);
1409
1410         filtered_stream = camel_stream_filter_new (transport->ostream);
1411
1412         /* setup progress reporting for message sending... */
1413         filter = camel_mime_filter_progress_new (cancellable, null->written);
1414         camel_stream_filter_add (
1415                 CAMEL_STREAM_FILTER (filtered_stream), filter);
1416         g_object_unref (filter);
1417         g_object_unref (null);
1418
1419         /* setup LF->CRLF conversion */
1420         filter = camel_mime_filter_crlf_new (
1421                 CAMEL_MIME_FILTER_CRLF_ENCODE,
1422                 CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
1423         camel_stream_filter_add (
1424                 CAMEL_STREAM_FILTER (filtered_stream), filter);
1425         g_object_unref (filter);
1426
1427         /* write the message */
1428         ret = camel_data_wrapper_write_to_stream_sync (
1429                 CAMEL_DATA_WRAPPER (message),
1430                 filtered_stream, cancellable, error);
1431
1432         /* restore the bcc headers */
1433         header->next = savedbcc;
1434
1435         if (ret == -1) {
1436                 g_prefix_error (error, _("DATA command failed: "));
1437
1438                 g_object_unref (filtered_stream);
1439
1440                 camel_service_disconnect_sync (
1441                         CAMEL_SERVICE (transport),
1442                         FALSE, cancellable, NULL);
1443                 return FALSE;
1444         }
1445
1446         camel_stream_flush (filtered_stream, cancellable, NULL);
1447         g_object_unref (filtered_stream);
1448
1449         /* terminate the message body */
1450
1451         d(fprintf (stderr, "sending : \\r\\n.\\r\\n\n"));
1452
1453         if (camel_stream_write (
1454                 transport->ostream, "\r\n.\r\n", 5,
1455                 cancellable, error) == -1) {
1456                 g_prefix_error (error, _("DATA command failed: "));
1457                 camel_service_disconnect_sync (
1458                         CAMEL_SERVICE (transport),
1459                         FALSE, cancellable, NULL);
1460                 return FALSE;
1461         }
1462
1463         do {
1464                 /* Check for "250 Sender OK..." */
1465                 g_free (respbuf);
1466                 respbuf = camel_stream_buffer_read_line (
1467                         CAMEL_STREAM_BUFFER (transport->istream),
1468                         cancellable, error);
1469                 if (respbuf == NULL) {
1470                         g_prefix_error (error, _("DATA command failed: "));
1471                         camel_service_disconnect_sync (
1472                                 CAMEL_SERVICE (transport),
1473                                 FALSE, cancellable, NULL);
1474                         return FALSE;
1475                 }
1476                 if (strncmp (respbuf, "250", 3) != 0) {
1477                         smtp_set_error (
1478                                 transport, respbuf, cancellable, error);
1479                         g_prefix_error (error, _("DATA command failed: "));
1480                         g_free (respbuf);
1481                         return FALSE;
1482                 }
1483         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1484         g_free (respbuf);
1485
1486         return TRUE;
1487 }
1488
1489 static gboolean
1490 smtp_rset (CamelSmtpTransport *transport,
1491            GCancellable *cancellable,
1492            GError **error)
1493 {
1494         /* we are going to reset the smtp server (just to be nice) */
1495         gchar *cmdbuf, *respbuf = NULL;
1496
1497         cmdbuf = g_strdup ("RSET\r\n");
1498
1499         d(fprintf (stderr, "sending : %s", cmdbuf));
1500
1501         if (camel_stream_write_string (
1502                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1503                 g_free (cmdbuf);
1504                 g_prefix_error (error, _("RSET command failed: "));
1505                 camel_service_disconnect_sync (
1506                         CAMEL_SERVICE (transport),
1507                         FALSE, cancellable, NULL);
1508                 return FALSE;
1509         }
1510         g_free (cmdbuf);
1511
1512         do {
1513                 /* Check for "250" */
1514                 g_free (respbuf);
1515                 respbuf = camel_stream_buffer_read_line (
1516                         CAMEL_STREAM_BUFFER (transport->istream),
1517                         cancellable, error);
1518                 if (respbuf == NULL) {
1519                         g_prefix_error (error, _("RSET command failed: "));
1520                         camel_service_disconnect_sync (
1521                                 CAMEL_SERVICE (transport),
1522                                 FALSE, cancellable, NULL);
1523                         return FALSE;
1524                 }
1525                 if (strncmp (respbuf, "250", 3) != 0) {
1526                         smtp_set_error (
1527                                 transport, respbuf, cancellable, error);
1528                         g_prefix_error (error, _("RSET command failed: "));
1529                         g_free (respbuf);
1530                         return FALSE;
1531                 }
1532         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1533         g_free (respbuf);
1534
1535         return TRUE;
1536 }
1537
1538 static gboolean
1539 smtp_quit (CamelSmtpTransport *transport,
1540            GCancellable *cancellable,
1541            GError **error)
1542 {
1543         /* we are going to reset the smtp server (just to be nice) */
1544         gchar *cmdbuf, *respbuf = NULL;
1545
1546         cmdbuf = g_strdup ("QUIT\r\n");
1547
1548         d(fprintf (stderr, "sending : %s", cmdbuf));
1549
1550         if (camel_stream_write_string (
1551                 transport->ostream, cmdbuf, cancellable, error) == -1) {
1552                 g_free (cmdbuf);
1553                 g_prefix_error (error, _("QUIT command failed: "));
1554                 return FALSE;
1555         }
1556         g_free (cmdbuf);
1557
1558         do {
1559                 /* Check for "221" */
1560                 g_free (respbuf);
1561                 respbuf = camel_stream_buffer_read_line (
1562                         CAMEL_STREAM_BUFFER (transport->istream),
1563                         cancellable, error);
1564
1565                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1566                 if (respbuf == NULL) {
1567                         g_prefix_error (error, _("QUIT command failed: "));
1568                         transport->connected = FALSE;
1569                         return FALSE;
1570                 }
1571                 if (strncmp (respbuf, "221", 3) != 0) {
1572                         smtp_set_error (
1573                                 transport, respbuf, cancellable, error);
1574                         g_prefix_error (error, _("QUIT command failed: "));
1575                         g_free (respbuf);
1576                         return FALSE;
1577                 }
1578         } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */
1579
1580         g_free (respbuf);
1581
1582         return TRUE;
1583 }