Don't check the initial auth response until we get into the while-loop
[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@helixcode.com>
6  *
7  * Copyright (C) 2000 Helix Code, Inc. (www.helixcode.com)
8  *
9  * This program is free software; you can redistribute it and/or 
10  * modify it under the terms of the GNU General Public License as 
11  * published by the Free Software Foundation; either version 2 of the
12  * License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22  * USA
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-mime-filter-linewrap.h"
44 #include "camel-stream-filter.h"
45 #include "camel-smtp-transport.h"
46 #include "camel-mime-message.h"
47 #include "camel-multipart.h"
48 #include "camel-mime-part.h"
49 #include "camel-stream-buffer.h"
50 #include "camel-tcp-stream.h"
51 #include "camel-tcp-stream-raw.h"
52 #ifdef HAVE_NSS
53 #include "camel-tcp-stream-ssl.h"
54 #include <prnetdb.h>
55 #endif
56 #ifdef HAVE_OPENSSL
57 #include "camel-tcp-stream-openssl.h"
58 #endif
59 #include "camel-session.h"
60 #include "camel-exception.h"
61 #include "camel-sasl.h"
62 #include "string-utils.h"
63
64 #define d(x) x
65
66 /* Specified in RFC 821 */
67 #define SMTP_PORT 25
68
69 /* camel smtp transport class prototypes */
70 static gboolean smtp_can_send (CamelTransport *transport, CamelMedium *message);
71 static gboolean smtp_send (CamelTransport *transport, CamelMedium *message, CamelException *ex);
72 static gboolean smtp_send_to (CamelTransport *transport, CamelMedium *message, GList *recipients, CamelException *ex);
73
74 /* support prototypes */
75 static void smtp_construct (CamelService *service, CamelSession *session,
76                             CamelProvider *provider, CamelURL *url,
77                             CamelException *ex);
78 static gboolean smtp_connect (CamelService *service, CamelException *ex);
79 static gboolean smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex);
80 static GHashTable *esmtp_get_authtypes (gchar *buffer);
81 static GList *query_auth_types (CamelService *service, CamelException *ex);
82 static char *get_name (CamelService *service, gboolean brief);
83
84 static gboolean smtp_helo (CamelSmtpTransport *transport, CamelException *ex);
85 static gboolean smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex);
86 static gboolean smtp_mail (CamelSmtpTransport *transport, const char *sender,
87                            gboolean has_8bit_parts, CamelException *ex);
88 static gboolean smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex);
89 static gboolean smtp_data (CamelSmtpTransport *transport, CamelMedium *message,
90                            gboolean has_8bit_parts, CamelException *ex);
91 static gboolean smtp_rset (CamelSmtpTransport *transport, CamelException *ex);
92 static gboolean smtp_quit (CamelSmtpTransport *transport, CamelException *ex);
93
94 /* private data members */
95 static CamelTransportClass *parent_class = NULL;
96
97 static void
98 camel_smtp_transport_class_init (CamelSmtpTransportClass *camel_smtp_transport_class)
99 {
100         CamelTransportClass *camel_transport_class =
101                 CAMEL_TRANSPORT_CLASS (camel_smtp_transport_class);
102         CamelServiceClass *camel_service_class =
103                 CAMEL_SERVICE_CLASS (camel_smtp_transport_class);
104         
105         parent_class = CAMEL_TRANSPORT_CLASS (camel_type_get_global_classfuncs (camel_transport_get_type ()));
106         
107         /* virtual method overload */
108         camel_service_class->construct = smtp_construct;
109         camel_service_class->connect = smtp_connect;
110         camel_service_class->disconnect = smtp_disconnect;
111         camel_service_class->query_auth_types = query_auth_types;
112         camel_service_class->get_name = get_name;
113
114         camel_transport_class->can_send = smtp_can_send;
115         camel_transport_class->send = smtp_send;
116         camel_transport_class->send_to = smtp_send_to;
117 }
118
119 static void
120 camel_smtp_transport_init (gpointer object)
121 {
122         CamelTransport *transport = CAMEL_TRANSPORT (object);
123         
124         transport->supports_8bit = FALSE;
125 }
126
127 CamelType
128 camel_smtp_transport_get_type (void)
129 {
130         static CamelType camel_smtp_transport_type = CAMEL_INVALID_TYPE;
131         
132         if (camel_smtp_transport_type == CAMEL_INVALID_TYPE) {
133                 camel_smtp_transport_type =
134                         camel_type_register (CAMEL_TRANSPORT_TYPE, "CamelSmtpTransport",
135                                              sizeof (CamelSmtpTransport),
136                                              sizeof (CamelSmtpTransportClass),
137                                              (CamelObjectClassInitFunc) camel_smtp_transport_class_init,
138                                              NULL,
139                                              (CamelObjectInitFunc) camel_smtp_transport_init,
140                                              NULL);
141         }
142         
143         return camel_smtp_transport_type;
144 }
145
146 static void
147 smtp_construct (CamelService *service, CamelSession *session,
148                 CamelProvider *provider, CamelURL *url,
149                 CamelException *ex)
150 {
151         CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (service);
152
153         CAMEL_SERVICE_CLASS (parent_class)->construct (service, session, provider, url, ex);
154
155         if (camel_url_get_param (url, "use_ssl"))
156                 smtp_transport->use_ssl = TRUE;
157 }
158
159 static const char *
160 get_smtp_error_string (int error)
161 {
162         /* SMTP error codes grabbed from rfc821 */
163         switch (error) {
164         case 0:
165                 /* looks like a read problem, check errno */
166                 return g_strerror (errno);
167         case 500:
168                 return _("Syntax error, command unrecognized");
169         case 501:
170                 return _("Syntax error in parameters or arguments");
171         case 502:
172                 return _("Command not implemented");
173         case 504:
174                 return _("Command parameter not implemented");
175         case 211:
176                 return _("System status, or system help reply");
177         case 214:
178                 return _("Help message");
179         case 220:
180                 return _("Service ready");
181         case 221:
182                 return _("Service closing transmission channel");
183         case 421:
184                 return _("Service not available, closing transmission channel");
185         case 250:
186                 return _("Requested mail action okay, completed");
187         case 251:
188                 return _("User not local; will forward to <forward-path>");
189         case 450:
190                 return _("Requested mail action not taken: mailbox unavailable");
191         case 550:
192                 return _("Requested action not taken: mailbox unavailable");
193         case 451:
194                 return _("Requested action aborted: error in processing");
195         case 551:
196                 return _("User not local; please try <forward-path>");
197         case 452:
198                 return _("Requested action not taken: insufficient system storage");
199         case 552:
200                 return _("Requested mail action aborted: exceeded storage allocation");
201         case 553:
202                 return _("Requested action not taken: mailbox name not allowed");
203         case 354:
204                 return _("Start mail input; end with <CRLF>.<CRLF>");
205         case 554:
206                 return _("Transaction failed");
207                 
208         /* AUTH error codes: */
209         case 432:
210                 return _("A password transition is needed");
211         case 534:
212                 return _("Authentication mechanism is too weak");
213         case 538:
214                 return _("Encryption required for requested authentication mechanism");
215         case 454:
216                 return _("Temporary authentication failure");
217         case 530:
218                 return _("Authentication required");
219                 
220         default:
221                 return _("Unknown");
222         }
223 }
224
225 static gboolean
226 connect_to_server (CamelService *service, CamelException *ex)
227 {
228         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
229         CamelStream *tcp_stream;
230         gchar *respbuf = NULL;
231         struct hostent *h;
232         guint32 addrlen;
233         int port, ret;
234         
235         if (!CAMEL_SERVICE_CLASS (parent_class)->connect (service, ex))
236                 return FALSE;
237         
238         h = camel_service_gethost (service, ex);
239         if (!h)
240                 return FALSE;
241         
242         /* set some smtp transport defaults */
243         transport->is_esmtp = FALSE;
244         transport->authtypes = NULL;
245         CAMEL_TRANSPORT (transport)->supports_8bit = FALSE;
246         
247         port = service->url->port ? service->url->port : SMTP_PORT;
248         
249 #if defined(HAVE_NSS) || defined(HAVE_OPENSSL)
250         if (transport->use_ssl) {
251                 port = service->url->port ? service->url->port : 465;
252 #ifdef HAVE_NSS
253                 /* use the preferred implementation - NSS */
254                 tcp_stream = camel_tcp_stream_ssl_new (service, service->url->host);
255 #else
256                 tcp_stream = camel_tcp_stream_openssl_new (service, service->url->host);
257 #endif /* HAVE_NSS */
258         } else {
259                 tcp_stream = camel_tcp_stream_raw_new ();
260         }
261 #else
262         tcp_stream = camel_tcp_stream_raw_new ();
263 #endif /* HAVE_NSS || HAVE_OPENSSL */
264         
265         ret = camel_tcp_stream_connect (CAMEL_TCP_STREAM (tcp_stream), h, port);
266         camel_free_host(h);
267         if (ret == -1) {
268                 camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE,
269                                       _("Could not connect to %s (port %d): %s"),
270                                       service->url->host, port,
271                                       g_strerror (errno));
272                 
273                 return FALSE;
274         }
275         
276         /* get the localaddr - needed later by smtp_helo */
277         addrlen = sizeof (transport->localaddr);
278 #ifdef HAVE_NSS
279         if (transport->use_ssl) {
280                 PRFileDesc *sockfd = camel_tcp_stream_get_socket (CAMEL_TCP_STREAM (tcp_stream));
281                 PRNetAddr addr;
282                 char hname[1024];
283                 
284                 PR_GetSockName (sockfd, &addr);
285                 memset (hname, 0, sizeof (hname));
286                 PR_NetAddrToString (&addr, hname, 1023);
287                 
288                 inet_aton (hname, (struct in_addr *)&transport->localaddr.sin_addr);
289         } else {
290                 int sockfd = GPOINTER_TO_INT (camel_tcp_stream_get_socket (CAMEL_TCP_STREAM (tcp_stream)));
291                 
292                 getsockname (sockfd, (struct sockaddr *)&transport->localaddr, &addrlen);
293         }
294 #else
295         getsockname (CAMEL_TCP_STREAM_RAW (tcp_stream)->sockfd,
296                      (struct sockaddr *)&transport->localaddr, &addrlen);
297 #endif /* HAVE_NSS */
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                         int error;
309                         
310                         error = respbuf ? atoi (respbuf) : 0;
311                         g_free (respbuf);
312                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
313                                               _("Welcome response error: %s: possibly non-fatal"),
314                                               get_smtp_error_string (error));
315                         return FALSE;
316                 }
317                 if (strstr (respbuf, "ESMTP"))
318                         transport->is_esmtp = TRUE;
319         } while (*(respbuf+3) == '-'); /* if we got "220-" then loop again */
320         g_free (respbuf);
321         
322         /* send HELO (or EHLO, depending on the service type) */
323         if (!transport->is_esmtp) {
324                 /* If we did not auto-detect ESMTP, we should still send EHLO */
325                 transport->is_esmtp = TRUE;
326                 if (!smtp_helo (transport, NULL)) {
327                         /* Okay, apprently this server doesn't support ESMTP */
328                         transport->is_esmtp = FALSE;
329                         smtp_helo (transport, ex);
330                 }
331         } else {
332                 /* send EHLO */
333                 smtp_helo (transport, ex);
334         }
335         
336         return TRUE;
337 }
338
339 static gboolean
340 smtp_connect (CamelService *service, CamelException *ex)
341 {
342         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
343
344         if (!connect_to_server (service, ex))
345                 return FALSE;
346
347         /* check to see if AUTH is required, if so...then AUTH ourselves */
348         if (service->url->authmech) {
349                 CamelSession *session = camel_service_get_session (service);
350                 CamelServiceAuthType *authtype;
351                 gboolean authenticated = FALSE;
352                 char *errbuf = NULL;
353                 
354                 if (!transport->is_esmtp || !g_hash_table_lookup (transport->authtypes, service->url->authmech)) {
355                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
356                                               _("SMTP server %s does not support requested "
357                                               "authentication type %s"), service->url->host,
358                                               service->url->authmech);
359                         camel_service_disconnect (service, TRUE, NULL);
360                         return FALSE;
361                 }
362                 
363                 authtype = camel_sasl_authtype (service->url->authmech);
364                 if (!authtype) {
365                         camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
366                                               _("No support for authentication type %s"),
367                                               service->url->authmech);
368                         camel_service_disconnect (service, TRUE, NULL);
369                         return FALSE;
370                 }
371                 
372                 if (!authtype->need_password) {
373                         /* authentication mechanism doesn't need a password,
374                            so if it fails there's nothing we can do */
375                         authenticated = smtp_auth (transport, authtype->authproto, ex);
376                         if (!authenticated) {
377                                 camel_service_disconnect (service, TRUE, NULL);
378                                 return FALSE;
379                         }
380                 }
381                 
382                 /* keep trying to login until either we succeed or the user cancels */
383                 while (!authenticated) {
384                         if (errbuf) {
385                                 /* We need to un-cache the password before prompting again */
386                                 camel_session_forget_password (
387                                         session, service, "password", ex);
388                                 g_free (service->url->passwd);
389                                 service->url->passwd = NULL;
390                         }
391                         
392                         if (!service->url->passwd) {
393                                 char *prompt;
394                                 
395                                 prompt = g_strdup_printf (_("%sPlease enter the SMTP password for %s@%s"),
396                                                           errbuf ? errbuf : "", service->url->user,
397                                                           service->url->host);
398                                 
399                                 service->url->passwd =
400                                         camel_session_get_password (
401                                                 session, prompt, TRUE,
402                                                 service, "password", ex);
403                                 
404                                 g_free (prompt);
405                                 g_free (errbuf);
406                                 errbuf = NULL;
407                                 
408                                 if (!service->url->passwd) {
409                                         camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
410                                                              _("You didn't enter a password."));
411                                         camel_service_disconnect (service, TRUE, NULL);
412                                         return FALSE;
413                                 }
414                         }
415                         
416                         authenticated = smtp_auth (transport, authtype->authproto, ex);
417                         if (!authenticated) {
418                                 errbuf = g_strdup_printf (_("Unable to authenticate "
419                                                             "to SMTP server.\n%s\n\n"),
420                                                           camel_exception_get_description (ex));
421                                 camel_exception_clear (ex);
422                         }
423                 }
424                 
425                 /* The spec says we have to re-EHLO, but some servers
426                  * we won't bother to name don't want you to... so ignore
427                  * errors.
428                  */
429                 smtp_helo (transport, NULL);
430         }
431         
432         return TRUE;
433 }
434
435 static gboolean
436 authtypes_free (gpointer key, gpointer value, gpointer data)
437 {
438         g_free (key);
439         g_free (value);
440         
441         return TRUE;
442 }
443
444 static gboolean
445 smtp_disconnect (CamelService *service, gboolean clean, CamelException *ex)
446 {
447         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
448         
449         /*if (!service->connected)
450          *      return TRUE;
451          */
452         
453         if (clean) {
454                 /* send the QUIT command to the SMTP server */
455                 smtp_quit (transport, ex);
456         }
457         
458         if (!CAMEL_SERVICE_CLASS (parent_class)->disconnect (service, clean, ex))
459                 return FALSE;
460         
461         if (transport->authtypes) {
462                 g_hash_table_foreach_remove (transport->authtypes, authtypes_free, NULL);
463                 g_hash_table_destroy (transport->authtypes);
464                 transport->authtypes = NULL;
465         }
466         
467         camel_object_unref (CAMEL_OBJECT (transport->ostream));
468         camel_object_unref (CAMEL_OBJECT (transport->istream));
469         
470         transport->ostream = NULL;
471         transport->istream = NULL;
472         
473         return TRUE;
474 }
475
476 static GHashTable *
477 esmtp_get_authtypes (char *buffer)
478 {
479         GHashTable *table = NULL;
480         gchar *start, *end;
481         
482         /* advance to the first token */
483         for (start = buffer; isspace (*start) || *start == '='; start++);
484         
485         if (!*start) return NULL;
486         
487         table = g_hash_table_new (g_str_hash, g_str_equal);
488         
489         for ( ; *start; ) {
490                 char *type;
491                 
492                 /* advance to the end of the token */
493                 for (end = start; *end && !isspace (*end); end++);
494                 
495                 type = g_strndup (start, end - start);
496                 g_hash_table_insert (table, g_strdup (type), type);
497                 
498                 /* advance to the next token */
499                 for (start = end; isspace (*start); start++);
500         }
501         
502         return table;
503 }
504
505 static GList *
506 query_auth_types (CamelService *service, CamelException *ex)
507 {
508         CamelSmtpTransport *transport = CAMEL_SMTP_TRANSPORT (service);
509         CamelServiceAuthType *authtype;
510         GList *types, *t, *next;
511         
512         if (!connect_to_server (service, ex))
513                 return NULL;
514         
515         types = g_list_copy (service->provider->authtypes);
516         for (t = types; t; t = next) {
517                 authtype = t->data;
518                 next = t->next;
519                 
520                 if (!g_hash_table_lookup (transport->authtypes, authtype->authproto)) {
521                         types = g_list_remove_link (types, t);
522                         g_list_free_1 (t);
523                 }
524         }
525         
526         smtp_disconnect (service, TRUE, NULL);
527         return types;
528 }
529
530 static char *
531 get_name (CamelService *service, gboolean brief)
532 {
533         if (brief)
534                 return g_strdup_printf (_("SMTP server %s"), service->url->host);
535         else {
536                 return g_strdup_printf (_("SMTP mail delivery via %s"),
537                                         service->url->host);
538         }
539 }
540
541 static gboolean
542 smtp_can_send (CamelTransport *transport, CamelMedium *message)
543 {
544         return CAMEL_IS_MIME_MESSAGE (message);
545 }
546
547 static gboolean
548 smtp_send_to (CamelTransport *transport, CamelMedium *message,
549               GList *recipients, CamelException *ex)
550 {
551         CamelSmtpTransport *smtp_transport = CAMEL_SMTP_TRANSPORT (transport);
552         const CamelInternetAddress *cia;
553         char *recipient;
554         const char *addr;
555         gboolean has_8bit_parts;
556         GList *r;
557         
558         cia = camel_mime_message_get_from(CAMEL_MIME_MESSAGE (message));
559         if (!cia) {
560                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
561                                       _("Cannot send message: "
562                                         "sender address not defined."));
563                 return FALSE;
564         }
565         
566         if (!camel_internet_address_get (cia, 0, NULL, &addr)) {
567                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
568                                       _("Cannot send message: "
569                                         "sender address not valid."));
570                 return FALSE;
571         }
572         
573         /* find out if the message has 8bit mime parts */
574         has_8bit_parts = camel_mime_message_has_8bit_parts (CAMEL_MIME_MESSAGE (message));
575         
576         /* rfc1652 (8BITMIME) requires that you notify the ESMTP daemon that
577            you'll be sending an 8bit mime message at "MAIL FROM:" time. */
578         smtp_mail (smtp_transport, addr, has_8bit_parts, ex);
579         
580         if (!recipients) {
581                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
582                                       _("Cannot send message: "
583                                         "no recipients defined."));
584                 return FALSE;
585         }
586         
587         for (r = recipients; r; r = r->next) {
588                 recipient = (char *) r->data;
589                 if (!smtp_rcpt (smtp_transport, recipient, ex)) {
590                         g_free (recipient);
591                         return FALSE;
592                 }
593                 g_free (recipient);
594         }
595         
596         /* passing in has_8bit_parts saves time as we don't have to
597            recurse through the message all over again if the user is
598            not sending 8bit mime parts */
599         if (!smtp_data (smtp_transport, message, has_8bit_parts, ex))
600                 return FALSE;
601         
602         /* reset the service for our next transfer session */
603         smtp_rset (smtp_transport, ex);
604         
605         return TRUE;
606 }
607
608 static gboolean
609 smtp_send (CamelTransport *transport, CamelMedium *message, CamelException *ex)
610 {
611         const CamelInternetAddress *to, *cc, *bcc;
612         GList *recipients = NULL;
613         guint index, len;
614         
615         to = camel_mime_message_get_recipients (CAMEL_MIME_MESSAGE (message), CAMEL_RECIPIENT_TYPE_TO);
616         cc = camel_mime_message_get_recipients (CAMEL_MIME_MESSAGE (message), CAMEL_RECIPIENT_TYPE_CC);
617         bcc = camel_mime_message_get_recipients (CAMEL_MIME_MESSAGE (message), CAMEL_RECIPIENT_TYPE_BCC);
618         
619         /* get all of the To addresses into our recipient list */
620         len = CAMEL_ADDRESS (to)->addresses->len;
621         for (index = 0; index < len; index++) {
622                 const char *addr;
623                 
624                 if (camel_internet_address_get (to, index, NULL, &addr))
625                         recipients = g_list_append (recipients, g_strdup (addr));
626         }
627         
628         /* get all of the Cc addresses into our recipient list */
629         len = CAMEL_ADDRESS (cc)->addresses->len;
630         for (index = 0; index < len; index++) {
631                 const char *addr;
632                 
633                 if (camel_internet_address_get (cc, index, NULL, &addr))
634                         recipients = g_list_append (recipients, g_strdup (addr));
635         }
636         
637         /* get all of the Bcc addresses into our recipient list */
638         len = CAMEL_ADDRESS (bcc)->addresses->len;
639         for (index = 0; index < len; index++) {
640                 const char *addr;
641                 
642                 if (camel_internet_address_get (bcc, index, NULL, &addr))
643                         recipients = g_list_append (recipients, g_strdup (addr));
644         }
645         
646         return smtp_send_to (transport, message, recipients, ex);
647 }
648
649 static gboolean
650 smtp_helo (CamelSmtpTransport *transport, CamelException *ex)
651 {
652         /* say hello to the server */
653         gchar *cmdbuf, *respbuf = NULL;
654         struct hostent *host;
655         
656         /* get the local host name */
657         host = gethostbyaddr ((gchar *)&transport->localaddr.sin_addr, sizeof (transport->localaddr.sin_addr), AF_INET);
658         
659         /* hiya server! how are you today? */
660         if (transport->is_esmtp) {
661                 if (host && host->h_name)
662                         cmdbuf = g_strdup_printf ("EHLO %s\r\n", host->h_name);
663                 else
664                         cmdbuf = g_strdup_printf ("EHLO [%s]\r\n", inet_ntoa (transport->localaddr.sin_addr));
665         } else {
666                 if (host && host->h_name)
667                         cmdbuf = g_strdup_printf ("HELO %s\r\n", host->h_name);
668                 else
669                         cmdbuf = g_strdup_printf ("HELO [%s]\r\n", inet_ntoa (transport->localaddr.sin_addr));
670         }
671         
672         d(fprintf (stderr, "sending : %s", cmdbuf));
673         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
674                 g_free (cmdbuf);
675                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
676                                       _("HELO request timed out: %s: non-fatal"),
677                                       g_strerror (errno));
678                 return FALSE;
679         }
680         g_free (cmdbuf);
681         
682         do {
683                 /* Check for "250" */
684                 g_free (respbuf);
685                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
686                 
687                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
688                 
689                 if (!respbuf || strncmp (respbuf, "250", 3)) {
690                         int error;
691
692                         error = respbuf ? atoi (respbuf) : 0;
693                         g_free (respbuf);
694                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
695                                               _("HELO response error: %s: non-fatal"),
696                                               get_smtp_error_string (error));
697                         return FALSE;
698                 }
699                 
700                 if (strstrcase (respbuf, "8BITMIME")) {
701                         d(fprintf (stderr, "This server supports 8bit MIME\n"));
702                         CAMEL_TRANSPORT (transport)->supports_8bit = TRUE;
703                 }
704                 
705                 /* Only parse authtypes if we don't already have them */
706                 if (transport->is_esmtp && strstr (respbuf, "AUTH") && !transport->authtypes) {
707                         /* parse for supported AUTH types */
708                         char *auths = strstr (respbuf, "AUTH") + 4;
709                         
710                         transport->authtypes = esmtp_get_authtypes (auths);
711                 }
712         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
713         g_free (respbuf);
714         
715         return TRUE;
716 }
717
718 static gboolean
719 smtp_auth (CamelSmtpTransport *transport, const char *mech, CamelException *ex)
720 {
721         gchar *cmdbuf, *respbuf = NULL, *challenge;
722         CamelSasl *sasl;
723         
724         sasl = camel_sasl_new ("smtp", mech, CAMEL_SERVICE (transport));
725         if (!sasl) {
726                 g_free (respbuf);
727                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
728                                       _("Error creating SASL authentication object."));
729                 return FALSE;
730         }
731         
732         challenge = camel_sasl_challenge_base64 (sasl, NULL, ex);
733         if (challenge) {
734                 cmdbuf = g_strdup_printf ("AUTH %s %s\r\n", mech, challenge);
735                 g_free (challenge);
736         } else
737                 cmdbuf = g_strdup_printf ("AUTH %s\r\n", mech);
738         
739         d(fprintf (stderr, "sending : %s", cmdbuf));
740         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
741                 g_free (cmdbuf);
742                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
743                                       _("AUTH request timed out: %s"),
744                                       g_strerror (errno));
745                 goto lose;
746         }
747         g_free (cmdbuf);
748         
749         respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
750         d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
751         
752         while (!camel_sasl_authenticated (sasl)) {
753                 if (!respbuf) {
754                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
755                                               _("AUTH request timed out: %s"),
756                                               g_strerror (errno));
757                         goto lose;
758                 }
759                 
760                 /* the server challenge/response should follow a 334 code */
761                 if (strcmp (respbuf, "334")) {
762                         g_free (respbuf);
763                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
764                                               _("AUTH request failed."));
765                         goto lose;
766                 }
767                 
768                 /* eat whtspc */
769                 for (challenge = respbuf + 4; isspace (*challenge); challenge++);
770                 
771                 challenge = camel_sasl_challenge_base64 (sasl, challenge, ex);
772                 g_free (respbuf);
773                 if (camel_exception_is_set (ex))
774                         goto break_and_lose;
775                 
776                 /* send our challenge */
777                 cmdbuf = g_strdup_printf ("%s\r\n", challenge);
778                 g_free (challenge);
779                 d(fprintf (stderr, "sending : %s", cmdbuf));
780                 if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
781                         g_free (cmdbuf);
782                         goto lose;
783                 }
784                 g_free (cmdbuf);
785                 
786                 /* get the server's response */
787                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
788                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
789         }
790         
791         /* check that the server says we are authenticated */
792         if (!respbuf || strncmp (respbuf, "235", 3)) {
793                 g_free (respbuf);
794                 goto lose;
795         }
796         
797         return TRUE;
798         
799  break_and_lose:
800         /* Get the server out of "waiting for continuation data" mode. */
801         d(fprintf (stderr, "sending : *\n"));
802         camel_stream_write (transport->ostream, "*\r\n", 3);
803         respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
804         d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
805         
806  lose:
807         if (!camel_exception_is_set (ex)) {
808                 camel_exception_set (ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE,
809                                      _("Bad authentication response from server.\n"));
810         }
811         
812         if (sasl)
813                 camel_object_unref (CAMEL_OBJECT (sasl));
814         
815         return FALSE;
816 }
817
818 static gboolean
819 smtp_mail (CamelSmtpTransport *transport, const char *sender, gboolean has_8bit_parts, CamelException *ex)
820 {
821         /* we gotta tell the smtp server who we are. (our email addy) */
822         gchar *cmdbuf, *respbuf = NULL;
823         
824         /* enclose address in <>'s since some SMTP daemons *require* that */
825         if (CAMEL_TRANSPORT (transport)->supports_8bit && has_8bit_parts)
826                 cmdbuf = g_strdup_printf ("MAIL FROM: <%s> BODY=8BITMIME\r\n", sender);
827         else
828                 cmdbuf = g_strdup_printf ("MAIL FROM: <%s>\r\n", sender);
829         
830         d(fprintf (stderr, "sending : %s", cmdbuf));
831         
832         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
833                 g_free (cmdbuf);
834                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
835                                       _("MAIL FROM request timed out: %s: mail not sent"),
836                                       g_strerror (errno));
837                 return FALSE;
838         }
839         g_free (cmdbuf);
840         
841         do {
842                 /* Check for "250 Sender OK..." */
843                 g_free (respbuf);
844                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
845                 
846                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
847                 
848                 if (!respbuf || strncmp (respbuf, "250", 3)) {
849                         int error;
850                         
851                         error = respbuf ? atoi (respbuf) : 0;
852                         g_free (respbuf);
853                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
854                                               _("MAIL FROM response error: %s: mail not sent"),
855                                               get_smtp_error_string (error));
856                         return FALSE;
857                 }
858         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
859         g_free (respbuf);
860         
861         return TRUE;
862 }
863
864 static gboolean
865 smtp_rcpt (CamelSmtpTransport *transport, const char *recipient, CamelException *ex)
866 {
867         /* we gotta tell the smtp server who we are going to be sending
868          * our email to */
869         gchar *cmdbuf, *respbuf = NULL;
870         
871         /* enclose address in <>'s since some SMTP daemons *require* that */
872         cmdbuf = g_strdup_printf ("RCPT TO: <%s>\r\n", recipient);
873         
874         d(fprintf (stderr, "sending : %s", cmdbuf));
875         
876         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
877                 g_free (cmdbuf);
878                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
879                                       _("RCPT TO request timed out: %s: mail not sent"),
880                                       g_strerror (errno));
881                 return FALSE;
882         }
883         g_free (cmdbuf);
884         
885         do {
886                 /* Check for "250 Sender OK..." */
887                 g_free (respbuf);
888                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
889                 
890                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
891                 
892                 if (!respbuf || strncmp (respbuf, "250", 3)) {
893                         int error;
894                         
895                         error = respbuf ? atoi (respbuf) : 0;
896                         g_free (respbuf);
897                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
898                                               _("RCPT TO response error: %s: mail not sent"),
899                                               get_smtp_error_string (error));
900                         return FALSE;
901                 }
902         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
903         g_free (respbuf);
904         
905         return TRUE;
906 }
907
908 static gboolean
909 smtp_data (CamelSmtpTransport *transport, CamelMedium *message, gboolean has_8bit_parts, CamelException *ex)
910 {
911         /* now we can actually send what's important :p */
912         gchar *cmdbuf, *respbuf = NULL;
913         CamelStreamFilter *filtered_stream;
914         CamelMimeFilter *crlffilter;
915         
916         /* if the message contains 8bit mime parts and the server
917            doesn't support it, encode 8bit parts to the best
918            encoding.  This will also enforce an encoding to keep the lines in limit */
919         if (has_8bit_parts && !CAMEL_TRANSPORT (transport)->supports_8bit)
920                 camel_mime_message_encode_8bit_parts (CAMEL_MIME_MESSAGE (message));
921         
922         cmdbuf = g_strdup ("DATA\r\n");
923         
924         d(fprintf (stderr, "sending : %s", cmdbuf));
925         
926         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
927                 g_free (cmdbuf);
928                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
929                                       _("DATA request timed out: %s: mail not sent"),
930                                       g_strerror (errno));
931                 return FALSE;
932         }
933         g_free (cmdbuf);
934         
935         respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
936         
937         d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
938         
939         if (!respbuf || strncmp (respbuf, "354", 3)) {
940                 /* we should have gotten instructions on how to use the DATA command:
941                  * 354 Enter mail, end with "." on a line by itself
942                  */
943                 int error;
944                         
945                 error = respbuf ? atoi (respbuf) : 0;
946                 g_free (respbuf);
947                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
948                                       _("DATA response error: %s: mail not sent"),
949                                       get_smtp_error_string (error));
950                 return FALSE;
951         }
952
953         g_free (respbuf);
954         respbuf = NULL;
955         
956         /* setup stream filtering */
957         crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS);
958         filtered_stream = camel_stream_filter_new_with_stream (transport->ostream);
959         camel_stream_filter_add (filtered_stream, CAMEL_MIME_FILTER (crlffilter));
960         
961         if (camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (message), CAMEL_STREAM (filtered_stream)) == -1) {
962                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
963                                       _("DATA send timed out: message termination: "
964                                         "%s: mail not sent"),
965                                       g_strerror (errno));
966                 
967                 camel_object_unref (CAMEL_OBJECT (filtered_stream));
968                 
969                 return FALSE;
970         }
971         
972         camel_stream_flush (CAMEL_STREAM (filtered_stream));
973         camel_object_unref (CAMEL_OBJECT (filtered_stream));
974         
975         /* terminate the message body */
976         
977         d(fprintf (stderr, "sending : \\r\\n.\\r\\n\n"));
978         
979         if (camel_stream_write (transport->ostream, "\r\n.\r\n", 5) == -1) {
980                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
981                                       _("DATA send timed out: message termination: "
982                                         "%s: mail not sent"),
983                                       g_strerror (errno));
984                 return FALSE;
985         }
986         
987         do {
988                 /* Check for "250 Sender OK..." */
989                 g_free (respbuf);
990                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
991                 
992                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
993                 
994                 if (!respbuf || strncmp (respbuf, "250", 3)) {
995                         int error;
996                         
997                         error = respbuf ? atoi (respbuf) : 0;
998                         g_free (respbuf);
999                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1000                                               _("DATA response error: message termination: "
1001                                                 "%s: mail not sent"),
1002                                               get_smtp_error_string (error));
1003                         return FALSE;
1004                 }
1005         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1006         g_free (respbuf);
1007         
1008         return TRUE;
1009 }
1010
1011 static gboolean
1012 smtp_rset (CamelSmtpTransport *transport, CamelException *ex)
1013 {
1014         /* we are going to reset the smtp server (just to be nice) */
1015         gchar *cmdbuf, *respbuf = NULL;
1016         
1017         cmdbuf = g_strdup ("RSET\r\n");
1018         
1019         d(fprintf (stderr, "sending : %s", cmdbuf));
1020         
1021         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1022                 g_free (cmdbuf);
1023                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1024                                       _("RSET request timed out: %s"),
1025                                       g_strerror (errno));
1026                 return FALSE;
1027         }
1028         g_free (cmdbuf);
1029         
1030         do {
1031                 /* Check for "250" */
1032                 g_free (respbuf);
1033                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1034                 
1035                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1036                 
1037                 if (!respbuf || strncmp (respbuf, "250", 3)) {
1038                         int error;
1039                         
1040                         error = respbuf ? atoi (respbuf) : 0;
1041                         g_free (respbuf);
1042                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1043                                               _("RSET response error: %s"),
1044                                               get_smtp_error_string (error));
1045                         return FALSE;
1046                 }
1047         } while (*(respbuf+3) == '-'); /* if we got "250-" then loop again */
1048         g_free (respbuf);
1049         
1050         return TRUE;
1051 }
1052
1053 static gboolean
1054 smtp_quit (CamelSmtpTransport *transport, CamelException *ex)
1055 {
1056         /* we are going to reset the smtp server (just to be nice) */
1057         gchar *cmdbuf, *respbuf = NULL;
1058         
1059         cmdbuf = g_strdup ("QUIT\r\n");
1060         
1061         d(fprintf (stderr, "sending : %s", cmdbuf));
1062         
1063         if (camel_stream_write (transport->ostream, cmdbuf, strlen (cmdbuf)) == -1) {
1064                 g_free (cmdbuf);
1065                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1066                                       _("QUIT request timed out: %s: non-fatal"),
1067                                       g_strerror (errno));
1068                 return FALSE;
1069         }
1070         g_free (cmdbuf);
1071         
1072         do {
1073                 /* Check for "221" */
1074                 g_free (respbuf);
1075                 respbuf = camel_stream_buffer_read_line (CAMEL_STREAM_BUFFER (transport->istream));
1076                 
1077                 d(fprintf (stderr, "received: %s\n", respbuf ? respbuf : "(null)"));
1078                 
1079                 if (!respbuf || strncmp (respbuf, "221", 3)) {
1080                         int error;
1081                         
1082                         error = respbuf ? atoi (respbuf) : 0;
1083                         g_free (respbuf);
1084                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1085                                               _("QUIT response error: %s: non-fatal"),
1086                                               get_smtp_error_string (error));
1087                         return FALSE;
1088                 }
1089         } while (*(respbuf+3) == '-'); /* if we got "221-" then loop again */
1090         g_free (respbuf);
1091         
1092         return TRUE;
1093 }