diff src/smtp_out.c @ 0:08114f7dcc23 0.2.21

this is masqmail-0.2.21 from oliver kurth
author meillo@marmaro.de
date Fri, 26 Sep 2008 17:05:23 +0200
parents
children 26e34ae9a3e3
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/smtp_out.c	Fri Sep 26 17:05:23 2008 +0200
@@ -0,0 +1,918 @@
+/* smtp_out.c, Copyright (C) 1999-2001 Oliver Kurth,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ send bugs to: kurth@innominate.de
+*/
+
+/*
+  I always forget these rfc numbers:
+  RFC 821  (SMTP)
+  RFC 1869 (ESMTP)
+  RFC 1870 (ESMTP SIZE)
+  RFC 2197 (ESMTP PIPELINE)
+  RFC 2554 (ESMTP AUTH)
+*/
+
+#include "masqmail.h"
+#include "smtp_out.h"
+#include "readsock.h"
+
+#ifdef ENABLE_AUTH
+
+#ifdef USE_LIB_CRYPTO
+#include <openssl/hmac.h>
+#include <openssl/md5.h>
+#include <openssl/evp.h>
+#else
+#include "md5/global.h"
+#include "md5/md5.h"
+#include "md5/hmac_md5.h"
+#endif
+
+#include "base64/base64.h"
+#endif
+
+void destroy_smtpbase(smtp_base *psb)
+{
+  fclose(psb->in);
+  fclose(psb->out);
+
+  close(psb->sock);
+
+  if(psb->helo_name) g_free(psb->helo_name);
+  if(psb->buffer) g_free(psb->buffer);
+  if(psb->auth_names) g_strfreev(psb->auth_names);
+
+  if(psb->auth_name) g_free(psb->auth_name);
+  if(psb->auth_login) g_free(psb->auth_login);
+  if(psb->auth_secret) g_free(psb->auth_secret);
+}
+
+gchar *set_heloname(smtp_base *psb, gchar *default_name, gboolean do_correct)
+{
+  struct sockaddr_in sname;
+  int len = sizeof(struct sockaddr_in);
+  struct hostent *host_entry;
+
+  if(do_correct){
+    getsockname(psb->sock, (struct sockaddr *)(&sname), &len);
+    DEBUG(5) debugf("socket: name.sin_addr = %s\n", inet_ntoa(sname.sin_addr));
+    host_entry =
+      gethostbyaddr((const char *)&(sname.sin_addr),
+		    sizeof(sname.sin_addr), AF_INET);
+    if(host_entry){
+      psb->helo_name = g_strdup(host_entry->h_name);
+    }else{
+      /* we failed to look up our own name. Instead of giving our local hostname,
+	 we may give our IP number to show the server that we are at least
+	 willing to be honest. For the really picky ones.*/
+      DEBUG(5) debugf("failed to look up own host name.\n");
+      psb->helo_name = g_strdup_printf("[%s]", inet_ntoa(sname.sin_addr));
+    }
+    DEBUG(5) debugf("helo_name = %s\n", psb->helo_name);
+  }
+  if(psb->helo_name == NULL){
+    psb->helo_name = g_strdup(default_name);
+  }
+  return psb->helo_name;
+} 
+
+#ifdef ENABLE_AUTH
+
+gboolean set_auth(smtp_base *psb, gchar *name, gchar *login, gchar *secret)
+{
+  if((strcasecmp(name, "CRAM-MD5") == 0) ||
+     (strcasecmp(name, "LOGIN") == 0)) {
+    psb->auth_name = g_strdup(name);
+    psb->auth_login = g_strdup(login);
+    psb->auth_secret = g_strdup(secret);
+    
+    return TRUE;
+  }
+  return FALSE;
+}
+
+#endif
+
+static
+smtp_base *create_smtpbase(gint sock)
+{
+  gint dup_sock;
+
+  smtp_base *psb = (smtp_base *)g_malloc(sizeof(smtp_base));
+
+  psb->sock = sock;
+
+  psb->use_esmtp = FALSE;
+  psb->use_size = FALSE;
+  psb->use_pipelining = FALSE;
+  psb->use_auth = FALSE;
+
+  psb->max_size = 0;
+  psb->auth_names = NULL;
+
+  psb->buffer = (gchar *)g_malloc(SMTP_BUF_LEN);
+
+  dup_sock = dup(sock);
+  psb->out = fdopen(sock, "w");
+  psb->in = fdopen(dup_sock, "r");
+
+  psb->error = smtp_ok;
+
+  psb->helo_name = NULL;
+  
+  psb->auth_name = psb->auth_login = psb->auth_secret = NULL;
+
+  return psb;
+}
+
+static
+gboolean read_response(smtp_base *psb, int timeout)
+{
+  gint buf_pos = 0;
+  gchar code[5];
+  gint i, len;
+
+  do{
+    len = read_sockline(psb->in, &(psb->buffer[buf_pos]),
+			SMTP_BUF_LEN - buf_pos, timeout, READSOCKL_CHUG);
+    if(len == -3){
+      psb->error = smtp_timeout;
+      return FALSE;
+    }
+    else if(len == -2){
+      psb->error = smtp_syntax;
+      return FALSE;
+    }
+    else if(len == -1){
+      psb->error = smtp_eof;
+      return FALSE;
+    }
+    for(i = 0; i < 4; i++)
+      code[i] = psb->buffer[buf_pos+i];
+    code[i] = 0;
+    psb->last_code = atoi(code);
+
+    buf_pos += len;
+
+  }while(code[3] == '-');
+
+  return TRUE;
+}
+
+static
+gboolean check_response(smtp_base *psb, gboolean after_data)
+{
+  char c = psb->buffer[0];
+
+  if(((c == '2') && !after_data) || ((c == '3') && after_data)){
+    psb->error = smtp_ok;
+    DEBUG(6) debugf("response OK:'%s' after_date = %d\n", psb->buffer, (int)after_data);
+    return TRUE;
+  }else{
+    if(c == '4')
+      psb->error = smtp_trylater;
+    else if(c == '5')
+      psb->error = smtp_fail;
+    else
+      psb->error = smtp_syntax;
+    DEBUG(6) debugf("response failure:'%s' after_date = %d\n", psb->buffer, (int)after_data);
+    return FALSE;
+  }
+}
+
+static
+gboolean check_init_response(smtp_base *psb)
+{
+  if(check_response(psb, FALSE)){
+    psb->use_esmtp = (strstr(psb->buffer, "ESMTP") != NULL);
+
+    DEBUG(4) debugf(psb->use_esmtp ? "uses esmtp\n" : "no esmtp\n");
+
+    return TRUE;
+  }
+  return FALSE;
+}
+
+static
+gchar *get_response_arg(gchar *response)
+{
+  gchar buf[SMTP_BUF_LEN];
+  gchar *p = response, *q = buf;
+
+  while(*p && (*p != '\n') && isspace(*p)) p++;
+  if(*p && (*p != '\n')){
+    while(*p && (*p != '\n') && (*p != '\r') && (q < buf+SMTP_BUF_LEN-1)) *(q++) = *(p++);
+    *q = 0;
+    return g_strdup(buf);
+  }
+  return NULL;
+}
+
+static
+gboolean check_helo_response(smtp_base *psb)
+{
+  gchar *ptr = psb->buffer;
+
+  if(!check_response(psb, FALSE))
+    return FALSE;
+
+  while(*ptr){
+    if(strncasecmp(&(ptr[4]), "SIZE", 4) == 0){
+      gchar *arg;
+      psb->use_size = TRUE;
+      arg = get_response_arg(&(ptr[8]));
+      if(arg){
+	psb->max_size = atoi(arg);
+	g_free(arg);
+      }
+    }
+
+    if(strncasecmp(&(ptr[4]), "PIPELINING", 10) == 0)
+      psb->use_pipelining = TRUE;
+
+    if(strncasecmp(&(ptr[4]), "AUTH", 4) == 0){
+      if((ptr[8] == ' ') || (ptr[8] == '=') || (ptr[8] == '\t')){ /* not sure about '\t' */
+	gchar *arg;
+	psb->use_auth = TRUE;
+	arg = get_response_arg(&(ptr[9])); /* after several years I finally learnt to count */
+	if(arg){
+	  psb->auth_names = g_strsplit(arg, " " , 0);
+	  g_free(arg);
+	  
+	  DEBUG(4){
+	    gint i = 0;
+	    while(psb->auth_names[i]){
+	      debugf("offered AUTH %s\n", psb->auth_names[i]);
+	      i++;
+	    }
+	  }
+	}
+      }
+    }
+
+    while(*ptr != '\n') ptr++;
+    ptr++;
+  }
+
+  DEBUG(4){
+    debugf(psb->use_size ? "uses SIZE\n" : "no size\n");
+    debugf(psb->use_pipelining ? "uses PIPELINING\n" : "no pipelining\n");
+    debugf(psb->use_auth ? "uses AUTH\n" : "no auth\n");
+  }
+
+  return TRUE;
+}
+
+static
+gboolean smtp_helo(smtp_base *psb, gchar *helo)
+{
+  while(TRUE){
+    if(psb->use_esmtp){
+      fprintf(psb->out, "EHLO %s\r\n", helo); fflush(psb->out);
+
+      DEBUG(4) debugf("EHLO %s\r\n", helo);
+
+    }else{
+      fprintf(psb->out, "HELO %s\r\n", helo); fflush(psb->out);
+
+      DEBUG(4) debugf("HELO %s\r\n", helo);
+
+    }
+    
+    if(!read_response(psb, SMTP_CMD_TIMEOUT))
+      return FALSE;
+
+    if(check_helo_response(psb))
+      return TRUE;
+    else{
+      if(psb->error == smtp_fail){
+	if(psb->use_esmtp){
+	  /* our guess that server understands EHLO was wrong,
+	     try again with HELO
+	  */
+	  psb->use_esmtp = FALSE;
+	}else{
+	  /* what sort of server ist THAT ?!
+	     give up...
+	  */
+	  return FALSE;
+	}
+      }else
+	return FALSE;
+    }
+  }
+}
+
+static
+void smtp_cmd_mailfrom(smtp_base *psb, address *return_path, guint size)
+{
+  if(psb->use_size){
+    fprintf(psb->out, "MAIL FROM:%s SIZE=%d\r\n",
+	    addr_string(return_path), size);
+    fflush(psb->out);
+
+    DEBUG(4) debugf("MAIL FROM:%s SIZE=%d\r\n",
+		    addr_string(return_path), size);
+
+  }else{
+    fprintf(psb->out, "MAIL FROM:%s\r\n", addr_string(return_path));
+    fflush(psb->out);
+
+    DEBUG(4) debugf("MAIL FROM:%s\r\n", addr_string(return_path));
+  }
+}
+
+static
+void smtp_cmd_rcptto(smtp_base *psb, address *rcpt)
+{
+  fprintf(psb->out, "RCPT TO:%s\r\n", addr_string(rcpt));
+  fflush(psb->out);
+  DEBUG(4) debugf("RCPT TO:%s\n", addr_string(rcpt));
+}
+
+static
+void send_data_line(smtp_base *psb, gchar *data)
+{
+  /* According to RFC 821 each line should be terminated with CRLF.
+     Since a dot on a line itself marks the end of data, each line
+     beginning with a dot is prepended with another dot.
+  */
+  gchar *ptr;
+  gboolean new_line = TRUE; /* previous versions assumed that each item was
+			       exactly one line. This is no longer the case */
+
+  ptr = data;
+  while(*ptr){
+    int c = (int)(*ptr);
+    if(c == '.')
+      if(new_line)
+	putc('.', psb->out);
+    if(c == '\n'){
+      putc('\r', psb->out);
+      putc('\n', psb->out);
+      new_line = TRUE;
+    }else{
+      putc(c, psb->out);
+      new_line = FALSE;
+    }
+    ptr++;
+  }
+}
+
+static
+void send_header(smtp_base *psb, GList *hdr_list)
+{
+  GList *node;
+  gint num_hdrs = 0;
+
+  /* header */
+  if(hdr_list){
+    foreach(hdr_list, node){
+      if(node->data){
+	header *hdr = (header *)(node->data);
+	if(hdr->header){
+	  send_data_line(psb, hdr->header);
+	  num_hdrs++;
+	}
+      }
+    }
+  }
+
+  /* empty line separating headers from data: */
+  putc('\r', psb->out);
+  putc('\n', psb->out);
+
+  DEBUG(4) debugf("sent %d headers\n", num_hdrs);
+}
+
+static
+void send_data(smtp_base *psb, message *msg)
+{
+  GList *node;
+  gint num_lines = 0;
+
+  /* data */
+  if(msg->data_list){
+    for(node = g_list_first(msg->data_list); node; node = g_list_next(node)){
+      if(node->data){
+	send_data_line(psb, node->data);
+	num_lines++;
+      }
+    }
+  }
+
+  DEBUG(4) debugf("sent %d lines of data\n", num_lines);
+
+  fprintf(psb->out, ".\r\n");
+  fflush(psb->out);
+}
+
+void smtp_out_mark_rcpts(smtp_base *psb, GList *rcpt_list)
+{
+  GList *rcpt_node;
+  for(rcpt_node = g_list_first(rcpt_list);
+      rcpt_node;
+      rcpt_node = g_list_next(rcpt_node)){
+    address *rcpt = (address *)(rcpt_node->data);
+
+    addr_unmark_delivered(rcpt);
+
+    if((psb->error == smtp_trylater) || (psb->error == smtp_timeout) ||
+       (psb->error == smtp_eof)){
+      addr_mark_defered(rcpt);
+    }else{
+      addr_mark_failed(rcpt);
+    }
+  }
+}
+
+void smtp_out_log_failure(smtp_base *psb, message *msg)
+{
+  gchar *err_str;
+
+  if(psb->error == smtp_timeout)
+    err_str = g_strdup("connection timed out.");
+  else if(psb->error == smtp_eof)
+    err_str = g_strdup("connection terminated prematurely.");
+  else if(psb->error == smtp_syntax)
+    err_str = g_strdup_printf("got unexpected response: %s", psb->buffer);
+  else if(psb->error == smtp_cancel)
+    err_str = g_strdup("delivery was canceled.\n");
+  else
+    /* error message should still be in the buffer */
+    err_str = g_strdup_printf("failed: %s\n", psb->buffer);
+
+  if(msg == NULL)
+    logwrite(LOG_NOTICE, "host=%s %s\n",
+	     psb->remote_host, err_str);
+  else
+    logwrite(LOG_NOTICE, "%s == host=%s %s\n",
+	     msg->uid, psb->remote_host, err_str);
+
+  g_free(err_str);
+}
+
+smtp_base *smtp_out_open(gchar *host, gint port, GList *resolve_list)
+{
+  smtp_base *psb;
+  gint sock;
+  mxip_addr *addr;
+
+  DEBUG(5) debugf("smtp_out_open entered, host = %s\n", host);
+
+  if((addr = connect_resolvelist(&sock, host, port, resolve_list))){
+    /* create structure to hold status data: */
+    psb = create_smtpbase(sock);
+    psb->remote_host = addr->name;
+
+    DEBUG(5){
+      struct sockaddr_in name;
+      int len = sizeof(struct sockaddr);
+      getsockname(sock, (struct sockaddr *)(&name), &len);
+      debugf("socket: name.sin_addr = %s\n", inet_ntoa(name.sin_addr));
+    }
+    return psb;
+  }else{
+    DEBUG(5) debugf("connect_resolvelist failed: %s %s\n", strerror(errno), hstrerror(h_errno));
+  }
+
+  return NULL;
+}
+
+smtp_base *smtp_out_open_child(gchar *cmd)
+{
+  smtp_base *psb;
+  gint sock;
+
+  DEBUG(5) debugf("smtp_out_open_child entered, cmd = %s\n", cmd);
+
+  sock = child(cmd);
+
+  if(sock > 0){
+    psb = create_smtpbase(sock);
+    psb->remote_host = NULL;
+
+    return psb;
+  }
+
+  return NULL;
+}
+
+gboolean smtp_out_rset(smtp_base *psb)
+{
+  gboolean ok;
+  
+  fprintf(psb->out, "RSET\r\n"); fflush(psb->out);
+  DEBUG(4) debugf("RSET\n");
+
+  if((ok = read_response(psb, SMTP_CMD_TIMEOUT)))
+    if(check_response(psb, FALSE))
+      return TRUE;
+
+  smtp_out_log_failure(psb, NULL);
+
+  return FALSE;
+}
+
+#ifdef ENABLE_AUTH
+
+static
+gboolean smtp_out_auth_cram_md5(smtp_base *psb)
+{
+  gboolean ok = FALSE;
+
+  fprintf(psb->out, "AUTH CRAM-MD5\r\n"); fflush(psb->out);
+  DEBUG(4) debugf("AUTH CRAM-MD5\n");
+  if((ok = read_response(psb, SMTP_CMD_TIMEOUT))){
+    if((ok = check_response(psb, TRUE))){
+      gchar *chall64 = get_response_arg(&(psb->buffer[4]));
+      gint chall_size;
+      gchar *chall = base64_decode(chall64, &chall_size);
+      guchar digest[16], *reply64, *reply;
+      gchar digest_string[33];
+      gint i;
+#ifdef USE_LIB_CRYPTO
+      unsigned int digest_len;
+#endif
+      
+      DEBUG(5) debugf("encoded challenge = %s\n", chall64);
+      DEBUG(5) debugf("decoded challenge = %s, size = %d\n", chall, chall_size);
+      
+      DEBUG(5) debugf("secret = %s\n", psb->auth_secret);
+      
+#ifdef USE_LIB_CRYPTO
+      HMAC(EVP_md5(), psb->auth_secret, strlen(psb->auth_secret), chall, chall_size, digest, &digest_len);
+#else
+      hmac_md5(chall, chall_size, psb->auth_secret, strlen(psb->auth_secret), digest);
+#endif
+      
+      for(i = 0; i < 16; i++)
+	sprintf(&(digest_string[i+i]), "%02x", (unsigned int)(digest[i]));
+      digest_string[32] = 0;
+      
+      DEBUG(5) debugf("digest = %s\n", digest_string);
+      
+      reply = g_strdup_printf("%s %s", psb->auth_login, digest_string);
+      DEBUG(5) debugf("unencoded reply = %s\n", reply);
+      
+      reply64 = base64_encode(reply, strlen(reply));
+      DEBUG(5) debugf("encoded reply = %s\n", reply64);
+          
+      fprintf(psb->out, "%s\r\n", reply64); fflush(psb->out);
+      DEBUG(4) debugf("%s\n", reply64);
+          
+      if((ok = read_response(psb, SMTP_CMD_TIMEOUT)))
+	ok = check_response(psb, FALSE);
+          
+      g_free(reply64);
+      g_free(reply);
+      g_free(chall);
+      g_free(chall64);
+    }
+  }
+  return ok;
+}
+
+static
+gboolean smtp_out_auth_login(smtp_base *psb)
+{
+  gboolean ok = FALSE;
+  fprintf(psb->out, "AUTH LOGIN\r\n"); fflush(psb->out);
+  if((ok = read_response(psb, SMTP_CMD_TIMEOUT))){
+    if((ok = check_response(psb, TRUE))){
+      gchar *resp64;
+      guchar *resp;
+      gint resp_size;
+      gchar *reply64;
+      
+      resp64 = get_response_arg(&(psb->buffer[4]));
+      DEBUG(5) debugf("encoded response = %s\n", resp64);
+      resp = base64_decode(resp64, &resp_size);
+      g_free(resp64);
+      DEBUG(5) debugf("decoded response = %s, size = %d\n", 
+                                        resp, resp_size);
+      g_free(resp);
+      reply64 = base64_encode(psb->auth_login,
+			      strlen(psb->auth_login));
+      fprintf(psb->out, "%s\r\n", reply64); fflush(psb->out);
+      g_free(reply64);
+      if((ok = read_response(psb, SMTP_CMD_TIMEOUT))) {
+	if ((ok = check_response(psb, TRUE))) {
+	  resp64 = get_response_arg(&(psb->buffer[4]));
+	  DEBUG(5) debugf("encoded response = %s\n", resp64);
+	  resp = base64_decode(resp64, &resp_size);
+	  g_free(resp64);
+	  DEBUG(5) debugf("decoded response = %s, size = %d\n", 
+			  resp, resp_size);
+	  g_free(resp);
+	  reply64 = base64_encode(psb->auth_secret,
+				  strlen(psb->auth_secret));
+	  fprintf(psb->out, "%s\r\n", reply64); fflush(psb->out);
+	  g_free(reply64);
+	  if((ok = read_response(psb, SMTP_CMD_TIMEOUT)))
+	    ok = check_response(psb, FALSE);
+	}
+      }          
+    }
+  }
+  return ok;
+}
+
+gboolean smtp_out_auth(smtp_base *psb)
+{
+  gboolean ok = FALSE;
+  gint i = 0;
+  while(psb->auth_names[i]){
+    if(strcasecmp(psb->auth_names[i], psb->auth_name) == 0)
+      break;
+    i++;
+  }
+  if(psb->auth_names[i]){
+    if(strcasecmp(psb->auth_name, "cram-md5") == 0){
+      smtp_out_auth_cram_md5(psb);
+    }else if(strcasecmp(psb->auth_name, "login") == 0){
+      smtp_out_auth_login(psb);
+    }else{
+      logwrite(LOG_ERR, "auth method %s not supported\n",  psb->auth_name);
+    }
+  }else{
+    logwrite(LOG_ERR, "no auth method %s found.\n", psb->auth_name);
+  }
+  return ok;
+}
+
+#endif
+
+gboolean smtp_out_init(smtp_base *psb)
+{
+  gboolean ok;
+
+  if((ok = read_response(psb, SMTP_INITIAL_TIMEOUT))){
+    if((ok = check_init_response(psb))){
+ 
+      if((ok = smtp_helo(psb, psb->helo_name))){
+#ifdef ENABLE_AUTH
+	if(psb->auth_name && psb->use_auth){
+	  /* we completely disregard the response of server here. If
+             authentication fails, the server will complain later
+             anyway. I know, this is not polite... */
+	  smtp_out_auth(psb);
+	}
+#endif
+      }
+    }
+  }
+  if(!ok)
+    smtp_out_log_failure(psb, NULL);
+  return ok;
+}
+
+gint smtp_out_msg(smtp_base *psb,
+		  message *msg, address *return_path, GList *rcpt_list,
+		  GList *hdr_list)
+{
+  gint i, size;
+  gboolean ok = TRUE;
+  int rcpt_cnt;
+  int rcpt_accept = 0;
+
+  DEBUG(5) debugf("smtp_out_msg entered\n");
+
+  /* defaults: */
+  if(return_path == NULL)
+    return_path = msg->return_path;
+  if(hdr_list == NULL)
+    hdr_list = msg->hdr_list;
+  if(rcpt_list == NULL)
+    rcpt_list = msg->rcpt_list;
+  rcpt_cnt = g_list_length(rcpt_list);
+
+  size = msg_calc_size(msg, TRUE);
+
+  /* respect maximum size given by server: */
+  if((psb->max_size > 0) && (size > psb->max_size)){
+    logwrite(LOG_WARNING,
+	     "%s == host=%s message size (%d) > fixed maximum message size of server (%d)",
+	     msg->uid, psb->remote_host, size, psb->max_size);
+    psb->error = smtp_cancel;
+    ok = FALSE;
+  }
+
+  if(ok){
+    smtp_cmd_mailfrom(psb, return_path,
+		      psb->use_size ? 
+		      size + SMTP_SIZE_ADD : 0);
+    
+    if(!psb->use_pipelining){
+      if((ok = read_response(psb, SMTP_CMD_TIMEOUT)))
+	ok = check_response(psb, FALSE);
+    }
+  }
+  if(ok){
+    GList *rcpt_node;
+    rcpt_accept = 0;
+
+    for(rcpt_node = g_list_first(rcpt_list);
+	rcpt_node != NULL;
+	rcpt_node = g_list_next(rcpt_node)){
+      address *rcpt = (address *)(rcpt_node->data);
+      smtp_cmd_rcptto(psb, rcpt);
+      if(!psb->use_pipelining){
+	if((ok = read_response(psb, SMTP_CMD_TIMEOUT)))
+	  if(check_response(psb, FALSE)){
+	    rcpt_accept++;
+	    addr_mark_delivered(rcpt);
+	  }
+	  else{
+	    /* if server returned an error for one recp. we
+	       may still try the others. But if it is a timeout, eof
+	       or unexpected response, it is more serious and we should
+	       give up. */
+	    if((psb->error != smtp_trylater) &&
+	       (psb->error != smtp_fail)){
+	      ok = FALSE;
+	      break;
+	    }else{
+	      logwrite(LOG_NOTICE, "%s == %s host=%s failed: %s",
+		       msg->uid, addr_string(rcpt),
+		       psb->remote_host, psb->buffer);
+	      if(psb->error == smtp_trylater){
+		addr_mark_defered(rcpt);
+	      }else{
+		addr_mark_failed(rcpt);
+	      }
+	    }
+	  }
+	else
+	  break;
+      }
+    }
+
+    /* There is no point in going on if no recp.s were accpted.
+       But we can check that at this point only if not pipelining: */
+    ok = (ok && (psb->use_pipelining || (rcpt_accept > 0)));
+    if(ok){
+
+      fprintf(psb->out, "DATA\r\n"); fflush(psb->out);
+
+      DEBUG(4) debugf("DATA\r\n");
+	
+      if(psb->use_pipelining){
+	/* the first pl'ed command was MAIL FROM
+	   the last was DATA, whose response can be handled by the 'normal' code
+	   all in between were RCPT TO:
+	*/
+	/* response to MAIL FROM: */
+	if((ok = read_response(psb, SMTP_CMD_TIMEOUT))){
+	  if((ok = check_response(psb, FALSE))){
+
+	    /* response(s) to RCPT TO:
+	       this is very similar to the sequence above for no pipeline
+	    */
+	    for(i = 0; i < rcpt_cnt; i++){
+	      if((ok = read_response(psb, SMTP_CMD_TIMEOUT))){
+		address *rcpt = g_list_nth_data(rcpt_list, i);
+		if(check_response(psb, FALSE)){
+		  rcpt_accept++;
+		  addr_mark_delivered(rcpt);
+		}
+		else{
+		  /* if server returned an error 4xx or 5xx for one recp. we
+		     may still try the others. But if it is a timeout, eof
+		     or unexpected response, it is more serious and we
+		     should give up. */
+		  if((psb->error != smtp_trylater) &&
+		     (psb->error != smtp_fail)){
+		    ok = FALSE;
+		    break;
+		  }else{
+		    logwrite(LOG_NOTICE, "%s == %s host=%s failed: %s",
+			     msg->uid, addr_string(rcpt),
+			     psb->remote_host, psb->buffer);
+		    if(psb->error == smtp_trylater){
+		      addr_mark_defered(rcpt);
+		    }else{
+		      addr_mark_failed(rcpt);
+		    }
+		  }
+		}
+	      }else{
+		DEBUG(5) debugf("check_response failed after RCPT TO\n");
+		break;
+	      }
+	    }
+	    if(rcpt_accept == 0)
+	      ok = FALSE;
+	  }else{
+	    DEBUG(5) debugf("check_response failed after MAIL FROM\n");
+	  }
+	}else{
+	  DEBUG(5) debugf("read_response failed after MAIL FROM\n");
+	}
+      } /* if(psb->use_pipelining) */
+
+      /* response to the DATA cmd */
+      if(ok){
+	if(read_response(psb, SMTP_DATA_TIMEOUT)){
+	  if(check_response(psb, TRUE)){
+	    send_header(psb, hdr_list);
+	    send_data(psb, msg);
+	      
+	    if(read_response(psb, SMTP_FINAL_TIMEOUT))
+	      ok = check_response(psb, FALSE);
+	  }
+	}
+      }
+    }
+  }
+
+  DEBUG(5){
+    debugf("psb->error = %d\n", psb->error);
+    debugf("ok = %d\n", ok);
+    debugf("rcpt_accept = %d\n", rcpt_accept);
+  }
+
+  if(psb->error == smtp_ok){
+    GList *rcpt_node;
+    for(rcpt_node = g_list_first(rcpt_list);
+	rcpt_node;
+	rcpt_node = g_list_next(rcpt_node)){
+      address *rcpt = (address *)(rcpt_node->data);
+      if(addr_is_delivered(rcpt))
+	logwrite(LOG_NOTICE, "%s => %s host=%s with %s\n",
+		 msg->uid, addr_string(rcpt), psb->remote_host,
+		 psb->use_esmtp ? "esmtp" : "smtp");
+    }
+  }else{
+    /* if something went wrong,
+       we have to unmark the rcpts prematurely marked as delivered
+       and mark the status */
+    smtp_out_mark_rcpts(psb, rcpt_list);
+
+    /* log the failure: */
+    smtp_out_log_failure(psb, msg);
+  }
+  return rcpt_accept;
+}
+
+gboolean smtp_out_quit(smtp_base *psb)
+{
+  fprintf(psb->out, "QUIT\r\n"); fflush(psb->out);
+  
+  DEBUG(4) debugf("QUIT\n");
+
+  signal(SIGALRM, SIG_DFL);
+
+  return TRUE;
+}
+  
+gint smtp_deliver(gchar *host, gint port, GList *resolve_list,
+		  message *msg,
+		  address *return_path,
+		  GList *rcpt_list)
+{
+  smtp_base *psb;
+  smtp_error err;
+
+  DEBUG(5) debugf("smtp_deliver entered\n");
+
+  if(return_path == NULL)
+    return_path = msg->return_path;
+
+  if((psb = smtp_out_open(host, port, resolve_list))){
+    set_heloname(psb, return_path->domain, TRUE);
+    /* initiate connection, send message and quit: */
+    if(smtp_out_init(psb)){
+      smtp_out_msg(psb, msg, return_path, rcpt_list, NULL);
+      if(psb->error == smtp_ok ||
+	 (psb->error == smtp_fail) ||
+	 (psb->error == smtp_trylater) ||
+	 (psb->error == smtp_syntax) ||
+	 (psb->error == smtp_cancel))
+	
+	smtp_out_quit(psb);
+    }
+    
+    err = psb->error;
+    destroy_smtpbase(psb);
+    
+    return err;
+  }
+  return -1;
+}