Mercurial > masqmail
view src/smtp_out.c @ 158:91b8b44ba619
documented that online_pipe must contain an absolute path
author | meillo@marmaro.de |
---|---|
date | Thu, 08 Jul 2010 10:35:42 +0200 |
parents | 1e2fd87d58ea |
children | a39c8ee61185 |
line wrap: on
line source
/* 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; debugf("in check_helo_response()\n"); while (psb->auth_names[i]) { debugf(" offered AUTH %s\n", psb->auth_names[i]); i++; } } } } } while (*ptr != '\n') ptr++; ptr++; } DEBUG(4) { debugf(" %s\n", psb->use_size ? "uses SIZE" : "no size"); debugf(" %s\n", psb->use_pipelining ? "uses PIPELINING" : "no pipelining"); debugf(" %s\n", psb->use_auth ? "uses AUTH" : "no auth"); } 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 == '.' && new_line) { /* dot-stuffing */ putc('.', psb->out); } if (c == '\n') { /* CRLF line terminators */ 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("smtp_out_auth_cram_md5():\n"); 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(" reply64 = %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; DEBUG(5) debugf("smtp_out_auth_login():\n"); 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) { /* pretend the message is a bit larger, just in case the size calculation is buggy */ 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\n", 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\n", 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("smtp_out_msg():\n"); 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; }