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