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