/* ** SMTP routines for mailsend - a simple mail sender via SMTP ** ** Limitations and Comments: ** SMTP RF[C] RFC-821 ** Also look at: TCP/IP Illustraged Vol 1 by Richard Stevens ** ** Written mainly for NT, Unix has this kind of tools in hundreds. ** ** Development History: ** who when why ** muquit@muquit.com Mar-26-2001 first cut */ #include "mailsend.h" static char buf[BUFSIZ]; static int bufsz = sizeof(buf) - 1; static int break_out=0; static char smtp_line[BUFSIZ]; static char smtp_errbuf[BUFSIZ]; static int smtp_code; static int smtp_sep; static int s_esmtp=0; #ifdef WINNT static BOOL WINAPI CntrlHandler(DWORD CtrlEvent); #else #define closesocket close #endif /* WINNT */ void smtpDisconnect(void) { msock_close(); } /* connect to SMTP server and returns the socket fd */ static SOCKET smtpConnect(char *smtp_server,int port) { SOCKET sfd; if (g_use_protocol == MSOCK_USE_IPV4) { showVerbose("Forcing to use IPv4 address of SMTP server\n"); } else if (g_use_protocol == MSOCK_USE_IPV6) { showVerbose("Forcing to use IPv6 address of SMTP server\n"); } else { showVerbose("Will detect IPv4 or IPv6 automatically\n"); } sfd=clientSocket(g_use_protocol, smtp_server,port, g_connect_timeout); if (sfd == INVALID_SOCKET) { errorMsg("Could not connect to SMTP server \"%s\" at port %d", smtp_server,port); return (INVALID_SOCKET); } /* set the socket to msock lib's static place, not thread safe*/ msock_set_socket(sfd); return (sfd); } int read_smtp_multi_lines(void) { int rc, lcnt = 0; if (smtp_sep == A_DASH) { for (;;) { rc=read_smtp_line(); if (rc < 0) break; lcnt++; if (lcnt >= 100) { errorMsg("Too many lines from server\n"); rc=(-1); goto ExitProcessing; } if (smtp_sep != A_DASH) break; } } smtp_sep = A_SPACE; return(0); ExitProcessing: return(1); } /* * sfd socket * * return 0 on success, -1 on failure * * populates globals: smtp_code,smtp_line and smtp_errbuf on error * */ int read_smtp_line(void) { int rc=(-1), n; char tbuf[BUFSIZ], lbuf[BUFSIZ]; memset(smtp_line,0,sizeof(smtp_line)); memset(smtp_errbuf,0,sizeof(smtp_errbuf)); smtp_code=(-1); memset(lbuf,0,sizeof(lbuf)); /* read a line */ n=msock_gets(lbuf,sizeof(lbuf)-1, g_read_timeout); if (n < 3 ) { /*errorMsg("Error reading SMTP line, read %d bytes",n);*/ return(-1); } showVerbose("[S] %s\n",lbuf); if (n >= 5) { memset(tbuf,0,sizeof(tbuf)); memcpy(tbuf,lbuf,3); smtp_code=atoi(tbuf); smtp_sep=lbuf[3]; (void) snprintf(smtp_line,sizeof(smtp_line)-1,"%s",lbuf + 4); rc=0; /* (void) fprintf(stderr," Line: \"%s\"\n",lbuf); (void) fprintf(stderr," Code: \"%d\"\n",smtp_code); (void) fprintf(stderr," Sep: '%c'\n",smtp_sep); (void) fprintf(stderr," line: \"%s\"\n",smtp_line); */ } else { (void) snprintf(smtp_errbuf,sizeof(smtp_errbuf)-1,"%s",lbuf); } return(rc); } int smtp_start_tls(int sfd) { int rc=(-1); #ifdef HAVE_OPENSSL SSL *ssl=NULL; #endif /* HAVE_OPENSSL */ memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"%s\r\n","STARTTLS"); showVerbose("[C] %s",buf); msock_puts(buf); rc=read_smtp_line(); if (smtp_code != 220) { errorMsg("Unknown STARTTLS response code %d\n",smtp_code); return(-1); } #ifdef HAVE_OPENSSL ssl=msock_get_ssl(); if (ssl) { if (!SSL_set_fd(ssl,sfd)) { errorMsg("failed to set socket to SSL\n"); return(-1); } /* must set back to msock's static */ msock_set_ssl(ssl); rc=SSL_connect(ssl); if (rc < 1) { errorMsg("SSL connection failed\n"); ERR_print_errors_fp(stderr); return(-1); } print_cert_info(ssl); /* tell msock everything is ssl after that */ msock_turn_ssl_on(); rc=0; } else { errorMsg("Could not start STARTTLS, SSL not initialized properly"); rc=(-1); } #else errorMsg("Not Compiled with OpenSSL, will not try STARTTLS"); rc=(-1); #endif /*HAVE_OPENSSL */ return(rc); } /* SMTP: HELO */ static int say_helo(char *helo_domain) { int cnt=0, rc; (void) snprintf(buf,sizeof(buf)-1,"%s %s\r\n", s_esmtp ? "EHLO" : "HELO",helo_domain); showVerbose("[C] %s",buf); /* send */ msock_puts(buf); rc=read_smtp_line(); if (smtp_code != 250) { errorMsg("%s failed", s_esmtp ? "EHLO" : "HELO"); return(-1); } /* read all the capabilities if separator is - */ if (smtp_sep == A_DASH) { for (;;) { rc=read_smtp_line(); if (rc == 0) add_server_cap_to_list(smtp_line); cnt++; if (cnt >= 1000) break; if (rc < 0 || smtp_sep != A_DASH) break; } } smtp_sep = A_SPACE; return(rc); } /* SMTP: MAIL FROM */ static int smtp_MAIL_FROM(char *from) { memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"MAIL FROM: <%s>\r\n",from); showVerbose("[C] %s",buf); msock_puts(buf); read_smtp_line(); if (smtp_code != 250) { errorMsg("MAIL FROM failed: '%d %s'",smtp_code,smtp_line); return(-1); } return(0); } /* SMTP: quit */ static int smtp_QUIT(void) { showVerbose("[C] QUIT\r\n"); msock_puts("QUIT\r\n"); read_smtp_line(); /* ** google does not seem to write anything back in response to QUIT ** command. I'll ignore it anyway */ return(0); } /* SMTP: RSET */ /* aborts current mail transaction and cause both ends to reset */ static int smtp_RSET() { msock_puts("RSET\r\n"); return(read_smtp_line()); } /* SMTP: RCPT TO */ static int smtp_RCPT_TO(void) { Sll *l, *al; Address *a; char *x; int rc; al=getAddressList(); for (l=al; l; l=l->next) { a=(Address *) l->data; if (! a) return(-1); if (! a->address) return(-1); memset(buf,0,sizeof(buf)); x=getenv("NOTIFY_RCPT"); if (x != NULL) { /* MS Exchange has it */ showVerbose("NOTIFY_RCPT=%s\n",x); (void) snprintf(buf,sizeof(buf)-1,"RCPT TO: %s %s\r\n", a->address,x); } else { (void) snprintf(buf,sizeof(buf)-1,"RCPT TO: <%s>\r\n",a->address); } showVerbose("[C] %s",buf); msock_puts(buf); rc=read_smtp_line(); if (rc == 0) { if (smtp_code != 250) { errorMsg("RCPT TO: <%s> failed '%d:%s'\n", a->address,smtp_code,smtp_line); smtp_RSET(); return(-1); } } } return (0); } /* SMTP: DATA */ static int smtp_DATA(void) { int rc; msock_puts("DATA\r\n"); showVerbose("[C] DATA\r\n"); rc=read_smtp_line(); if (rc == 0) { if (smtp_code != 354) { errorMsg("DATA failed: '%d %s'\n",smtp_code,smtp_line); return(-1); } } return(0); } /* SMTP: EOM */ /* return 0 on success, -1 on failure */ int smtpEom(int sfd) { int rc; msock_puts("\r\n.\r\n"); showVerbose("\r\n[C] .\r\n"); /* ** Bug# 1 ** we want to see smtp code 250 now ** if mail is too big, it can mail with 552 message too large */ rc = read_smtp_line(); if (smtp_code != 250) { read_smtp_multi_lines(); errorMsg("Expected smtp code 250, got %d\n",smtp_code); rc = (-1); } return(rc); } void doCleanup(void) { smtpDisconnect(); } #ifdef WINNT /* ** Handle Ctrl+C */ static BOOL WINAPI CntrlHandler(DWORD CtrlEvent) { break_out=0; switch (CtrlEvent) { case CTRL_C_EVENT: { break_out=1; (void) fprintf(stderr,"\nNot sending mail. Exiting.......\n"); exit_error(); break; } } return (TRUE); } #endif /* WINNT */ int write_to_socket(char *str) { int n; if (str == NULL) { return (-1); } n = msock_puts(str); /*showVerbose("%d bytes: %s\n",n, str);*/ return(n); } /* ** return 0 on success */ int encode2base64andwrite2socket(const char *str) { FILE *tfp1 = NULL, *tfp2 = NULL; char mbuf[1000], oneline_tempfile1[MUTILS_PATH_MAX], oneline_tempfile2[MUTILS_PATH_MAX]; memset(oneline_tempfile1, 0, sizeof(oneline_tempfile1)); /* write the text to a tmp file */ tfp1 = mutils_get_tempfileFP(oneline_tempfile1, sizeof(oneline_tempfile1)-1); if (tfp1 == NULL) { errorMsg("%s (%d) - Could not open temp file1 for writing (%s)", MFL, ERR_STR); return (-1); } (void) fprintf(tfp1,"%s",str); (void) fclose(tfp1); /* open another tmp file to write the base64 of the first tmp file to */ memset(oneline_tempfile2, 0, sizeof(oneline_tempfile2)); tfp2 = mutils_get_tempfileFP(oneline_tempfile2, sizeof(oneline_tempfile2)-1); showVerbose("Oneline temp file2: * %s\n",oneline_tempfile2); if (tfp2 == NULL) { errorMsg("%s (%d) - Could not open temp file2 for writing (%s)", MFL, ERR_STR); return (-1); } tfp1 = fopen(oneline_tempfile1,"rb"); if (tfp1 == NULL) { errorMsg("%s (%d) - Could not open temp file for reading (%s)", MFL, ERR_STR); return(-1); } mutilsBase64Encode(tfp1,tfp2); (void) fclose(tfp1); (void) fclose(tfp2); /* open the file with base64 and write the content to socket */ tfp2 = fopen(oneline_tempfile2,"r"); if (tfp2 == NULL) { errorMsg("%s (%d) - Could not open temp file for reading (%s)", MFL, ERR_STR); return(-1); } while(fgets(mbuf, sizeof(mbuf)-1, tfp2)) { write_to_socket(mbuf); if (g_show_attachment_in_log) { showVerbose("[C] %s",mbuf); } } (void) fclose(tfp2); unlink(oneline_tempfile1); unlink(oneline_tempfile2); return(0); } /* ** send one line messages, each one is an inline attachment ** return 0 if mail is sent, -1 otherwise */ int process_oneline_messages(const char *boundary) { Attachment *a = NULL; Sll *l, *oneline_attachment_list; oneline_attachment_list = get_oneline_attachment_list(); if (oneline_attachment_list == NULL) { return(0); } print_oneline_attachment_list(); for (l = oneline_attachment_list; l; l = l->next) { a = (Attachment *) l->data; (void) snprintf(buf, bufsz, "\r\n--%s\r\n",boundary); write_to_socket(buf); if (strcmp(a->charset,"none") != 0) { (void) snprintf(buf, bufsz, "Content-Type: %s; charset=%s\r\n", a->mime_type, a->charset); } else { (void) snprintf(buf, bufsz, "Content-Type: %s\r\n",a->mime_type); } write_to_socket(buf); (void) strcpy(buf,"Content-Disposition: inline\r\n"); write_to_socket(buf); /* add encoding type if needed */ if (strncmp(a->content_transfer_encoding,"none",4) != 0) { (void) snprintf(buf, bufsz, "Content-Transfer-Encoding: %s\r\n\r\n", a->content_transfer_encoding); write_to_socket(buf); } if (strncmp(a->content_transfer_encoding,"base64",6) == 0) { /* encode the mssage to base 64 and write to socket */ encode2base64andwrite2socket(a->oneline_msg); } else { /* ** no encoding type, so last \r\n was no added, we must ** add it or some mailer will thing the mail is invalid */ if (strncmp(a->content_transfer_encoding,"none",4) == 0) { write_to_socket("\r\n"); } write_to_socket(a->oneline_msg); if (g_show_attachment_in_log) { showVerbose("[C] %s\n",a->oneline_msg); } } write_to_socket("\r\n"); } return(0); } /* ** include the content of the file as body of the message ** No encoding will be done. */ int include_msg_body(void) { Sll *l, *msg_body_attachment_head; FILE *fp = NULL; Attachment *a; msg_body_attachment_head = get_msg_body_attachment_list(); if (msg_body_attachment_head == NULL) { return(-1); } l = msg_body_attachment_head; a = (Attachment *) l->data; (void) snprintf(buf,bufsz,"MIME-Version: 1.0\r\n"); write_to_socket(buf); if (strcmp(a->charset,"none") != 0) { (void) snprintf(buf,bufsz,"Content-Type: %s; charset=%s\r\n\r\n", a->mime_type, a->charset); } else { (void) snprintf(buf,bufsz,"Content-Type: %s\r\n\r\n",a->mime_type); } write_to_socket(buf); fp=fopen(a->file_path,"r"); if (fp == (FILE *) NULL) { errorMsg("Could not open message body file: %s",a->file_path); return (-1); } while (fgets(buf,bufsz,fp)) { write_to_socket(buf); if (g_show_attachment_in_log) { showVerbose("[C] %s",buf); } } (void) fclose(fp); (void) snprintf(buf,bufsz,"\r\n\r\n"); msock_puts(buf); showVerbose(buf); return(0); } int include_image(void) { return(0); } static void cleanup_attachment(Attachment *a) { if (a == NULL) return; if (a->fp_read) { (void) fclose(a->fp_read); } if (*a->mime_tmpfile != '\0') { unlink(a->mime_tmpfile); } } /* ** return the attachment with fp_read member open. ** If encoding type requires some kind of encoding, ** mime_tmpfile will contain the file which holds the ** encoded content. The caller must close the file pointer ** and unlik the tmp_filename ** return NULL on error */ static Attachment *get_encoded_attachment(Attachment *a) { FILE *fp_read = NULL, *tfp_write = NULL; int tempfile_opened = 0; if (strncmp(a->content_transfer_encoding, "base64", 6) == 0) { tfp_write = mutils_get_tempfileFP(a->mime_tmpfile,sizeof(a->mime_tmpfile)-1); if (tfp_write == NULL) { errorMsg("%s (%d) - Could not create temp file for MIME (%s)", MFL, ERR_STR); return(NULL); } tempfile_opened = 1; showVerbose("%s (%d) - MIME temp file: %s created successfully, FILE pointer=%x\n", MFL, a->mime_tmpfile, tfp_write); /* open the file to attach */ fp_read = fopen(a->file_path,"rb"); if (fp_read == (FILE *) NULL) { errorMsg("%s (%d) - Could not open file for %s reading (%s)", MFL, a->file_path, ERR_STR); goto ExitProcessing; } showVerbose("%s (%d) - Writing Content to FILE pointer: %x\n", MFL, tfp_write); /* write base64 content to tmp file */ mutilsBase64Encode(fp_read,tfp_write); (void) fclose(fp_read); fp_read = NULL; (void) fclose(tfp_write); tfp_write = NULL; a->fp_read = fopen(a->mime_tmpfile, "r"); if (a->fp_read == (FILE *) NULL) { errorMsg("%s (%d) - Could not open file for %s reading (%s)", MFL, a->file_path, ERR_STR); goto ExitProcessing; } if (tempfile_opened) { unlink(a->mime_tmpfile); } return(a); } /* no tmp file */ memset(a->mime_tmpfile, 0, sizeof(a->mime_tmpfile)); /* open the attachment. caller must close it */ a->fp_read = fopen(a->file_path, "r"); if (a->fp_read == (FILE *) NULL) { errorMsg("%s (%d) - Could not open file for %s reading (%s)", MFL, a->file_path, ERR_STR); return (NULL); } return(a); ExitProcessing: if (tempfile_opened) { unlink(a->mime_tmpfile); } return(NULL); } /* ** write MIME headers, encode attachment if needed ** and write to socket ** returns 0 on success -1 on failure */ int send_attachment(Attachment *a, const char *boundary) { Attachment *encoded_attachment; encoded_attachment = get_encoded_attachment(a); if (encoded_attachment == NULL) { errorMsg("%s (%d) - Could not encode attachment %s", MFL, a->file_path); return(-1); } (void) snprintf(buf, bufsz,"\r\n--%s\r\n",boundary); write_to_socket(buf); if (a->charset && strncmp(a->charset,"none", 4) != 0) { (void) snprintf(buf, bufsz, "Content-Type: %s; charset=%s\r\n", a->mime_type, a->charset); } else { if (a->attachment_name) { (void) snprintf(buf, bufsz, "Content-Type: %s; name=\"%s\"\r\n", a->mime_type, a->attachment_name); } else { (void) snprintf(buf, bufsz, "Content-Type: %s\r\n",a->mime_type); } } write_to_socket(buf); if (a->content_id == NULL) { if (strncmp(a->content_disposition, "inline", 6) == 0) { (void) snprintf(buf, bufsz, "Content-Disposition: %s\r\n",a->content_disposition); } else { if (a->attachment_name) { (void) snprintf(buf, bufsz, "Content-Disposition: %s; filename=\"%s\"\r\n", a->content_disposition, a->attachment_name); } else if (a->file_name) { (void) snprintf(buf, bufsz, "Content-Disposition: %s; filename=\"%s\"\r\n", a->content_disposition, a->file_name); } else { (void) snprintf(buf, bufsz, "Content-Disposition: %s\r\n",a->content_disposition); } } write_to_socket(buf); } if (a->content_id) { (void) snprintf(buf, bufsz, "Content-ID: <%s>\r\n",a->content_id); write_to_socket(buf); (void) snprintf(buf, bufsz, "X-Attachment-Id: %s\r\n",a->content_id); write_to_socket(buf); } if (strncmp(a->content_transfer_encoding,"none",4) != 0) { (void) snprintf(buf, bufsz, "Content-Transfer-Encoding: %s\r\n\r\n", a->content_transfer_encoding); write_to_socket(buf); } else { write_to_socket("\r\n"); } /* our FILE pointer is already open and read to read from */ while (fgets(buf, bufsz, a->fp_read)) { write_to_socket(buf); } /* close file, remove tmp file if needed */ cleanup_attachment(a); return(0); } int process_attachments(const char *boundary) { Attachment *a; Sll *attachment_list, *al; int rc; attachment_list = get_attachment_list(); if (attachment_list == NULL) { return(0); } for (al=attachment_list; al; al=al->next) { a=(Attachment *) al->data; if (a == NULL) continue; rc = send_attachment(a, boundary); if (rc == -1) { errorMsg("%s (%d) - failed to send attachment %s\n", MFL, a->file_path); return(-1); } } (void) snprintf(buf,sizeof(buf)-1,"\r\n--%s--\r\n",boundary); msock_puts(buf); showVerbose(buf); return(0); } /* ** Print multipart/mixed or multipart/related header ** if mixed and embedded images are specified, make ** to use multipart/alternative for the embedded images. */ int print_content_type_header(const char *boundary) { Sll *oneline_attachment_list, *attachment_list, *embed_image_list; (void) snprintf(buf, bufsz,"MIME-Version: 1.0\r\n"); write_to_socket(buf); if (*g_content_type!='\0') { (void) snprintf(buf,sizeof(buf)-1,"Content-Type: %s; boundary=\"%s\"\r\n", g_content_type, boundary); write_to_socket(buf); return(0); } oneline_attachment_list = get_oneline_attachment_list(); attachment_list = get_attachment_list(); embed_image_list = get_embed_image_attachment_list(); if (oneline_attachment_list || attachment_list) { (void) snprintf(buf,sizeof(buf)-1, "Content-Type: multipart/mixed; boundary=\"%s\"\r\n", boundary); write_to_socket(buf); return(0); } if (embed_image_list) { (void) snprintf(buf,sizeof(buf)-1, "Content-Type: multipart/related; boundary=\"%s\"\r\n", boundary); write_to_socket(buf); return(0); } write_to_socket("\r\n"); return(0); } int process_embeded_images(const char *boundary) { char *ib, *b, related[17], cid[17], tbuf[24], alternative[17]; Attachment *a; Sll *il, *oneline_attachment_list, *attachment_list, *embed_image_list; int rc, ic = 1; oneline_attachment_list = get_oneline_attachment_list(); attachment_list = get_attachment_list(); embed_image_list = get_embed_image_attachment_list(); if (embed_image_list == NULL) { return(0); } memset(related, 0, sizeof(related)); memset(alternative, 0, sizeof(alternative)); mutilsGenerateMIMEBoundary(related,sizeof(related)); mutilsGenerateMIMEBoundary(alternative,sizeof(alternative)); b = boundary; ib = boundary; (void) snprintf(buf, bufsz, "\r\n--%s\r\n",boundary); write_to_socket(buf); if (attachment_list || oneline_attachment_list) { b = related; ib = b; (void) snprintf(buf, bufsz, "Content-Type: multipart/related; boundary=%s\r\n",b); write_to_socket(buf); (void) snprintf(buf, bufsz, "Content-Type: multipart/alternative; boundary=%s\r\n\r\n", alternative); write_to_socket(buf); (void) snprintf(buf, bufsz, "\r\n--%s\r\n",b); write_to_socket(buf); } else { ib = alternative; (void) snprintf(buf, bufsz, "Content-Type: multipart/alternative; boundary=%s\r\n\r\n", alternative); write_to_socket(buf); (void) snprintf(buf, bufsz, "\r\n--%s\r\n",alternative); write_to_socket(buf); } write_to_socket("Content-Type: text/html; charset=ISO-8859-1\r\n\r\n"); /* write the img tags with cid */ embed_image_list = get_embed_image_attachment_list(); for (il = embed_image_list; il; il = il->next) { a = (Attachment *) il->data; mutilsGenerateMIMEBoundary(cid,sizeof(cid)); (void) snprintf(tbuf,sizeof(tbuf)-1,"ii%d_%s",ic,cid); a->content_id = xStrdup(tbuf); (void) snprintf(buf, bufsz, "\"inline
\n", tbuf, ic); write_to_socket(buf); ic++; } write_to_socket("\r\n"); (void) snprintf(buf, bufsz, "\r\n--%s--\r\n",ib); write_to_socket(buf); for (il = embed_image_list; il; il = il->next) { a = (Attachment *) il->data; if (a == NULL) continue; rc = send_attachment(a,b); RETURN_IF_NOT_ZERO(rc); } (void) snprintf(buf, bufsz, "\r\n--%s--\r\n",b); write_to_socket(buf); return(0); } static void print_end_boundary(const char *boundary) { if (get_attachment_list() == NULL) { (void) snprintf(buf,sizeof(buf)-1,"\r\n--%s--\r\n",boundary); msock_puts(buf); showVerbose(buf); return; } /* process_attachments(boundary) already printed the end bounday */ } /* SMTP: mail */ static int smtpMail(int sfd,char *to,char *cc,char *bcc,char *from,char *rrr,char *rt, char *subject,char *attach_file,char *msg_body_file, char *the_msg,int is_mime,int add_dateh) { char *os="Unix", boundary[17], related[17], alternative[17], mbuf[1024]; int newline_before; Sll *oneline_attachment_list, *attachment_list, *embed_image_list; #ifdef WINNT os="Windows"; #else os="Unix"; #endif /* WINNT */ memset(boundary, 0, sizeof(boundary)); memset(related, 0, sizeof(related)); memset(alternative, 0, sizeof(alternative)); attachment_list=get_attachment_list(); embed_image_list = get_embed_image_attachment_list(); oneline_attachment_list = get_oneline_attachment_list(); if (attachment_list || embed_image_list || oneline_attachment_list) { is_mime=1; } if (subject) { memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"Subject: %s\r\n",subject); msock_puts(buf); showVerbose(buf); } /* headers */ if (from) { memset(buf,0,sizeof(buf)); if (*g_from_name != '\0') { /* Name in From: */ memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"From: %s <%s>\r\n", g_from_name,from); } else { (void) snprintf(buf,sizeof(buf)-1,"From: %s\r\n",from); } msock_puts(buf); showVerbose(buf); } if (add_dateh) { /* add Date: header */ char datebuf[65]; memset(datebuf,0,sizeof(datebuf)); if (rfc822_date(time(NULL),datebuf,sizeof(datebuf)-1) == 0) { memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"Date: %s\r\n",datebuf); msock_puts(buf); showVerbose(buf); } } if (to) { memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"To: %s\r\n",to); msock_puts(buf); showVerbose(buf); } if (cc) { memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"Cc: %s\r\n",cc); msock_puts(buf); showVerbose(buf); } /* if (bcc) { memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"Bcc: %s\r\n",bcc); msock_puts(buf); showVerbose(buf); } */ if (rt != NULL) { memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"Reply-To: %s\r\n",rt); msock_puts(buf); showVerbose(buf); } if (rrr != NULL) { memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"Disposition-Notification-To: %s\r\n",rrr); msock_puts(buf); showVerbose(buf); } /* add custom headers if any. No verification is done */ { Sll *l, *custom_header_list; custom_header_list = get_custom_header_list(); if (custom_header_list) { for (l = custom_header_list; l; l = l->next) { if (l->data) { msock_puts((char *) l->data); msock_puts("\r\n"); showVerbose((char *) l->data); showVerbose("\r\n"); } } } } memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"X-Mailer: %s (%s)\r\n",MAILSEND_VERSION,os); msock_puts(buf); showVerbose(buf); memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"X-Copyright: %s\r\n",NO_SPAM_STATEMENT); msock_puts(buf); showVerbose(buf); if (is_mime) { int rc; srand(time(NULL)); memset(boundary,0,sizeof(boundary)); mutilsGenerateMIMEBoundary(boundary,sizeof(boundary)); /* if msg body file is specified, include and return */ if (msg_body_file) { return (include_msg_body()); } rc = print_content_type_header(boundary); RETURN_IF_NOT_ZERO(rc); rc = process_oneline_messages(boundary); RETURN_IF_NOT_ZERO(rc); rc = process_embeded_images(boundary); RETURN_IF_NOT_ZERO(rc); rc = process_attachments(boundary); RETURN_IF_NOT_ZERO(rc); /* ** if there were other kind of MIME types but no attachments, ** we have to print the last boundary */ print_end_boundary(boundary); /* handle MIME attachments ends */ goto done; } /* is_mime */ /* mail body */ if (attach_file == NULL && the_msg == NULL) /* read from stdin */ { /* if stdin is a terminal, print the instruction */ if (isInConsole(_fileno(stdin))) { (void) printf("=========================================================================\n"); (void) printf("Type . in a new line and press Enter to end the message, CTRL+C to abort\n"); (void) printf("=========================================================================\n"); } #ifdef WINNT SetConsoleCtrlHandler(CntrlHandler,TRUE); #endif /* WINNT */ newline_before=1; msock_puts("\r\n"); /* RFC822 sec 3.1 */ showVerbose("\r\n"); while (fgets(mbuf,sizeof(mbuf)-1,stdin) && (break_out == 0)) { if (newline_before && *mbuf == '.') { break; } else { int len; /* vinicio qmail fix */ len=strlen(mbuf); if (mbuf[len-1] != '\n') strcat(mbuf,"\r\n"); else { mbuf[--len]='\0'; strcat(mbuf,"\r\n"); } /* vinicio qmail fix */ msock_puts(mbuf); showVerbose("[C] %s",mbuf); } newline_before=(*mbuf != '\0' && mbuf[strlen(mbuf)-1] == '\n'); if (break_out == 1) { (void) fprintf(stderr," Breaking out\n"); return (0); } } } done: return (0); } /* greeting can be multi line */ static int read_greetings(void) { int lcnt=0, rc=(-1); rc=read_smtp_line(); if (rc < 0) goto cleanup; s_esmtp=g_esmtp; /* if forced with -ehlo */ if (smtp_code != 220) { read_smtp_multi_lines(); errorMsg("Expected smtp code 220, got %d\n",smtp_code); rc=(-1); goto cleanup; } if (mutilsStristr(smtp_line,"ESMTP")) { s_esmtp=1; } /* greeting can be multi-line */ if (smtp_sep == A_DASH) { for (;;) { rc=read_smtp_line(); if (rc < 0) break; lcnt++; if (lcnt >= 100) { errorMsg("Too many greeting lines\n"); rc=(-1); goto cleanup; } if (mutilsStristr(smtp_line,"ESMTP")) { s_esmtp=1; } if (smtp_sep != A_DASH) break; } } cleanup: smtp_sep = A_SPACE; return(rc); } static int turn_on_raw_ssl(SOCKET sfd) { #ifdef HAVE_OPENSSL int rc=(-1); SSL *ssl; ssl=msock_get_ssl(); if (ssl) { if (!SSL_set_fd(ssl,sfd)) { errorMsg("turn_on_raw_ssl: failed to set socket %d to SSL\n",sfd); return(-1); } /* must set back to msock's static */ msock_set_ssl(ssl); rc=SSL_connect(ssl); if (rc < 1) { errorMsg("turn_on_raw_ssl: SSL connection failed\n"); ERR_print_errors_fp(stderr); return(-1); } print_cert_info(ssl); /* tell msock everything is ssl after that */ msock_turn_ssl_on(); rc=0; return(0); } #endif /* HAVE_OPENSSL */ return(-1); } /* It's stupid, I need to change all the args to a struct, one of those * days! I'll do it */ /* returns 0 on success, -1 on failure */ int send_the_mail(char *from,char *to,char *cc,char *bcc,char *sub, char *smtp_server,int smtp_port,char *helo_domain, char *attach_file,char *txt_msg_file,char *the_msg,int is_mime,char *rrr,char *rt, int add_dateh,char* return_path_addr) { SOCKET sfd; TheMail *mail; Sll *al; int rc=(-1); char *mech=NULL, *auth=NULL; /* unsigned char *b64=NULL; */ char *b64 = NULL; int authenticated=0; /* (void) fprintf(stderr,"From: %s\n",from); (void) fprintf(stderr,"To: %s\n",to); (void) fprintf(stderr,"Cc: %s\n",cc); (void) fprintf(stderr,"Cc: %s\n",bcc); (void) fprintf(stderr,"Sub: %s\n",sub); (void) fprintf(stderr,"smtp: %s\n",smtp_server); (void) fprintf(stderr,"smtp port: %d\n",smtp_port); (void) fprintf(stderr,"domain: %s\n",helo_domain); (void) fprintf(stderr,"attach file: %s\n",attach_file); (void) fprintf(stderr,"txt_msg_file: %s\n",txt_msg_file); (void) fprintf(stderr,"the_msg: %s\n",the_msg); (void) fprintf(stderr,"is_mime: %d\n",is_mime); */ al=getAddressList(); if (al == (Sll *) NULL) { errorMsg("No To address/es specified"); return (-1); } if (from == (char *) NULL) { errorMsg("No From address specified"); return (-1); } if (smtp_server == (char *) NULL) smtp_server="127.0.0.1"; if (smtp_port == -1) smtp_port=MAILSEND_SMTP_PORT; if (sub == (char *) NULL) sub=MAILSEND_DEF_SUB; if (helo_domain == (char *) NULL) { errorMsg("No domain specified"); return (-1); } mail=newTheMail(); if (mail == (TheMail *) NULL) { errorMsg("Error: malloc failed in createTheMail()\n"); return (-1); } showVerbose("Connecting to %s:%d\n",smtp_server,smtp_port); /* open the network connection */ sfd=smtpConnect(smtp_server,smtp_port); if (sfd == INVALID_SOCKET) { rc=(-1); goto cleanup; } if (g_do_ssl) /* smtp.gmail:465 supports it for example */ { turn_on_raw_ssl(sfd); } /* read greeting */ rc=read_greetings(); if (rc < 0) goto cleanup; /* say HELO/EHLO */ say_helo(helo_domain); /* check if the server supports STARTTLS or TLS */ if (g_do_starttls) { if (check_server_cap("STARTTLS") || check_server_cap("TLS")) { rc=smtp_start_tls(sfd); if (rc == 0) { /* send HELO again */ say_helo(helo_domain); } } } if (g_do_auth || g_auth_cram_md5 || g_auth_login || g_auth_plain) { auth=check_server_cap("AUTH"); } if (!auth) goto MailFrom; /* (void) fprintf(stderr,"auth=%s\n",auth); (void) fprintf(stderr," g_auth_cram_md5=%d; g_auth_login=%d; g_auth_plain=%d\n", g_auth_cram_md5, g_auth_login, g_auth_plain); */ /* if (auth && g_do_auth) { g_auth_cram_md5=1; g_auth_login=1; g_auth_plain=1; } */ /* Try CRAM-MD5 first */ mech="CRAM-MD5"; if (g_auth_cram_md5 && check_server_cap(mech)) { char *cmd5 = NULL; CHECK_USERNAME(mech); CHECK_USERPASS(mech); #ifndef HAVE_OPENSSL errorMsg("Must be compiled with OpenSSL in order to get CRAM-MD5 support\n"); goto cleanup; #endif /* !HAVE_OPENSSL */ showVerbose("Using AUTH %s\n",mech); memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"AUTH %s\r\n",mech); showVerbose("[C] %s",buf); msock_puts(buf); read_smtp_line(); if (smtp_code != 334) { errorMsg("AUTH CRAM-MD5 failed: '%d %s'", smtp_code, smtp_line); rc=(-1); goto cleanup; } cmd5 = encode_cram_md5(smtp_line,g_username,g_userpass); if (cmd5 == NULL) { errorMsg("Could not encode CRAM-MD5"); rc = (-1); goto cleanup; } memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"%s\r\n",cmd5); showVerbose("[C] %s",buf); msock_puts(buf); read_smtp_line(); if (smtp_code != 235) { errorMsg("AUTH CRAM-MD5 failed: '%d %s'", smtp_code, smtp_line); rc=(-1); goto cleanup; } showVerbose("%s Authentication succeeded\n",mech); authenticated++; if (cmd5) { (void) free((char *) cmd5); } } else { if (g_auth_cram_md5) showVerbose("Server does not support AUTH CRAM-MD5\n"); } if (authenticated) goto MailFrom; mech="LOGIN"; if (g_auth_login && check_server_cap(mech)) { CHECK_USERNAME(mech); CHECK_USERPASS(mech); showVerbose("Using AUTH %s\n",mech); memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"AUTH %s\r\n",mech); showVerbose("[C] %s",buf); msock_puts(buf); read_smtp_line(); if (smtp_code != 334) { errorMsg("AUTH LOGIN failed: '%d %s'", smtp_code, smtp_line); rc=(-1); goto cleanup; } /* b64=mutils_encode_base64(g_username,strlen(g_username),&b64len); b64[b64len-2]='\0'; */ b64=mutils_encode_base64_noformat(g_username,strlen(g_username)); if (b64 == NULL) { errorMsg("Could not base64 encode user: %s",g_username); rc=(-1); goto cleanup; } memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"%s\r\n",b64); showVerbose("[C] %s",buf); msock_puts(buf); read_smtp_line(); if (smtp_code != 334) { errorMsg("AUTH LOGIN failed: '%d %s'", smtp_code, smtp_line); rc=(-1); goto cleanup; } /* b64=mutils_encode_base64(g_userpass,strlen(g_userpass),&b64len); b64[b64len-2]='\0'; */ b64=mutils_encode_base64_noformat(g_userpass,strlen(g_userpass)); if (b64 == NULL) { errorMsg("Could not base64 encode passworf of user: %s",g_username); rc=(-1); goto cleanup; } memset(buf,0,sizeof(buf)); (void) snprintf(buf,sizeof(buf)-1,"%s\r\n",b64); showVerbose("[C] %s",buf); msock_puts(buf); read_smtp_line(); if (smtp_code != 235) { errorMsg("AUTH LOGIN failed: '%d %s'", smtp_code, smtp_line); rc=(-1); goto cleanup; } authenticated++; } else { if (g_auth_login) showVerbose("Server does not support AUTH LOGIN\n"); } if (authenticated) goto MailFrom; mech="PLAIN"; if (g_auth_plain && check_server_cap(mech)) { int len, ulen, plen; unsigned char *b64=NULL; CHECK_USERNAME(mech); CHECK_USERPASS(mech); showVerbose("Using AUTH %s\n",mech); memset(buf,0,sizeof(buf)); /* ** authzid\0authid\0pass ** authzid can be skipped if both are the same */ ulen=strlen(g_username); memcpy(buf + 1,g_username,ulen); plen=strlen(g_userpass); memcpy(buf + ulen + 2,g_userpass,plen); len=ulen + plen + 2; #if 0 b64=mutils_encode_base64(buf,len,&b64len); /* mutils_encode_base64 adds CRLF */ b64[b64len-2]='\0'; #endif b64=mutils_encode_base64_noformat(buf,len); if (b64 == NULL) { errorMsg("Could not base64 for AUTH-PLAIN for user: %s",g_username); rc=(-1); goto cleanup; } (void) snprintf(buf,sizeof(buf)-1,"AUTH PLAIN %s\r\n",(char *) b64); showVerbose("[C] %s",buf); msock_puts(buf); read_smtp_line(); if (smtp_code != 235) { errorMsg("AUTH PLAIN failed: '%d %s'", smtp_code, smtp_line); rc=(-1); goto cleanup; } showVerbose("PLAIN Authentication succeeded\n"); authenticated++; } else { if (g_auth_plain) showVerbose("Server does not support AUTH PLAIN\n"); } if (authenticated) goto MailFrom; if (auth && !g_quiet) { if (!g_auth_cram_md5) { if (check_server_cap("CRAM-MD5")) { (void) fprintf(stderr, " * Server supports AUTH CRAM-MD5."); } } if (!g_auth_login) { if (check_server_cap("LOGIN")) { (void) fprintf(stderr, " * Server supports AUTH LOGIN.\n"); } } if (!g_auth_plain) { if (check_server_cap("PLAIN")) { (void) fprintf(stderr, " * Server supports AUTH PLAIN.\n"); } } if (!authenticated) { (void) fprintf(stderr, " Use -auth or specify a mechanism that server supports. exiting.\n"); exit_error(); } } MailFrom: if (return_path_addr) rc=smtp_MAIL_FROM(return_path_addr); else rc=smtp_MAIL_FROM(from); if (rc != 0) goto cleanup; rc=smtp_RCPT_TO(); if (rc != 0) goto cleanup; rc=smtp_DATA(); if (rc != 0) goto cleanup; rc=smtpMail(sfd,to,cc,bcc,from,rrr,rt,sub,attach_file,txt_msg_file,the_msg,is_mime,add_dateh); RETURN_IF_NOT_ZERO(rc); rc=smtpEom(sfd); RETURN_IF_NOT_ZERO(rc); smtp_QUIT(); cleanup: return (rc); } /* ** print info about SMTP server on stdout ** return 0 on success -1 on error */ int show_smtp_info(char *smtp_server,int port,char *domain) { int fd, rc = (-1); #ifdef HAVE_OPENSSL SSL *ssl=NULL; #endif /* HAVE_OPENSSL */ g_verbose=1; print_info("Connecting to SMTP server: %s at Port: %d\n",smtp_server,port); print_info("Connection timeout: %d secs\n",g_connect_timeout); /* connect */ fd=smtpConnect(smtp_server,port); if (fd == INVALID_SOCKET) { rc = (-1); goto ExitProcessing; } #ifdef HAVE_OPENSSL if (g_do_ssl) { ssl=msock_get_ssl(); if (ssl) { if (!SSL_set_fd(ssl,fd)) { errorMsg("failed to set socket to SSL\n"); goto ExitProcessing; } /* must set back to msock's static */ msock_set_ssl(ssl); rc=SSL_connect(ssl); if (rc < 1) { errorMsg("SSL connection failed\n"); ERR_print_errors_fp(stderr); goto ExitProcessing; } print_cert_info(ssl); /* tell msock everything is ssl after that */ msock_turn_ssl_on(); rc=0; } } #endif /* HAVE_OPENSSL */ /* read greeting */ rc=read_greetings(); if (rc < 0) { errorMsg("Could not read greetings\n"); goto ExitProcessing; } /* say HELO/EHLO */ say_helo(domain); /* check if server supports STARTTLS */ if (check_server_cap("STARTTLS") || check_server_cap("TLS")) { rc=smtp_start_tls(fd); if (rc == 0) say_helo(domain); else goto ExitProcessing; } smtp_QUIT(); smtpDisconnect(); ExitProcessing: return (rc); }