Fix FSF address (Tobias Mueller, #470445)
[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) 2000 Ximian, Inc. (www.ximian.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
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <ctype.h>
30 #include <errno.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <sys/param.h>
36 #include <sys/types.h>
37
38 #include <glib/gi18n-lib.h>
39
40 #undef MIN
41 #undef MAX
42
43 #include "camel-exception.h"
44 #include "camel-mime-filter-crlf.h"
45 #include "camel-mime-message.h"
46 #include "camel-mime-part.h"
47 #include "camel-multipart.h"
48 #include "camel-net-utils.h"
49 #include "camel-operation.h"
50 #include "camel-sasl.h"
51 #include "camel-session.h"
52 #include "camel-smtp-transport.h"
53 #include "camel-stream-buffer.h"
54 #include "camel-stream-filter.h"
55 #include "camel-tcp-stream-raw.h"
56 #include "camel-tcp-stream.h"
57
58 #ifdef HAVE_SSL
59 #include "camel-tcp-stream-ssl.h"
60 #endif
61
62 extern int camel_verbose_debug;
63 #define d(x) (camel_verbose_debug ? (x) : 0)
64
65 /* Specified in RFC 821 */
66 #define SMTP_PORT "25"
67 #define SMTPS_PORT "465"
68
69 /* camel smtp transport class prototypes */
70 static gboolean smtp_send_to (CamelTransport *transport, CamelMimeMessage *message,
71                               CamelAddress *from, CamelAddress *recipients, CamelException *ex);
72
73 /* support prototypes */
74 static void smtp_construct (CamelService *service, CamelSession *session,
75                             CamelProvider *provider, CamelURL *url,
76                             CamelException *ex);
77 static gboolean smtp_connect (CamelService *service, CamelException *ex);
78 static gboolean smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex);
79 static GHashTable *esmtp_get_authtypes (const unsigned char *buffer);
80 static GList *query_auth_types (CamelService *service, CamelException *ex);
81 static char *get_name (CamelService *service, gboolean brief);
82
83 static gboolean smtp_helo (CamelSmtpTransport *transport, CamelException *ex);
84 static gboolean smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex);
85 static gboolean smtp_mail (CamelSmtpTransport *transport, const char *sender,
86                            gboolean has_8bit_parts, CamelException *ex);
87 static gboolean smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex);
88 static gboolean smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex);
89 static gboolean smtp_rset (CamelSmtpTransport *transport, CamelException *ex);
90 static gboolean smtp_quit (CamelSmtpTransport *transport, CamelException *ex);
91
92 static void smtp_set_exception (CamelSmtpTransport *transport, gboolean disconnect, const char *respbuf,
93                                 const char *message, CamelException *ex);
94
95 /* private data members */
96 static CamelTransportClass *parent_class = NULL;
97
98 static void
99 camel_smtp_transport_class_init (CamelSmtpTransportClass *camel_smtp_transport_class)
100 {
101         CamelTransportClass *camel_transport_class =
102                 CAMEL_TRANSPORT_CLASS (camel_smtp_transport_class);
103         CamelServiceClass *camel_service_class =
104                 CAMEL_SERVICE_CLASS (camel_smtp_transport_class);
105         
106         parent_class = CAMEL_TRANSPORT_CLASS (camel_type_get_global_classfuncs (camel_transport_get_type ()));
107         
108         /* virtual method overload */
109         camel_service_class->construct = smtp_construct;
110         camel_service_class->connect = smtp_connect;
111         camel_service_class->disconnect = smtp_disconnect;
112         camel_service_class->query_auth_types = query_auth_types;
113         camel_service_class->get_name = get_name;
114         
115         camel_transport_class->send_to = smtp_send_to;
116 }
117
118 static void
119 camel_smtp_transport_init (gpointer object)
120 {
121         CamelSmtpTransport *smtp = CAMEL_SMTP_TRANSPORT (object);
122         
123         smtp->flags = 0;
124         smtp->connected = FALSE;
125 }
126
127 CamelType
128 camel_smtp_transport_get_type (void)
129 {
130         static CamelType type = CAMEL_INVALID_TYPE;
131         
132         if (type == CAMEL_INVALID_TYPE) {
133                 type = camel_type_register (CAMEL_TRANSPORT_TYPE,
134                                             "CamelSmtpTransport",
135                                             sizeof (CamelSmtpTransport),
136                                             sizeof (CamelSmtpTransportClass),
137                                             (CamelObjectClassInitFunc) camel_smtp_transport_class_init,
138                                             NULL,
139                                             (CamelObjectInitFunc) camel_smtp_transport_init,
140                                             NULL);
141         }
142         
143         return type;
144 }
145
146 static void
147 smtp_construct (CamelService *service, CamelSession *session,
148                 CamelProvider *provider, CamelURL *url,
149                 CamelException *ex)
150 {
151         CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
152 }
153
154 static const char *
155 smtp_error_string (int error)
156 {
157         /* SMTP error codes grabbed from rfc821 */
158         switch (error) {
159         case 0:
160                 /* looks like a read problem, check errno */
161                 if (errno)
162                         return g_strerror (errno);
163                 else
164                         return _("Unknown");
165         case 500:
166                 return _("Syntax error, command unrecognized");
167         case 501:
168                 return _("Syntax error in parameters or arguments");
169         case 502:
170                 return _("Command not implemented");
171         case 504:
172                 return _("Command parameter not implemented");
173         case 211:
174                 return _("System status, or system help reply");
175         case 214:
176                 return _("Help message");
177         case 220:
178                 return _("Service ready");
179         case 221:
180                 return _("Service closing transmission channel");
181         case 421:
182                 return _("Service not available, closing transmission channel");
183         case 250:
184                 return _("Requested mail action okay, completed");
185         case 251:
186                 return _("User not local; will forward to <forward-path>");
187         case 450:
188                 return _("Requested mail action not taken: mailbox unavailable");
189         case 550:
190                 return _("Requested action not taken: mailbox unavailable");
191         case 451:
192                 return _("Requested action aborted: error in processing");
193         case 551:
194                 return _("User not local; please try <forward-path>");
195         case 452:
196                 return _("Requested action not taken: insufficient system storage");
197         case 552:
198                 return _("Requested mail action aborted: exceeded storage allocation");
199         case 553:
200                 return _("Requested action not taken: mailbox name not allowed");
201         case 354:
202                 return _("Start mail input; end with <CRLF>.<CRLF>");
203         case 554:
204                 return _("Transaction failed");
205                 
206         /* AUTH error codes: */
207         case 432:
208                 return _("A password transition is needed");
209         case 534:
210                 return _("Authentication mechanism is too weak");
211         case 538:
212                 return _("Encryption required for requested authentication mechanism");
213         case 454:
214                 return _("Temporary authentication failure");
215         case 530:
216                 return _("Authentication required");
217                 
218         default:
219                 return _("Unknown");
220         }
221 }
222
223 enum {
224         MODE_CLEAR,
225         MODE_SSL,
226         MODE_TLS,
227 };
228
229 #ifdef HAVE_SSL
230 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
231 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
232 #endif
233
234 static gboolean
235 connect_to_server (CamelService *service, struct addrinfo *ai, int ssl_mode, CamelException *ex)
236 {
237         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
238         CamelStream *tcp_stream;
239         char *respbuf = NULL;
240         int ret;
241         
242         if (!CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex))
243                 return FALSE;
244         
245         /* set some smtp transport defaults */
246         transport->flags = 0;
247         transport->authtypes = NULL;
248         
249         if (ssl_mode != MODE_CLEAR) {
250 #ifdef HAVE_SSL
251                 if (ssl_mode == MODE_TLS) {
252                         tcp_stream = camel_tcp_stream_ssl_new_raw (service->session, service->url->host, STARTTLS_FLAGS);
253                 } else {
254                         tcp_stream = camel_tcp_stream_ssl_new (service->session, service->url->host, SSL_PORT_FLAGS);
255                 }
256 #else
257                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
258                                       _("Could not connect to %s: %s"),
259                                       service->url->host, _("SSL unavailable"));
260                 
261                 return FALSE;
262 #endif /* HAVE_SSL */
263         } else {
264                 tcp_stream = camel_tcp_stream_raw_new ();
265         }
266         
267         if ((ret = camel_tcp_stream_connect ((CamelTcpStream *) tcp_stream, ai)) == -1) {
268                 if (errno == EINTR)
269                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
270                                              _("Connection canceled"));
271                 else
272                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
273                                               _("Could not connect to %s: %s"),
274                                               service->url->host, g_strerror (errno));
275                 
276                 camel_object_unref (tcp_stream);
277                 
278                 return FALSE;
279         }
280         
281         transport->connected = TRUE;
282         
283         /* get the localaddr - needed later by smtp_helo */
284         transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream), &transport->localaddrlen);
285         
286         transport->ostream = tcp_stream;
287         transport->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
288         
289         /* Read the greeting, note whether the server is ESMTP or not. */
290         do {
291                 /* Check for "220" */
292                 g_free (respbuf);
293                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
294                 if (!respbuf || strncmp (respbuf, "220", 3)) {
295                         smtp_set_exception (transport, FALSE, respbuf, _("Welcome response error"), ex);
296                         g_free (respbuf);
297                         return FALSE;
298                 }
299         } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
300         g_free (respbuf);
301         
302         /* Try sending EHLO */
303         transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
304         if (!smtp_helo (transport, ex)) {
305                 if (!transport->connected)
306                         return FALSE;
307                 
308                 /* Fall back to HELO */
309                 camel_exception_clear (ex);
310                 transport->flags &= ~CAMEL_SMTP_TRANSPORT_IS_ESMTP;
311                 if (!smtp_helo (transport, ex) && !transport->connected)
312                         return FALSE;
313         }
314         
315         /* clear any EHLO/HELO exception and assume that any SMTP errors encountered were non-fatal */
316         camel_exception_clear (ex);
317         
318         if (ssl_mode != MODE_TLS) {
319                 /* we're done */
320                 return TRUE;
321         }
322         
323 #ifdef HAVE_SSL
324         if (!(transport->flags & CAMEL_SMTP_TRANSPORT_STARTTLS)) {
325                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
326                                       _("Failed to connect to SMTP server %s in secure mode: %s"),
327                                       service->url->host, _("STARTTLS not supported"));
328                 
329                 goto exception_cleanup;
330         }
331         
332         d(fprintf (stderr, "sending : STARTTLS\r\n"));
333         if (camel_stream_write (tcp_stream, "STARTTLS\r\n", 10) == -1) {
334                 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
335                                       _("STARTTLS command failed: %s"),
336                                       g_strerror (errno));
337                 goto exception_cleanup;
338         }
339         
340         respbuf = NULL;
341         
342         do {
343                 /* Check for "220 Ready for TLS" */
344                 g_free (respbuf);
345                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
346                 
347                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
348                 
349                 if (!respbuf || strncmp (respbuf, "220", 3)) {
350                         smtp_set_exception (transport, FALSE, respbuf, _("STARTTLS command failed"), ex);
351                         g_free (respbuf);
352                         goto exception_cleanup;
353                 }
354         } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
355         
356         /* Okay, now toggle SSL/TLS mode */
357         if (camel_tcp_stream_ssl_enable_ssl (CAMEL_TCP_STREAM_SSL (tcp_stream)) == -1) {
358                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
359                                       _("Failed to connect to SMTP server %s in secure mode: %s"),
360                                       service->url->host, g_strerror (errno));
361                 goto exception_cleanup;
362         }
363 #else
364         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
365                               _("Failed to connect to SMTP server %s in secure mode: %s"),
366                               service->url->host, _("SSL is not available in this build"));
367         goto exception_cleanup;
368 #endif /* HAVE_SSL */
369         
370         /* We are supposed to re-EHLO after a successful STARTTLS to
371            re-fetch any supported extensions. */
372         if (!smtp_helo (transport, ex) && !transport->connected)
373                 return FALSE;
374         
375         return TRUE;
376         
377  exception_cleanup:
378         
379         camel_object_unref (transport->istream);
380         transport->istream = NULL;
381         camel_object_unref (transport->ostream);
382         transport->ostream = NULL;
383         
384         transport->connected = FALSE;
385         
386         return FALSE;
387 }
388
389 static struct {
390         char *value;
391         char *serv;
392         char *port;
393         int mode;
394 } ssl_options[] = {
395         { "",              "smtps", SMTPS_PORT, MODE_SSL   },  /* really old (1.x) */
396         { "always",        "smtps", SMTPS_PORT, MODE_SSL   },
397         { "when-possible", "smtp",  SMTP_PORT, MODE_TLS   },
398         { "never",         "smtp",  SMTP_PORT, MODE_CLEAR },
399         { NULL,            "smtp",  SMTP_PORT, MODE_CLEAR },
400 };
401
402 static gboolean
403 connect_to_server_wrapper (CamelService *service, CamelException *ex)
404 {
405         struct addrinfo hints, *ai;
406         const char *ssl_mode;
407         int mode, ret, i;
408         char *serv;
409         const char *port;
410         
411         if ((ssl_mode = camel_url_get_param (service->url, "use_ssl"))) {
412                 for (i = 0; ssl_options[i].value; i++)
413                         if (!strcmp (ssl_options[i].value, ssl_mode))
414                                 break;
415                 mode = ssl_options[i].mode;
416                 serv = ssl_options[i].serv;
417                 port = ssl_options[i].port;
418         } else {
419                 mode = MODE_CLEAR;
420                 serv = "smtp";
421                 port = SMTP_PORT;
422         }
423         
424         if (service->url->port) {
425                 serv = g_alloca (16);
426                 sprintf (serv, "%d", service->url->port);
427                 port = NULL;
428         }
429         
430         memset (&hints, 0, sizeof (hints));
431         hints.ai_socktype = SOCK_STREAM;
432         hints.ai_family = PF_UNSPEC;
433         ai = camel_getaddrinfo(service->url->host, serv, &hints, ex);
434         if (ai == NULL && port != NULL && camel_exception_get_id(ex) != CAMEL_EXCEPTION_USER_CANCEL) {
435                 camel_exception_clear (ex);
436                 ai = camel_getaddrinfo(service->url->host, port, &hints, ex);
437         }
438         
439         if (ai == NULL)
440                 return FALSE;
441         
442         ret = connect_to_server (service, ai, mode, ex);
443         
444         camel_freeaddrinfo (ai);
445         
446         return ret;
447 }
448
449 static gboolean
450 smtp_connect (CamelService *service, CamelException *ex)
451 {
452         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
453         gboolean has_authtypes;
454         
455         /* We (probably) need to check popb4smtp before we connect ... */
456         if (service->url->authmech && !strcmp (service->url->authmech, "POPB4SMTP")) {
457                 int truth;
458                 GByteArray *chal;
459                 CamelSasl *sasl;
460                 
461                 sasl = camel_sasl_new ("smtp", "POPB4SMTP", service);
462                 chal = camel_sasl_challenge (sasl, NULL, ex);
463                 truth = camel_sasl_authenticated (sasl);
464                 if (chal)
465                         g_byte_array_free (chal, TRUE);
466                 camel_object_unref (sasl);
467                 
468                 if (!truth)
469                         return FALSE;
470                 
471                 return connect_to_server_wrapper (service, ex);
472         }
473         
474         if (!connect_to_server_wrapper (service, ex))
475                 return FALSE;
476         
477         /* check to see if AUTH is required, if so...then AUTH ourselves */
478         has_authtypes = transport->authtypes ? g_hash_table_size (transport->authtypes) > 0 : FALSE;
479         if (service->url->authmech && (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) && has_authtypes) {
480                 CamelSession *session = camel_service_get_session (service);
481                 CamelServiceAuthType *authtype;
482                 gboolean authenticated = FALSE;
483                 char *errbuf = NULL;
484                 
485                 if (!g_hash_table_lookup (transport->authtypes, service->url->authmech)) {
486                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
487                                               _("SMTP server %s does not support requested "
488                                                 "authentication type %s."),
489                                               service->url->host, service->url->authmech);
490                         camel_service_disconnect (service, TRUE, NULL);
491                         return FALSE;
492                 }
493                 
494                 authtype = camel_sasl_authtype (service->url->authmech);
495                 if (!authtype) {
496                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
497                                               _("No support for authentication type %s"),
498                                               service->url->authmech);
499                         camel_service_disconnect (service, TRUE, NULL);
500                         return FALSE;
501                 }
502                 
503                 if (!authtype->need_password) {
504                         /* authentication mechanism doesn't need a password,
505                            so if it fails there's nothing we can do */
506                         authenticated = smtp_auth (transport, authtype->authproto, ex);
507                         if (!authenticated) {
508                                 camel_service_disconnect (service, TRUE, NULL);
509                                 return FALSE;
510                         }
511                 }
512                 
513                 /* keep trying to login until either we succeed or the user cancels */
514                 while (!authenticated) {
515                         if (errbuf) {
516                                 /* We need to un-cache the password before prompting again */
517                                 camel_session_forget_password (session, service, NULL, "password", NULL);
518                                 g_free (service->url->passwd);
519                                 service->url->passwd = NULL;
520                         }
521                         
522                         if (!service->url->passwd) {
523                                 char *prompt;
524                                 
525                                 prompt = g_strdup_printf (_("%sPlease enter the SMTP password for %s on host %s"),
526                                                           errbuf ? errbuf : "", service->url->user,
527                                                           service->url->host);
528                                 
529                                 service->url->passwd = camel_session_get_password (session, service, NULL,
530                                                                                    prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, ex);
531                                 
532                                 g_free (prompt);
533                                 g_free (errbuf);
534                                 errbuf = NULL;
535                                 
536                                 if (!service->url->passwd) {
537                                         camel_service_disconnect (service, TRUE, NULL);
538                                         return FALSE;
539                                 }
540                         }
541                         
542                         authenticated = smtp_auth (transport, authtype->authproto, ex);
543                         if (!authenticated) {
544                                 errbuf = g_strdup_printf (_("Unable to authenticate "
545                                                             "to SMTP server.\n%s\n\n"),
546                                                           camel_exception_get_description (ex));
547                                 camel_exception_clear (ex);
548                         }
549                 }
550         }
551         
552         return TRUE;
553 }
554
555 static void
556 authtypes_free (gpointer key, gpointer value, gpointer data)
557 {
558         g_free (value);
559 }
560
561 static gboolean
562 smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex)
563 {
564         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
565         
566         /*if (!service->connected)
567          *      return TRUE;
568          */
569         
570         if (transport->connected && clean) {
571                 /* send the QUIT command to the SMTP server */
572                 smtp_quit (transport, ex);
573         }
574         
575         if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
576                 return FALSE;
577         
578         if (transport->authtypes) {
579                 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
580                 g_hash_table_destroy (transport->authtypes);
581                 transport->authtypes = NULL;
582         }
583         
584         if (transport->istream) {
585                 camel_object_unref (transport->istream);
586                 transport->istream = NULL;
587         }
588         
589         if (transport->ostream) {
590                 camel_object_unref (transport->ostream);
591                 transport->ostream = NULL;
592         }
593         
594         g_free(transport->localaddr);
595         transport->localaddr = NULL;
596         
597         transport->connected = FALSE;
598         
599         return TRUE;
600 }
601
602 static GHashTable *
603 esmtp_get_authtypes (const unsigned char *buffer)
604 {
605         const unsigned char *start, *end;
606         GHashTable *table = NULL;
607         
608         /* advance to the first token */
609         start = buffer;
610         while (isspace ((int) *start) || *start == '=')
611                 start++;
612         
613         if (!*start)
614                 return NULL;
615         
616         table = g_hash_table_new (g_str_hash, g_str_equal);
617         
618         for ( ; *start; ) {
619                 char *type;
620                 
621                 /* advance to the end of the token */
622                 end = start;
623                 while (*end && !isspace ((int) *end))
624                         end++;
625                 
626                 type = g_strndup (start, end - start);
627                 g_hash_table_insert (table, type, type);
628                 
629                 /* advance to the next token */
630                 start = end;
631                 while (isspace ((int) *start))
632                         start++;
633         }
634         
635         return table;
636 }
637
638 static GList *
639 query_auth_types (CamelService *service, CamelException *ex)
640 {
641         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
642         CamelServiceAuthType *authtype;
643         GList *types, *t, *next;
644         
645         if (!connect_to_server_wrapper (service, ex))
646                 return NULL;
647         
648         types = g_list_copy (service->provider->authtypes);
649         for (t = types; t; t = next) {
650                 authtype = t->data;
651                 next = t->next;
652                 
653                 if (!g_hash_table_lookup (transport->authtypes, authtype->authproto)) {
654                         types = g_list_remove_link (types, t);
655                         g_list_free_1 (t);
656                 }
657         }
658         
659         smtp_disconnect (service, TRUE, NULL);
660         
661         return types;
662 }
663
664 static char *
665 get_name (CamelService *service, gboolean brief)
666 {
667         if (brief)
668                 return g_strdup_printf (_("SMTP server %s"), service->url->host);
669         else {
670                 return g_strdup_printf (_("SMTP mail delivery via %s"),
671                                         service->url->host);
672         }
673 }
674
675 static gboolean
676 smtp_send_to (CamelTransport *transport, CamelMimeMessage *message,
677               CamelAddress *from, CamelAddress *recipients,
678               CamelException *ex)
679 {
680         CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (transport);
681         const CamelInternetAddress *cia;
682         gboolean has_8bit_parts;
683         const char *addr;
684         int i, len;
685         
686         if (!smtp_transport->connected) {
687                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_NOT_CONNECTED,
688                                       _("Cannot send message: service not connected."));
689                 return FALSE;
690         }
691         
692         if (!camel_internet_address_get (CAMEL_INTERNET_ADDRESS (from), 0, NULL, &addr)) {
693                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
694                                       _("Cannot send message: sender address not valid."));
695                 return FALSE;
696         }
697         
698         camel_operation_start (NULL, _("Sending message"));
699         
700         /* find out if the message has 8bit mime parts */
701         has_8bit_parts = camel_mime_message_has_8bit_parts (message);
702         
703         /* rfc1652 (8BITMIME) requires that you notify the ESMTP daemon that
704            you'll be sending an 8bit mime message at "MAIL FROM:" time. */
705         if (!smtp_mail (smtp_transport, addr, has_8bit_parts, ex)) {
706                 camel_operation_end (NULL);
707                 return FALSE;
708         }
709         
710         len = camel_address_length (recipients);
711         if (len == 0) {
712                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
713                                       _("Cannot send message: no recipients defined."));
714                 camel_operation_end (NULL);
715                 return FALSE;
716         }
717         
718         cia = CAMEL_INTERNET_ADDRESS (recipients);
719         for (i = 0; i < len; i++) {
720                 char *enc;
721                 
722                 if (!camel_internet_address_get (cia, i, NULL, &addr)) {
723                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
724                                              _("Cannot send message: one or more invalid recipients"));
725                         camel_operation_end (NULL);
726                         return FALSE;
727                 }
728                 
729                 enc = camel_internet_address_encode_address(NULL, NULL, addr);
730                 if (!smtp_rcpt (smtp_transport, enc, ex)) {
731                         g_free(enc);
732                         camel_operation_end (NULL);
733                         return FALSE;
734                 }
735                 g_free(enc);
736         }
737         
738         if (!smtp_data (smtp_transport, message, ex)) {
739                 camel_operation_end (NULL);
740                 return FALSE;
741         }
742         
743         /* reset the service for our next transfer session */
744         if (!smtp_rset (smtp_transport, ex))
745                 camel_exception_clear (ex);
746         
747         camel_operation_end (NULL);
748         
749         return TRUE;
750 }
751
752 static const char *
753 smtp_next_token (const char *buf)
754 {
755         const unsigned char *token;
756         
757         token = (const unsigned char *) buf;
758         while (*token && !isspace ((int) *token))
759                 token++;
760         
761         while (*token && isspace ((int) *token))
762                 token++;
763         
764         return (const char *) token;
765 }
766
767 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : (c) - 'A' + 10)
768
769 /*
770  * example (rfc2034):
771  * 5.1.1 Mailbox "nosuchuser" does not exist
772  *
773  * The human-readable status code is what we want. Since this text
774  * could possibly be encoded, we must decode it.
775  *
776  * "xtext" is formally defined as follows:
777  *
778  *   xtext = *( xchar / hexchar / linear-white-space / comment )
779  *
780  *   xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
781  *        except for "+", "\" and "(".
782  *
783  * "hexchar"s are intended to encode octets that cannot be represented
784  * as plain text, either because they are reserved, or because they are
785  * non-printable.  However, any octet value may be represented by a
786  * "hexchar".
787  *
788  *   hexchar = ASCII "+" immediately followed by two upper case
789  *        hexadecimal digits
790  */
791 static char *
792 smtp_decode_status_code (const char *in, size_t len)
793 {
794         unsigned char *inptr, *outptr;
795         const unsigned char *inend;
796         char *outbuf;
797         
798         outptr = outbuf = g_malloc (len + 1);
799         
800         inptr = (unsigned char *) in;
801         inend = inptr + len;
802         while (inptr < inend) {
803                 if (*inptr == '+') {
804                         if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
805                                 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
806                                 inptr += 3;
807                         } else
808                                 *outptr++ = *inptr++;
809                 } else
810                         *outptr++ = *inptr++;
811         }
812         
813         *outptr = '\0';
814         
815         return outbuf;
816 }
817
818 static void
819 smtp_set_exception (CamelSmtpTransport *transport, gboolean disconnect, const char *respbuf, const char *message, CamelException *ex)
820 {
821         const char *token, *rbuf = respbuf;
822         char *buffer = NULL;
823         GString *string;
824         int error;
825         
826         if (!respbuf || !(transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES)) {
827         fake_status_code:
828                 error = respbuf ? atoi (respbuf) : 0;
829                 camel_exception_setv (ex, error == 0 && errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
830                                       "%s: %s", message, smtp_error_string (error));
831         } else {
832                 string = g_string_new ("");
833                 do {
834                         token = smtp_next_token (rbuf + 4);
835                         if (*token == '\0') {
836                                 g_free (buffer);
837                                 g_string_free (string, TRUE);
838                                 goto fake_status_code;
839                         }
840                         
841                         g_string_append (string, token);
842                         if (*(rbuf + 3) == '-') {
843                                 g_free (buffer);
844                                 buffer = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
845                                 g_string_append_c (string, '\n');
846                         } else {
847                                 g_free (buffer);
848                                 buffer = NULL;
849                         }
850                         
851                         rbuf = buffer;
852                 } while (rbuf);
853                 
854                 buffer = smtp_decode_status_code (string->str, string->len);
855                 g_string_free (string, TRUE);
856                 if (!buffer)
857                         goto fake_status_code;
858                 
859                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
860                                       "%s: %s", message, buffer);
861                 
862                 g_free (buffer);
863         }
864         
865         if (!respbuf) {
866                 /* we got disconnected */
867                 if (disconnect)
868                         camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
869                 else
870                         transport->connected = FALSE;
871         }
872 }
873
874 static gboolean
875 smtp_helo (CamelSmtpTransport *transport, CamelException *ex)
876 {
877         char *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
878         const char *token, *numeric = NULL;
879         struct sockaddr *addr;
880         socklen_t addrlen;
881         
882         /* these are flags that we set, so unset them in case we
883            are being called a second time (ie, after a STARTTLS) */
884         transport->flags &= ~(CAMEL_SMTP_TRANSPORT_8BITMIME |
885                               CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES |
886                               CAMEL_SMTP_TRANSPORT_STARTTLS);
887         
888         if (transport->authtypes) {
889                 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
890                 g_hash_table_destroy (transport->authtypes);
891                 transport->authtypes = NULL;
892         }
893         
894         camel_operation_start_transient (NULL, _("SMTP Greeting"));
895         
896         addr = transport->localaddr;
897         addrlen = transport->localaddrlen;
898         
899         if (camel_getnameinfo (addr, addrlen, &name, NULL, NI_NUMERICHOST, NULL) != 0) {
900                 name = g_strdup ("localhost.localdomain");
901         } else {
902                 if (addr->sa_family == AF_INET6)
903                         numeric = "IPv6:";
904                 else
905                         numeric = "";
906         }
907         
908         token = (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) ? "EHLO" : "HELO";
909         if (numeric)
910                 cmdbuf = g_strdup_printf("%s [%s%s]\r\n", token, numeric, name);
911         else
912                 cmdbuf = g_strdup_printf("%s %s\r\n", token, name);
913         g_free (name);
914         
915         d(fprintf (stderr, "sending : %s", cmdbuf));
916         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
917                 g_free (cmdbuf);
918                 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
919                                       _("HELO command failed: %s"), g_strerror (errno));
920                 camel_operation_end (NULL);
921                 
922                 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
923                 
924                 return FALSE;
925         }
926         g_free (cmdbuf);
927         
928         do {
929                 /* Check for "250" */
930                 g_free (respbuf);
931                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
932                 
933                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
934                 
935                 if (!respbuf || strncmp (respbuf, "250", 3)) {
936                         smtp_set_exception (transport, FALSE, respbuf, _("HELO command failed"), ex);
937                         camel_operation_end (NULL);
938                         g_free (respbuf);
939                         
940                         return FALSE;
941                 }
942                 
943                 token = respbuf + 4;
944                 
945                 if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) {
946                         if (!strncmp (token, "8BITMIME", 8)) {
947                                 d(fprintf (stderr, "This server supports 8bit MIME\n"));
948                                 transport->flags |= CAMEL_SMTP_TRANSPORT_8BITMIME;
949                         } else if (!strncmp (token, "ENHANCEDSTATUSCODES", 19)) {
950                                 d(fprintf (stderr, "This server supports enhanced status codes\n"));
951                                 transport->flags |= CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES;
952                         } else if (!strncmp (token, "STARTTLS", 8)) {
953                                 d(fprintf (stderr, "This server supports STARTTLS\n"));
954                                 transport->flags |= CAMEL_SMTP_TRANSPORT_STARTTLS;
955                         } else if (!strncmp (token, "AUTH", 4)) {
956                                 if (!transport->authtypes || transport->flags & CAMEL_SMTP_TRANSPORT_AUTH_EQUAL) {
957                                         /* Don't bother parsing any authtypes if we already have a list.
958                                          * Some servers will list AUTH twice, once the standard way and
959                                          * once the way Microsoft Outlook requires them to be:
960                                          *
961                                          * 250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-MD5
962                                          * 250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-MD5
963                                          *
964                                          * Since they can come in any order, parse each list that we get
965                                          * until we parse an authtype list that does not use the AUTH=
966                                          * format. We want to let the standard way have priority over the
967                                          * broken way.
968                                          **/
969                                         
970                                         if (token[4] == '=')
971                                                 transport->flags |= CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
972                                         else
973                                                 transport->flags &= ~CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
974                                         
975                                         /* parse for supported AUTH types */
976                                         token += 5;
977                                         
978                                         if (transport->authtypes) {
979                                                 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
980                                                 g_hash_table_destroy (transport->authtypes);
981                                         }
982                                         
983                                         transport->authtypes = esmtp_get_authtypes (token);
984                                 }
985                         }
986                 }
987         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
988         g_free (respbuf);
989         
990         camel_operation_end (NULL);
991         
992         return TRUE;
993 }
994
995 static gboolean
996 smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex)
997 {
998         char *cmdbuf, *respbuf = NULL, *challenge;
999         gboolean auth_challenge = FALSE;
1000         CamelSasl *sasl = NULL;
1001         
1002         camel_operation_start_transient (NULL, _("SMTP Authentication"));
1003         
1004         sasl = camel_sasl_new ("smtp", mech, CAMEL_SERVICE (transport));
1005         if (!sasl) {
1006                 camel_operation_end (NULL);
1007                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1008                                       _("Error creating SASL authentication object."));
1009                 return FALSE;
1010         }
1011         
1012         challenge = camel_sasl_challenge_base64 (sasl, NULL, ex);
1013         if (challenge) {
1014                 auth_challenge = TRUE;
1015                 cmdbuf = g_strdup_printf ("AUTH %s %s\r\n", mech, challenge);
1016                 g_free (challenge);
1017         } else {
1018                 cmdbuf = g_strdup_printf ("AUTH %s\r\n", mech);
1019         }
1020         
1021         d(fprintf (stderr, "sending : %s", cmdbuf));
1022         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1023                 g_free (cmdbuf);
1024                 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1025                                       _("AUTH command failed: %s"), g_strerror (errno));
1026                 goto lose;
1027         }
1028         g_free (cmdbuf);
1029         
1030         respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1031         d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1032         
1033         while (!camel_sasl_authenticated (sasl)) {
1034                 if (!respbuf) {
1035                         camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1036                                               _("AUTH command failed: %s"), g_strerror (errno));
1037                         goto lose;
1038                 }
1039                 
1040                 /* the server challenge/response should follow a 334 code */
1041                 if (strncmp (respbuf, "334", 3) != 0) {
1042                         smtp_set_exception (transport, FALSE, respbuf, _("AUTH command failed"), ex);
1043                         g_free (respbuf);
1044                         goto lose;
1045                 }
1046                 
1047                 if (FALSE) {
1048                 broken_smtp_server:
1049                         d(fprintf (stderr, "Your SMTP server's implementation of the %s SASL\n"
1050                                    "authentication mechanism is broken. Please report this to the\n"
1051                                    "appropriate vendor and suggest that they re-read rfc2554 again\n"
1052                                    "for the first time (specifically Section 4).\n",
1053                                    mech));
1054                 }
1055                 
1056                 /* eat whtspc */
1057                 for (challenge = respbuf + 4; isspace (*challenge); challenge++);
1058                 
1059                 challenge = camel_sasl_challenge_base64 (sasl, challenge, ex);
1060                 g_free (respbuf);
1061                 if (challenge == NULL)
1062                         goto break_and_lose;
1063                 
1064                 /* send our challenge */
1065                 cmdbuf = g_strdup_printf ("%s\r\n", challenge);
1066                 g_free (challenge);
1067                 d(fprintf (stderr, "sending : %s", cmdbuf));
1068                 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1069                         g_free (cmdbuf);
1070                         goto lose;
1071                 }
1072                 g_free (cmdbuf);
1073                 
1074                 /* get the server's response */
1075                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1076                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1077         }
1078         
1079         /* check that the server says we are authenticated */
1080         if (!respbuf || strncmp (respbuf, "235", 3)) {
1081                 if (respbuf && auth_challenge && !strncmp (respbuf, "334", 3)) {
1082                         /* broken server, but lets try and work around it anyway... */
1083                         goto broken_smtp_server;
1084                 }
1085                 g_free (respbuf);
1086                 goto lose;
1087         }
1088         
1089         g_free (respbuf);
1090         camel_object_unref (sasl);
1091         camel_operation_end (NULL);
1092         
1093         return TRUE;
1094         
1095  break_and_lose:
1096         /* Get the server out of "waiting for continuation data" mode. */
1097         d(fprintf (stderr, "sending : *\n"));
1098         camel_stream_write (transport->ostream, "*\r\n", 3);
1099         respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1100         d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1101         g_free (respbuf);
1102         
1103  lose:
1104         if (!camel_exception_is_set (ex)) {
1105                 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1106                                      _("Bad authentication response from server.\n"));
1107         }
1108         
1109         camel_object_unref (sasl);
1110         camel_operation_end (NULL);
1111         
1112         return FALSE;
1113 }
1114
1115 static gboolean
1116 smtp_mail (CamelSmtpTransport *transport, const char *sender, gboolean has_8bit_parts, CamelException *ex)
1117 {
1118         /* we gotta tell the smtp server who we are. (our email addy) */
1119         char *cmdbuf, *respbuf = NULL;
1120         
1121         if (transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME && has_8bit_parts)
1122                 cmdbuf = g_strdup_printf ("MAIL FROM:<%s> BODY=8BITMIME\r\n", sender);
1123         else
1124                 cmdbuf = g_strdup_printf ("MAIL FROM:<%s>\r\n", sender);
1125         
1126         d(fprintf (stderr, "sending : %s", cmdbuf));
1127         
1128         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1129                 g_free (cmdbuf);
1130                 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1131                                       _("MAIL FROM command failed: %s: mail not sent"),
1132                                       g_strerror (errno));
1133                 
1134                 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1135                 
1136                 return FALSE;
1137         }
1138         g_free (cmdbuf);
1139         
1140         do {
1141                 /* Check for "250 Sender OK..." */
1142                 g_free (respbuf);
1143                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1144                 
1145                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1146                 
1147                 if (!respbuf || strncmp (respbuf, "250", 3)) {
1148                         smtp_set_exception (transport, TRUE, respbuf, _("MAIL FROM command failed"), ex);
1149                         g_free (respbuf);
1150                         return FALSE;
1151                 }
1152         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1153         g_free (respbuf);
1154         
1155         return TRUE;
1156 }
1157
1158 static gboolean
1159 smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex)
1160 {
1161         /* we gotta tell the smtp server who we are going to be sending
1162          * our email to */
1163         char *cmdbuf, *respbuf = NULL;
1164         
1165         cmdbuf = g_strdup_printf ("RCPT TO:<%s>\r\n", recipient);
1166         
1167         d(fprintf (stderr, "sending : %s", cmdbuf));
1168         
1169         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1170                 g_free (cmdbuf);
1171                 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1172                                       _("RCPT TO command failed: %s: mail not sent"),
1173                                       g_strerror (errno));
1174                 
1175                 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1176                 
1177                 return FALSE;
1178         }
1179         g_free (cmdbuf);
1180         
1181         do {
1182                 /* Check for "250 Recipient OK..." */
1183                 g_free (respbuf);
1184                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1185                 
1186                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1187                 
1188                 if (!respbuf || strncmp (respbuf, "250", 3)) {
1189                         char *message;
1190                         
1191                         message = g_strdup_printf (_("RCPT TO <%s> failed"), recipient);
1192                         smtp_set_exception (transport, TRUE, respbuf, message, ex);
1193                         g_free (message);
1194                         g_free (respbuf);
1195                         return FALSE;
1196                 }
1197         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1198         g_free (respbuf);
1199         
1200         return TRUE;
1201 }
1202
1203 static gboolean
1204 smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex)
1205 {
1206         CamelBestencEncoding enctype = CAMEL_BESTENC_8BIT;
1207         struct _camel_header_raw *header, *savedbcc, *n, *tail;
1208         char *cmdbuf, *respbuf = NULL;
1209         CamelStreamFilter *filtered_stream;
1210         CamelMimeFilter *crlffilter;
1211         int ret;
1212         
1213         /* If the server doesn't support 8BITMIME, set our required encoding to be 7bit */
1214         if (!(transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME))
1215                 enctype = CAMEL_BESTENC_7BIT;
1216         
1217         /* FIXME: should we get the best charset too?? */
1218         /* Changes the encoding of all mime parts to fit within our required
1219            encoding type and also force any text parts with long lines (longer
1220            than 998 octets) to wrap by QP or base64 encoding them. */
1221         camel_mime_message_set_best_encoding (message, CAMEL_BESTENC_GET_ENCODING, enctype);
1222         
1223         cmdbuf = g_strdup ("DATA\r\n");
1224         
1225         d(fprintf (stderr, "sending : %s", cmdbuf));
1226         
1227         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1228                 g_free (cmdbuf);
1229                 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1230                                       _("DATA command failed: %s: mail not sent"),
1231                                       g_strerror (errno));
1232                 
1233                 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1234                 
1235                 return FALSE;
1236         }
1237         g_free (cmdbuf);
1238         
1239         respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1240         
1241         d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1242         
1243         if (!respbuf || strncmp (respbuf, "354", 3)) {
1244                 /* we should have gotten instructions on how to use the DATA command:
1245                  * 354 Enter mail, end with "." on a line by itself
1246                  */
1247                 smtp_set_exception (transport, TRUE, respbuf, _("DATA command failed"), ex);
1248                 g_free (respbuf);
1249                 return FALSE;
1250         }
1251         
1252         g_free (respbuf);
1253         respbuf = NULL;
1254         
1255         /* setup stream filtering */
1256         crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
1257         filtered_stream = camel_stream_filter_new_with_stream (transport->ostream);
1258         camel_stream_filter_add (filtered_stream, CAMEL_MIME_FILTER (crlffilter));
1259         camel_object_unref (crlffilter);
1260         
1261         /* unlink the bcc headers */
1262         savedbcc = NULL;
1263         tail = (struct _camel_header_raw *) &savedbcc;
1264         
1265         header = (struct _camel_header_raw *) &CAMEL_MIME_PART (message)->headers;
1266         n = header->next;
1267         while (n != NULL) {
1268                 if (!g_ascii_strcasecmp (n->name, "Bcc")) {
1269                         header->next = n->next;
1270                         tail->next = n;
1271                         n->next = NULL;
1272                         tail = n;
1273                 } else {
1274                         header = n;
1275                 }
1276                 
1277                 n = header->next;
1278         }
1279         
1280         /* write the message */
1281         ret = camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), CAMEL_STREAM (filtered_stream));
1282         
1283         /* restore the bcc headers */
1284         header->next = savedbcc;
1285         
1286         if (ret == -1) {
1287                 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1288                                       _("DATA command failed: %s: mail not sent"),
1289                                       g_strerror (errno));
1290                 
1291                 camel_object_unref (filtered_stream);
1292                 
1293                 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1294                 
1295                 return FALSE;
1296         }
1297         
1298         camel_stream_flush (CAMEL_STREAM (filtered_stream));
1299         camel_object_unref (filtered_stream);
1300         
1301         /* terminate the message body */
1302         
1303         d(fprintf (stderr, "sending : \\r\\n.\\r\\n\n"));
1304         
1305         if (camel_stream_write (transport->ostream, "\r\n.\r\n", 5) == -1) {
1306                 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1307                                       _("DATA command failed: %s: mail not sent"),
1308                                       g_strerror (errno));
1309                 
1310                 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1311                 
1312                 return FALSE;
1313         }
1314         
1315         do {
1316                 /* Check for "250 Sender OK..." */
1317                 g_free (respbuf);
1318                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1319                 
1320                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1321                 
1322                 if (!respbuf || strncmp (respbuf, "250", 3)) {
1323                         smtp_set_exception (transport, TRUE, respbuf, _("DATA command failed"), ex);
1324                         g_free (respbuf);
1325                         return FALSE;
1326                 }
1327         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1328         g_free (respbuf);
1329         
1330         return TRUE;
1331 }
1332
1333 static gboolean
1334 smtp_rset (CamelSmtpTransport *transport, CamelException *ex)
1335 {
1336         /* we are going to reset the smtp server (just to be nice) */
1337         char *cmdbuf, *respbuf = NULL;
1338         
1339         cmdbuf = g_strdup ("RSET\r\n");
1340         
1341         d(fprintf (stderr, "sending : %s", cmdbuf));
1342         
1343         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1344                 g_free (cmdbuf);
1345                 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1346                                       _("RSET command failed: %s"), g_strerror (errno));
1347                 
1348                 camel_service_disconnect ((CamelService *) transport, FALSE, NULL);
1349                 
1350                 return FALSE;
1351         }
1352         g_free (cmdbuf);
1353         
1354         do {
1355                 /* Check for "250" */
1356                 g_free (respbuf);
1357                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1358                 
1359                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1360                 
1361                 if (!respbuf || strncmp (respbuf, "250", 3)) {
1362                         smtp_set_exception (transport, TRUE, respbuf, _("RSET command failed"), ex);
1363                         g_free (respbuf);
1364                         return FALSE;
1365                 }
1366         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1367         g_free (respbuf);
1368         
1369         return TRUE;
1370 }
1371
1372 static gboolean
1373 smtp_quit (CamelSmtpTransport *transport, CamelException *ex)
1374 {
1375         /* we are going to reset the smtp server (just to be nice) */
1376         char *cmdbuf, *respbuf = NULL;
1377         
1378         cmdbuf = g_strdup ("QUIT\r\n");
1379         
1380         d(fprintf (stderr, "sending : %s", cmdbuf));
1381         
1382         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1383                 g_free (cmdbuf);
1384                 camel_exception_setv (ex, errno == EINTR ? CAMEL_EXCEPTION_USER_CANCEL : CAMEL_EXCEPTION_SYSTEM,
1385                                       _("QUIT command failed: %s"), g_strerror (errno));
1386                 
1387                 return FALSE;
1388         }
1389         g_free (cmdbuf);
1390         
1391         do {
1392                 /* Check for "221" */
1393                 g_free (respbuf);
1394                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1395                 
1396                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1397                 
1398                 if (!respbuf || strncmp (respbuf, "221", 3)) {
1399                         smtp_set_exception (transport, FALSE, respbuf, _("QUIT command failed"), ex);
1400                         g_free (respbuf);
1401                         return FALSE;
1402                 }
1403         } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */
1404         g_free (respbuf);
1405         
1406         return TRUE;
1407 }