c5947dc0ad37c0ac38757df6066fe7d134ba9362
[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 #include "string-utils.h"
59
60
61 extern int camel_verbose_debug;
62 #define d(x) (camel_verbose_debug ? (x) : 0)
63
64 /* Specified in RFC 821 */
65 #define SMTP_PORT 25
66
67 /* camel smtp transport class prototypes */
68 static gboolean smtp_send_to (CamelTransport *transport, CamelMimeMessage *message,
69                               CamelAddress *from, CamelAddress *recipients, CamelException *ex);
70
71 /* support prototypes */
72 static void smtp_construct (CamelService *service, CamelSession *session,
73                             CamelProvider *provider, CamelURL *url,
74                             CamelException *ex);
75 static gboolean smtp_connect (CamelService *service, CamelException *ex);
76 static gboolean smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex);
77 static GHashTable *esmtp_get_authtypes (const unsigned char *buffer);
78 static GList *query_auth_types (CamelService *service, CamelException *ex);
79 static char *get_name (CamelService *service, gboolean brief);
80
81 static gboolean smtp_helo (CamelSmtpTransport *transport, CamelException *ex);
82 static gboolean smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex);
83 static gboolean smtp_mail (CamelSmtpTransport *transport, const char *sender,
84                            gboolean has_8bit_parts, CamelException *ex);
85 static gboolean smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex);
86 static gboolean smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex);
87 static gboolean smtp_rset (CamelSmtpTransport *transport, CamelException *ex);
88 static gboolean smtp_quit (CamelSmtpTransport *transport, CamelException *ex);
89
90 static void smtp_set_exception (CamelSmtpTransport *transport, const char *respbuf,
91                                 const char *message, CamelException *ex);
92
93 /* private data members */
94 static CamelTransportClass *parent_class = NULL;
95
96 static void
97 camel_smtp_transport_class_init (CamelSmtpTransportClass *camel_smtp_transport_class)
98 {
99         CamelTransportClass *camel_transport_class =
100                 CAMEL_TRANSPORT_CLASS (camel_smtp_transport_class);
101         CamelServiceClass *camel_service_class =
102                 CAMEL_SERVICE_CLASS (camel_smtp_transport_class);
103         
104         parent_class = CAMEL_TRANSPORT_CLASS (camel_type_get_global_classfuncs (camel_transport_get_type ()));
105         
106         /* virtual method overload */
107         camel_service_class->construct = smtp_construct;
108         camel_service_class->connect = smtp_connect;
109         camel_service_class->disconnect = smtp_disconnect;
110         camel_service_class->query_auth_types = query_auth_types;
111         camel_service_class->get_name = get_name;
112         
113         camel_transport_class->send_to = smtp_send_to;
114 }
115
116 static void
117 camel_smtp_transport_init (gpointer object)
118 {
119         CamelSmtpTransport *smtp = CAMEL_SMTP_TRANSPORT (object);
120         
121         smtp->flags = 0;
122         smtp->connected = FALSE;
123 }
124
125 CamelType
126 camel_smtp_transport_get_type (void)
127 {
128         static CamelType type = CAMEL_INVALID_TYPE;
129         
130         if (type == CAMEL_INVALID_TYPE) {
131                 type = camel_type_register (CAMEL_TRANSPORT_TYPE,
132                                             "CamelSmtpTransport",
133                                             sizeof (CamelSmtpTransport),
134                                             sizeof (CamelSmtpTransportClass),
135                                             (CamelObjectClassInitFunc) camel_smtp_transport_class_init,
136                                             NULL,
137                                             (CamelObjectInitFunc) camel_smtp_transport_init,
138                                             NULL);
139         }
140         
141         return type;
142 }
143
144 static void
145 smtp_construct (CamelService *service, CamelSession *session,
146                 CamelProvider *provider, CamelURL *url,
147                 CamelException *ex)
148 {
149         CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (service);
150         const char *use_ssl;
151         
152         CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
153         
154         if ((use_ssl = camel_url_get_param (url, "use_ssl"))) {
155                 /* Note: previous versions would use "" to toggle use_ssl to 'on' */
156                 if (!*use_ssl || !strcmp (use_ssl, "always"))
157                         smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL_ALWAYS;
158                 else if (!strcmp (use_ssl, "when-possible"))
159                         smtp_transport->flags |= CAMEL_SMTP_TRANSPORT_USE_SSL_WHEN_POSSIBLE;
160         }
161 }
162
163 static const char *
164 smtp_error_string (int error)
165 {
166         /* SMTP error codes grabbed from rfc821 */
167         switch (error) {
168         case 0:
169                 /* looks like a read problem, check errno */
170                 if (errno)
171                         return g_strerror (errno);
172                 else
173                         return _("Unknown");
174         case 500:
175                 return _("Syntax error, command unrecognized");
176         case 501:
177                 return _("Syntax error in parameters or arguments");
178         case 502:
179                 return _("Command not implemented");
180         case 504:
181                 return _("Command parameter not implemented");
182         case 211:
183                 return _("System status, or system help reply");
184         case 214:
185                 return _("Help message");
186         case 220:
187                 return _("Service ready");
188         case 221:
189                 return _("Service closing transmission channel");
190         case 421:
191                 return _("Service not available, closing transmission channel");
192         case 250:
193                 return _("Requested mail action okay, completed");
194         case 251:
195                 return _("User not local; will forward to <forward-path>");
196         case 450:
197                 return _("Requested mail action not taken: mailbox unavailable");
198         case 550:
199                 return _("Requested action not taken: mailbox unavailable");
200         case 451:
201                 return _("Requested action aborted: error in processing");
202         case 551:
203                 return _("User not local; please try <forward-path>");
204         case 452:
205                 return _("Requested action not taken: insufficient system storage");
206         case 552:
207                 return _("Requested mail action aborted: exceeded storage allocation");
208         case 553:
209                 return _("Requested action not taken: mailbox name not allowed");
210         case 354:
211                 return _("Start mail input; end with <CRLF>.<CRLF>");
212         case 554:
213                 return _("Transaction failed");
214                 
215         /* AUTH error codes: */
216         case 432:
217                 return _("A password transition is needed");
218         case 534:
219                 return _("Authentication mechanism is too weak");
220         case 538:
221                 return _("Encryption required for requested authentication mechanism");
222         case 454:
223                 return _("Temporary authentication failure");
224         case 530:
225                 return _("Authentication required");
226                 
227         default:
228                 return _("Unknown");
229         }
230 }
231
232 #define SSL_PORT_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_SSL2 | CAMEL_TCP_STREAM_SSL_ENABLE_SSL3)
233 #define STARTTLS_FLAGS (CAMEL_TCP_STREAM_SSL_ENABLE_TLS)
234
235 static gboolean
236 connect_to_server (CamelService *service, int try_starttls, CamelException *ex)
237 {
238         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
239         CamelStream *tcp_stream;
240         char *respbuf = NULL;
241         struct hostent *h;
242         int port, ret;
243         
244         if (!CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex))
245                 return FALSE;
246         
247         h = camel_service_gethost (service, ex);
248         if (!h)
249                 return FALSE;
250         
251         /* set some smtp transport defaults */
252         transport->flags &= CAMEL_SMTP_TRANSPORT_USE_SSL; /* reset all but ssl flags */
253         transport->authtypes = NULL;
254         
255         port = service->url->port ? service->url->port : SMTP_PORT;
256         
257 #ifdef HAVE_SSL
258         if (transport->flags & CAMEL_SMTP_TRANSPORT_USE_SSL) {
259                 if (try_starttls) {
260                         tcp_stream = camel_tcp_stream_ssl_new_raw (service, service->url->host, STARTTLS_FLAGS);
261                 } else {
262                         port = service->url->port ? service->url->port : 465;
263                         tcp_stream = camel_tcp_stream_ssl_new (service, service->url->host, SSL_PORT_FLAGS);
264                 }
265         } else {
266                 tcp_stream = camel_tcp_stream_raw_new ();
267         }
268 #else
269         tcp_stream = camel_tcp_stream_raw_new ();
270 #endif /* HAVE_SSL */
271         
272         ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
273         camel_free_host (h);
274         if (ret == -1) {
275                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
276                                       _("Could not connect to %s (port %d): %s"),
277                                       service->url->host, port,
278                                       g_strerror (errno));
279                 
280                 camel_object_unref (CAMEL_OBJECT (tcp_stream));
281                 
282                 return FALSE;
283         }
284         
285         transport->connected = TRUE;
286         
287         /* get the localaddr - needed later by smtp_helo */
288         transport->localaddr = camel_tcp_stream_get_local_address (CAMEL_TCP_STREAM (tcp_stream));
289         
290         transport->ostream = tcp_stream;
291         transport->istream = camel_stream_buffer_new (tcp_stream, CAMEL_STREAM_BUFFER_READ);
292         
293         /* Read the greeting, note whether the server is ESMTP or not. */
294         do {
295                 /* Check for "220" */
296                 g_free (respbuf);
297                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
298                 if (!respbuf || strncmp (respbuf, "220", 3)) {
299                         smtp_set_exception (transport, respbuf,  _("Welcome response error"), ex);
300                         g_free (respbuf);
301                         return FALSE;
302                 }
303                 if (strstr (respbuf, "ESMTP"))
304                         transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
305         } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
306         g_free (respbuf);
307         
308         /* send EHLO (or HELO, depending on the service type) */
309         if (!(transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP)) {
310                 /* If we did not auto-detect ESMTP, we should still send EHLO */
311                 transport->flags |= CAMEL_SMTP_TRANSPORT_IS_ESMTP;
312                 if (!smtp_helo (transport, ex)) {
313                         if (!transport->connected)
314                                 return FALSE;
315                         
316                         /* Okay, apprently this server doesn't support ESMTP */
317                         camel_exception_clear (ex);
318                         transport->flags &= ~CAMEL_SMTP_TRANSPORT_IS_ESMTP;
319                         if (!smtp_helo (transport, ex) && !transport->connected)
320                                 return FALSE;
321                 }
322         } else {
323                 /* send EHLO */
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 (CAMEL_OBJECT (transport->istream));
397         transport->istream = NULL;
398         camel_object_unref (CAMEL_OBJECT (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 (CAMEL_OBJECT (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 (CAMEL_OBJECT (transport->istream));
584                 transport->istream = NULL;
585         }
586         
587         if (transport->ostream) {
588                 camel_object_unref (CAMEL_OBJECT (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                 if (!camel_internet_address_get (cia, i, NULL, &addr)) {
714                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
715                                              _("Cannot send message: one or more invalid recipients"));
716                         camel_operation_end (NULL);
717                         return FALSE;
718                 }
719                 
720                 if (!smtp_rcpt (smtp_transport, addr, ex)) {
721                         camel_operation_end (NULL);
722                         return FALSE;
723                 }
724         }
725         
726         if (!smtp_data (smtp_transport, message, ex)) {
727                 camel_operation_end (NULL);
728                 return FALSE;
729         }
730         
731         /* reset the service for our next transfer session */
732         smtp_rset (smtp_transport, ex);
733         
734         camel_operation_end (NULL);
735         
736         return TRUE;
737 }
738
739 static const char *
740 smtp_next_token (const char *buf)
741 {
742         const unsigned char *token;
743         
744         token = (const unsigned char *) buf;
745         while (*token && !isspace ((int) *token))
746                 token++;
747         
748         while (*token && isspace ((int) *token))
749                 token++;
750         
751         return (const char *) token;
752 }
753
754 #define HEXVAL(c) (isdigit (c) ? (c) - '0' : (c) - 'A' + 10)
755
756 /**
757  * example (rfc2034):
758  * 5.1.1 Mailbox "nosuchuser" does not exist
759  *
760  * The human-readable status code is what we want. Since this text
761  * could possibly be encoded, we must decode it.
762  *
763  * "xtext" is formally defined as follows:
764  *
765  *   xtext = *( xchar / hexchar / linear-white-space / comment )
766  *
767  *   xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
768  *        except for "+", "\" and "(".
769  *
770  * "hexchar"s are intended to encode octets that cannot be represented
771  * as plain text, either because they are reserved, or because they are
772  * non-printable.  However, any octet value may be represented by a
773  * "hexchar".
774  *
775  *   hexchar = ASCII "+" immediately followed by two upper case
776  *        hexadecimal digits
777  **/
778 static char *
779 smtp_decode_status_code (const char *in, size_t len)
780 {
781         unsigned char *inptr, *outptr;
782         const unsigned char *inend;
783         char *outbuf;
784         
785         outptr = outbuf = g_malloc (len + 1);
786         
787         inptr = (unsigned char *) in;
788         inend = inptr + len;
789         while (inptr < inend) {
790                 if (*inptr == '+') {
791                         if (isxdigit (inptr[1]) && isxdigit (inptr[2])) {
792                                 *outptr++ = HEXVAL (inptr[1]) * 16 + HEXVAL (inptr[2]);
793                                 inptr += 3;
794                         } else
795                                 *outptr++ = *inptr++;
796                 } else
797                         *outptr++ = *inptr++;
798         }
799         
800         *outptr = '\0';
801         
802         return outbuf;
803 }
804
805 static void
806 smtp_set_exception (CamelSmtpTransport *transport, const char *respbuf, const char *message, CamelException *ex)
807 {
808         const char *token, *rbuf = respbuf;
809         char *buffer = NULL;
810         GString *string;
811         int error;
812         
813         if (!respbuf || !(transport->flags & CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES)) {
814         fake_status_code:
815                 error = respbuf ? atoi (respbuf) : 0;
816                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, "%s: %s", message,
817                                       smtp_error_string (error));
818         } else {
819                 string = g_string_new ("");
820                 do {
821                         token = smtp_next_token (rbuf + 4);
822                         if (*token == '\0') {
823                                 g_free (buffer);
824                                 g_string_free (string, TRUE);
825                                 goto fake_status_code;
826                         }
827                         
828                         g_string_append (string, token);
829                         if (*(rbuf + 3) == '-') {
830                                 g_free (buffer);
831                                 buffer = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
832                                 g_string_append_c (string, '\n');
833                         } else {
834                                 g_free (buffer);
835                                 buffer = NULL;
836                         }
837                         
838                         rbuf = buffer;
839                 } while (rbuf);
840                 
841                 buffer = smtp_decode_status_code (string->str, string->len);
842                 g_string_free (string, TRUE);
843                 if (!buffer)
844                         goto fake_status_code;
845                 
846                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
847                                       "%s: %s", message, buffer);
848                 
849                 g_free (buffer);
850         }
851         
852         if (!respbuf) {
853                 /* we got disconnected */
854                 transport->connected = FALSE;
855         }
856 }
857
858 static gboolean
859 smtp_helo (CamelSmtpTransport *transport, CamelException *ex)
860 {
861         /* say hello to the server */
862         char *name = NULL, *cmdbuf = NULL, *respbuf = NULL;
863         struct hostent *host;
864         CamelException err;
865         const char *token;
866         int af;
867         
868         /* these are flags that we set, so unset them in case we
869            are being called a second time (ie, after a STARTTLS) */
870         transport->flags &= ~(CAMEL_SMTP_TRANSPORT_8BITMIME |
871                               CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES |
872                               CAMEL_SMTP_TRANSPORT_STARTTLS);
873         
874         if (transport->authtypes) {
875                 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
876                 g_hash_table_destroy (transport->authtypes);
877                 transport->authtypes = NULL;
878         }
879         
880         camel_operation_start_transient (NULL, _("SMTP Greeting"));
881         
882         /* get the local host name */
883         camel_exception_init (&err);
884 #ifdef ENABLE_IPv6
885         af = transport->localaddr->family == CAMEL_TCP_ADDRESS_IPv6 ? AF_INET6 : AF_INET;
886 #else
887         af = AF_INET;
888 #endif
889         host = camel_gethostbyaddr ((char *) &transport->localaddr->address,
890                                     transport->localaddr->length, af, &err);
891         
892         camel_exception_clear (&err);
893         
894         if (host && host->h_name && *host->h_name) {
895                 name = g_strdup (host->h_name);
896         } else {
897 #ifdef ENABLE_IPv6
898                 char ip[MAXHOSTNAMELEN + 1];
899                 
900                 name = g_strdup_printf ("[%s]", inet_ntop (af, transport->localaddr->address, ip, MAXHOSTNAMELEN));
901 #else
902                 /* We *could* use inet_ntoa() here, but it's probably
903                    not worth it since we would have to worry about
904                    some systems not having inet_ntoa() */
905                 name = g_strdup_printf ("[%d.%d.%d.%d]",
906                                         transport->localaddr->address[0],
907                                         transport->localaddr->address[1],
908                                         transport->localaddr->address[2],
909                                         transport->localaddr->address[3]);
910 #endif
911         }
912         
913         if (host)
914                 camel_free_host (host);
915         
916         /* hiya server! how are you today? */
917         if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP)
918                 cmdbuf = g_strdup_printf ("EHLO %s\r\n", name);
919         else
920                 cmdbuf = g_strdup_printf ("HELO %s\r\n", name);
921         g_free (name);
922         
923         d(fprintf (stderr, "sending : %s", cmdbuf));
924         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
925                 g_free (cmdbuf);
926                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
927                                       _("HELO request timed out: %s"),
928                                       g_strerror (errno));
929                 camel_operation_end (NULL);
930                 
931                 transport->connected = FALSE;
932                 camel_object_unref (transport->istream);
933                 transport->istream = NULL;
934                 camel_object_unref (transport->ostream);
935                 transport->ostream = NULL;
936                 
937                 return FALSE;
938         }
939         g_free (cmdbuf);
940         
941         do {
942                 /* Check for "250" */
943                 g_free (respbuf);
944                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
945                 
946                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
947                 
948                 if (!respbuf || strncmp (respbuf, "250", 3)) {
949                         smtp_set_exception (transport, respbuf, _("HELO response error"), ex);
950                         camel_operation_end (NULL);
951                         g_free (respbuf);
952                         
953                         return FALSE;
954                 }
955                 
956                 token = respbuf + 4;
957                 
958                 if (transport->flags & CAMEL_SMTP_TRANSPORT_IS_ESMTP) {
959                         if (!strncmp (token, "8BITMIME", 8)) {
960                                 d(fprintf (stderr, "This server supports 8bit MIME\n"));
961                                 transport->flags |= CAMEL_SMTP_TRANSPORT_8BITMIME;
962                         } else if (!strncmp (token, "ENHANCEDSTATUSCODES", 19)) {
963                                 d(fprintf (stderr, "This server supports enhanced status codes\n"));
964                                 transport->flags |= CAMEL_SMTP_TRANSPORT_ENHANCEDSTATUSCODES;
965                         } else if (!strncmp (token, "STARTTLS", 8)) {
966                                 d(fprintf (stderr, "This server supports STARTTLS\n"));
967                                 transport->flags |= CAMEL_SMTP_TRANSPORT_STARTTLS;
968                         } else if (!strncmp (token, "AUTH", 4)) {
969                                 if (!transport->authtypes || transport->flags & CAMEL_SMTP_TRANSPORT_AUTH_EQUAL) {
970                                         /* Don't bother parsing any authtypes if we already have a list.
971                                          * Some servers will list AUTH twice, once the standard way and
972                                          * once the way Microsoft Outlook requires them to be:
973                                          *
974                                          * 250-AUTH LOGIN PLAIN DIGEST-MD5 CRAM-MD5
975                                          * 250-AUTH=LOGIN PLAIN DIGEST-MD5 CRAM-MD5
976                                          *
977                                          * Since they can come in any order, parse each list that we get
978                                          * until we parse an authtype list that does not use the AUTH=
979                                          * format. We want to let the standard way have priority over the
980                                          * broken way.
981                                          **/
982                                         
983                                         if (token[4] == '=')
984                                                 transport->flags |= CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
985                                         else
986                                                 transport->flags &= ~CAMEL_SMTP_TRANSPORT_AUTH_EQUAL;
987                                         
988                                         /* parse for supported AUTH types */
989                                         token += 5;
990                                         
991                                         if (transport->authtypes) {
992                                                 g_hash_table_foreach (transport->authtypes, authtypes_free, NULL);
993                                                 g_hash_table_destroy (transport->authtypes);
994                                         }
995                                         
996                                         transport->authtypes = esmtp_get_authtypes (token);
997                                 }
998                         }
999                 }
1000         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1001         g_free (respbuf);
1002         
1003         camel_operation_end (NULL);
1004         
1005         return TRUE;
1006 }
1007
1008 static gboolean
1009 smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex)
1010 {
1011         char *cmdbuf, *respbuf = NULL, *challenge;
1012         gboolean auth_challenge = FALSE;
1013         CamelSasl *sasl = NULL;
1014         
1015         camel_operation_start_transient (NULL, _("SMTP Authentication"));
1016         
1017         sasl = camel_sasl_new ("smtp", mech, CAMEL_SERVICE (transport));
1018         if (!sasl) {
1019                 camel_operation_end (NULL);
1020                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1021                                       _("Error creating SASL authentication object."));
1022                 return FALSE;
1023         }
1024         
1025         challenge = camel_sasl_challenge_base64 (sasl, NULL, ex);
1026         if (challenge) {
1027                 auth_challenge = TRUE;
1028                 cmdbuf = g_strdup_printf ("AUTH %s %s\r\n", mech, challenge);
1029                 g_free (challenge);
1030         } else {
1031                 cmdbuf = g_strdup_printf ("AUTH %s\r\n", mech);
1032         }
1033         
1034         d(fprintf (stderr, "sending : %s", cmdbuf));
1035         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1036                 g_free (cmdbuf);
1037                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1038                                       _("AUTH request timed out: %s"),
1039                                       g_strerror (errno));
1040                 goto lose;
1041         }
1042         g_free (cmdbuf);
1043         
1044         respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1045         d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1046         
1047         while (!camel_sasl_authenticated (sasl)) {
1048                 if (!respbuf) {
1049                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1050                                               _("AUTH request timed out: %s"),
1051                                               g_strerror (errno));
1052                         goto lose;
1053                 }
1054                 
1055                 /* the server challenge/response should follow a 334 code */
1056                 if (strncmp (respbuf, "334", 3)) {
1057                         g_free (respbuf);
1058                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1059                                               _("AUTH request failed."));
1060                         goto lose;
1061                 }
1062                 
1063                 if (FALSE) {
1064                 broken_smtp_server:
1065                         d(fprintf (stderr, "Your SMTP server's implementation of the %s SASL\n"
1066                                    "authentication mechanism is broken. Please report this to the\n"
1067                                    "appropriate vendor and suggest that they re-read rfc2222 again\n"
1068                                    "for the first time (specifically Section 4, paragraph 2).\n",
1069                                    mech));
1070                 }
1071                 
1072                 /* eat whtspc */
1073                 for (challenge = respbuf + 4; isspace (*challenge); challenge++);
1074                 
1075                 challenge = camel_sasl_challenge_base64 (sasl, challenge, ex);
1076                 g_free (respbuf);
1077                 if (challenge == NULL)
1078                         goto break_and_lose;
1079                 
1080                 /* send our challenge */
1081                 cmdbuf = g_strdup_printf ("%s\r\n", challenge);
1082                 g_free (challenge);
1083                 d(fprintf (stderr, "sending : %s", cmdbuf));
1084                 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1085                         g_free (cmdbuf);
1086                         goto lose;
1087                 }
1088                 g_free (cmdbuf);
1089                 
1090                 /* get the server's response */
1091                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1092                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1093         }
1094         
1095         /* check that the server says we are authenticated */
1096         if (!respbuf || strncmp (respbuf, "235", 3)) {
1097                 if (respbuf && auth_challenge && !strncmp (respbuf, "334", 3)) {
1098                         /* broken server, but lets try and work around it anyway... */
1099                         goto broken_smtp_server;
1100                 }
1101                 g_free (respbuf);
1102                 goto lose;
1103         }
1104         
1105         camel_object_unref (CAMEL_OBJECT (sasl));
1106         camel_operation_end (NULL);
1107         
1108         return TRUE;
1109         
1110  break_and_lose:
1111         /* Get the server out of "waiting for continuation data" mode. */
1112         d(fprintf (stderr, "sending : *\n"));
1113         camel_stream_write (transport->ostream, "*\r\n", 3);
1114         respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1115         d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1116         
1117  lose:
1118         if (!camel_exception_is_set (ex)) {
1119                 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
1120                                      _("Bad authentication response from server.\n"));
1121         }
1122         
1123         camel_object_unref (CAMEL_OBJECT (sasl));
1124         camel_operation_end (NULL);
1125         
1126         return FALSE;
1127 }
1128
1129 static gboolean
1130 smtp_mail (CamelSmtpTransport *transport, const char *sender, gboolean has_8bit_parts, CamelException *ex)
1131 {
1132         /* we gotta tell the smtp server who we are. (our email addy) */
1133         char *cmdbuf, *respbuf = NULL;
1134         
1135         if (transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME && has_8bit_parts)
1136                 cmdbuf = g_strdup_printf ("MAIL FROM:<%s> BODY=8BITMIME\r\n", sender);
1137         else
1138                 cmdbuf = g_strdup_printf ("MAIL FROM:<%s>\r\n", sender);
1139         
1140         d(fprintf (stderr, "sending : %s", cmdbuf));
1141         
1142         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1143                 g_free (cmdbuf);
1144                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1145                                       _("MAIL FROM request timed out: %s: mail not sent"),
1146                                       g_strerror (errno));
1147                 
1148                 camel_object_unref (transport->istream);
1149                 transport->istream = NULL;
1150                 camel_object_unref (transport->ostream);
1151                 transport->ostream = NULL;
1152                 
1153                 return FALSE;
1154         }
1155         g_free (cmdbuf);
1156         
1157         do {
1158                 /* Check for "250 Sender OK..." */
1159                 g_free (respbuf);
1160                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1161                 
1162                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1163                 
1164                 if (!respbuf || strncmp (respbuf, "250", 3)) {
1165                         smtp_set_exception (transport, respbuf, _("MAIL FROM response error"), ex);
1166                         g_free (respbuf);
1167                         return FALSE;
1168                 }
1169         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1170         g_free (respbuf);
1171         
1172         return TRUE;
1173 }
1174
1175 static gboolean
1176 smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex)
1177 {
1178         /* we gotta tell the smtp server who we are going to be sending
1179          * our email to */
1180         char *cmdbuf, *respbuf = NULL;
1181         
1182         cmdbuf = g_strdup_printf ("RCPT TO:<%s>\r\n", recipient);
1183         
1184         d(fprintf (stderr, "sending : %s", cmdbuf));
1185         
1186         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1187                 g_free (cmdbuf);
1188                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1189                                       _("RCPT TO request timed out: %s: mail not sent"),
1190                                       g_strerror (errno));
1191                 
1192                 camel_object_unref (transport->istream);
1193                 transport->istream = NULL;
1194                 camel_object_unref (transport->ostream);
1195                 transport->ostream = NULL;
1196                 
1197                 return FALSE;
1198         }
1199         g_free (cmdbuf);
1200         
1201         do {
1202                 /* Check for "250 Recipient OK..." */
1203                 g_free (respbuf);
1204                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1205                 
1206                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1207                 
1208                 if (!respbuf || strncmp (respbuf, "250", 3)) {
1209                         char *message;
1210                         
1211                         message = g_strdup_printf (_("RCPT TO <%s> failed"), recipient);
1212                         smtp_set_exception (transport, respbuf, message, ex);
1213                         g_free (message);
1214                         g_free (respbuf);
1215                         return FALSE;
1216                 }
1217         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1218         g_free (respbuf);
1219         
1220         return TRUE;
1221 }
1222
1223 static gboolean
1224 smtp_data (CamelSmtpTransport *transport, CamelMimeMessage *message, CamelException *ex)
1225 {
1226         CamelBestencEncoding enctype = CAMEL_BESTENC_8BIT;
1227         struct _header_raw *header, *savedbcc, *n, *tail;
1228         char *cmdbuf, *respbuf = NULL;
1229         CamelStreamFilter *filtered_stream;
1230         CamelMimeFilter *crlffilter;
1231         int ret;
1232         
1233         /* If the server doesn't support 8BITMIME, set our required encoding to be 7bit */
1234         if (!(transport->flags & CAMEL_SMTP_TRANSPORT_8BITMIME))
1235                 enctype = CAMEL_BESTENC_7BIT;
1236         
1237         /* FIXME: should we get the best charset too?? */
1238         /* Changes the encoding of all mime parts to fit within our required
1239            encoding type and also force any text parts with long lines (longer
1240            than 998 octets) to wrap by QP or base64 encoding them. */
1241         camel_mime_message_set_best_encoding (message, CAMEL_BESTENC_GET_ENCODING, enctype);
1242         
1243         cmdbuf = g_strdup ("DATA\r\n");
1244         
1245         d(fprintf (stderr, "sending : %s", cmdbuf));
1246         
1247         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1248                 g_free (cmdbuf);
1249                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1250                                       _("DATA request timed out: %s: mail not sent"),
1251                                       g_strerror (errno));
1252                 
1253                 camel_object_unref (transport->istream);
1254                 transport->istream = NULL;
1255                 camel_object_unref (transport->ostream);
1256                 transport->ostream = NULL;
1257                 
1258                 return FALSE;
1259         }
1260         g_free (cmdbuf);
1261         
1262         respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1263         
1264         d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1265         
1266         if (!respbuf || strncmp (respbuf, "354", 3)) {
1267                 /* we should have gotten instructions on how to use the DATA command:
1268                  * 354 Enter mail, end with "." on a line by itself
1269                  */
1270                 smtp_set_exception (transport, respbuf, _("DATA response error"), ex);
1271                 g_free (respbuf);
1272                 return FALSE;
1273         }
1274         
1275         g_free (respbuf);
1276         respbuf = NULL;
1277         
1278         /* setup stream filtering */
1279         crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
1280         filtered_stream = camel_stream_filter_new_with_stream (transport->ostream);
1281         camel_stream_filter_add (filtered_stream, CAMEL_MIME_FILTER (crlffilter));
1282         camel_object_unref (CAMEL_OBJECT (crlffilter));
1283         
1284         /* unlink the bcc headers */
1285         savedbcc = NULL;
1286         tail = (struct _header_raw *) &savedbcc;
1287         
1288         header = (struct _header_raw *) &CAMEL_MIME_PART (message)->headers;
1289         n = header->next;
1290         while (n != NULL) {
1291                 if (!strcasecmp (n->name, "Bcc")) {
1292                         header->next = n->next;
1293                         tail->next = n;
1294                         n->next = NULL;
1295                         tail = n;
1296                 } else {
1297                         header = n;
1298                 }
1299                 
1300                 n = header->next;
1301         }
1302         
1303         /* write the message */
1304         ret = camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), CAMEL_STREAM (filtered_stream));
1305         
1306         /* restore the bcc headers */
1307         header->next = savedbcc;
1308         
1309         if (ret == -1) {
1310                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1311                                       _("DATA send timed out: message termination: "
1312                                         "%s: mail not sent"),
1313                                       g_strerror (errno));
1314                 
1315                 camel_object_unref (CAMEL_OBJECT (filtered_stream));
1316                 
1317                 camel_object_unref (transport->istream);
1318                 transport->istream = NULL;
1319                 camel_object_unref (transport->ostream);
1320                 transport->ostream = NULL;
1321                 
1322                 return FALSE;
1323         }
1324         
1325         camel_stream_flush (CAMEL_STREAM (filtered_stream));
1326         camel_object_unref (CAMEL_OBJECT (filtered_stream));
1327         
1328         /* terminate the message body */
1329         
1330         d(fprintf (stderr, "sending : \\r\\n.\\r\\n\n"));
1331         
1332         if (camel_stream_write (transport->ostream, "\r\n.\r\n", 5) == -1) {
1333                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1334                                       _("DATA send timed out: message termination: "
1335                                         "%s: mail not sent"),
1336                                       g_strerror (errno));
1337                 
1338                 camel_object_unref (transport->istream);
1339                 transport->istream = NULL;
1340                 camel_object_unref (transport->ostream);
1341                 transport->ostream = NULL;
1342                 
1343                 return FALSE;
1344         }
1345         
1346         do {
1347                 /* Check for "250 Sender OK..." */
1348                 g_free (respbuf);
1349                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1350                 
1351                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1352                 
1353                 if (!respbuf || strncmp (respbuf, "250", 3)) {
1354                         smtp_set_exception (transport, respbuf, _("DATA termination response error"), ex);
1355                         g_free (respbuf);
1356                         return FALSE;
1357                 }
1358         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1359         g_free (respbuf);
1360         
1361         return TRUE;
1362 }
1363
1364 static gboolean
1365 smtp_rset (CamelSmtpTransport *transport, CamelException *ex)
1366 {
1367         /* we are going to reset the smtp server (just to be nice) */
1368         char *cmdbuf, *respbuf = NULL;
1369         
1370         cmdbuf = g_strdup ("RSET\r\n");
1371         
1372         d(fprintf (stderr, "sending : %s", cmdbuf));
1373         
1374         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1375                 g_free (cmdbuf);
1376                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1377                                       _("RSET request timed out: %s"),
1378                                       g_strerror (errno));
1379                 
1380                 camel_object_unref (transport->istream);
1381                 transport->istream = NULL;
1382                 camel_object_unref (transport->ostream);
1383                 transport->ostream = NULL;
1384                 
1385                 return FALSE;
1386         }
1387         g_free (cmdbuf);
1388         
1389         do {
1390                 /* Check for "250" */
1391                 g_free (respbuf);
1392                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1393                 
1394                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1395                 
1396                 if (!respbuf || strncmp (respbuf, "250", 3)) {
1397                         smtp_set_exception (transport, respbuf, _("RSET response error"), ex);
1398                         g_free (respbuf);
1399                         return FALSE;
1400                 }
1401         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1402         g_free (respbuf);
1403         
1404         return TRUE;
1405 }
1406
1407 static gboolean
1408 smtp_quit (CamelSmtpTransport *transport, CamelException *ex)
1409 {
1410         /* we are going to reset the smtp server (just to be nice) */
1411         char *cmdbuf, *respbuf = NULL;
1412         
1413         cmdbuf = g_strdup ("QUIT\r\n");
1414         
1415         d(fprintf (stderr, "sending : %s", cmdbuf));
1416         
1417         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1418                 g_free (cmdbuf);
1419                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1420                                       _("QUIT request timed out: %s"),
1421                                       g_strerror (errno));
1422                 
1423                 camel_object_unref (transport->istream);
1424                 transport->istream = NULL;
1425                 camel_object_unref (transport->ostream);
1426                 transport->ostream = NULL;
1427                 
1428                 return FALSE;
1429         }
1430         g_free (cmdbuf);
1431         
1432         do {
1433                 /* Check for "221" */
1434                 g_free (respbuf);
1435                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1436                 
1437                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1438                 
1439                 if (!respbuf || strncmp (respbuf, "221", 3)) {
1440                         smtp_set_exception (transport, respbuf, _("QUIT response error"), ex);
1441                         g_free (respbuf);
1442                         return FALSE;
1443                 }
1444         } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */
1445         g_free (respbuf);
1446         
1447         return TRUE;
1448 }