From 52a42728f3b2354d50bce3d5217089acfec79797 Mon Sep 17 00:00:00 2001 From: Cedric RICARD Date: Wed, 25 Feb 2009 18:35:25 +0000 Subject: [PATCH] Improved send_mail() function: - ability to define your own logger - callback to capture progression - low limit for transfer rate (64Kb/h) --- calexium_lib/mail/send_mail.anubis | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------------------------------- calexium_lib/net_services_protocols/logger_service.anubis | 8 ++++++++ 2 files changed, 179 insertions(+), 116 deletions(-) diff --git a/calexium_lib/mail/send_mail.anubis b/calexium_lib/mail/send_mail.anubis index 112e404..c4494d2 100644 --- a/calexium_lib/mail/send_mail.anubis +++ b/calexium_lib/mail/send_mail.anubis @@ -88,22 +88,23 @@ define Command_Result RStream conn, List(Word8) so_far, Word8 previous, - Int time_out + Int time_out, + (LogLevel, String) -> One logger ) = if read_network_byte(conn, time_out) is { - failure then logTrace(send_mail_log, send_mail_mask, "receive_command read_network_byte 0 failure");failure, - timeout then logWarning(send_mail_log, "[send_mail] receive_command timeout"); timeout, + failure then logger(logTrace, "receive_command read_network_byte 0 failure");failure, + timeout then logger(logWarning, "[send_mail] receive_command timeout"); timeout, success(c) then if c = 10 & previous = 13 then // with result = implode(reverse(so_far)), - logTrace(send_mail_log, send_mail_mask,"<-S-"+result); + logger(logTrace, "<-S-"+result); success(result) else if previous = 13 then - receive_command(conn, so_far, c, time_out) + receive_command(conn, so_far, c, time_out, logger) else - receive_command(conn, [previous . so_far], c, time_out) + receive_command(conn, [previous . so_far], c, time_out, logger) }. define Command_Result @@ -111,13 +112,14 @@ define Command_Result ( RStream conn, List(Word8) so_far, - Int time_out + Int time_out, + (LogLevel, String) -> One logger ) = if read_network_byte(conn, time_out) is { - failure then logTrace(send_mail_log, send_mail_mask, "receive_command read_network_byte 0 failure"); failure, - timeout then (if time_out = 0 then unique else logWarning(send_mail_log, "[send_mail] receive_command timeout")); timeout, - success(c) then receive_command(conn,[], c, time_out) + failure then logger(logTrace, "receive_command read_network_byte 0 failure"); failure, + timeout then (if time_out = 0 then unique else logger(logWarning, "[send_mail] receive_command timeout")); timeout, + success(c) then receive_command(conn,[], c, time_out, logger) }. @@ -150,12 +152,13 @@ define Reply_Result ( RStream conn, List(String) so_far, - Int time_out + Int time_out, + (LogLevel, String) -> One logger ) = - if receive_command(conn, [], time_out) is + if receive_command(conn, [], time_out, logger) is { - failure then logError(send_mail_log, "receive_reply: error receiving command"); error, - timeout then (if time_out > 0 then logError(send_mail_log, "receive_reply: timeout receiving command") else unique); timeout, + failure then logger(logError, "receive_reply: error receiving command"); error, + timeout then (if time_out > 0 then logger(logError, "receive_reply: timeout receiving command") else unique); timeout, success(line) then //check if we must read another line by presence of hyphen after the reply code //220-bla bla bla @@ -163,24 +166,24 @@ define Reply_Result if length(line) =< 3 then if decimal_scan(line) is { - failure then logError(send_mail_log, "receive_reply: can't extract reply code from '" + line + "'"); error, + failure then logger(logError, "receive_reply: can't extract reply code from '" + line + "'"); error, success(code) then reply(code, reverse(so_far)) } else if nth(3, line) is { - failure then logError(send_mail_log, "receive_reply: error getting 4th character from '" + line +"'"); error, + failure then logger(logError, "receive_reply: error getting 4th character from '" + line +"'"); error, success(char) then if char = '-' then - receive_reply(conn, [line . so_far], time_out) + receive_reply(conn, [line . so_far], time_out, logger) else //decode the code if sub_string(line, 0, 3) is { - failure then logError(send_mail_log, "receive_reply: error extracting reply code from '" + line+"'"); error, // should never occure + failure then logger(logError, "receive_reply: error extracting reply code from '" + line+"'"); error, // should never occure success(code_str) then if decimal_scan(code_str) is { - failure then logError(send_mail_log, "receive_reply: can't extract reply code from '" + code_str + "'"); error, //unreadable code + failure then logger(logError, "receive_reply: can't extract reply code from '" + code_str + "'"); error, //unreadable code success(code) then reply(code , reverse([line . so_far])) } } @@ -191,23 +194,25 @@ define Reply_Result receive_reply ( RWStream conn, + (LogLevel, String) -> One logger ) = - receive_reply(weaken(conn), [], smtp_time_out). + receive_reply(weaken(conn), [], smtp_time_out, logger). Sending a piece of text (String) from the begining. define Maybe(One) smtp_send_line - ( - RWStream conn, - String text - ) = + ( + RWStream conn, + String text, + (LogLevel, String) -> One logger + ) = if reliable_write(tcp(conn),[text + crlf]) is { - failure then logError(send_mail_log, "smtp_send_line: error writing '" + text + "'"); failure, + failure then logger(logError, "smtp_send_line: error writing '" + text + "'"); failure, success(_) then - logTrace(send_mail_log,send_mail_mask, "-C->"+text);success(unique) + logger(logTrace, "-C->"+text);success(unique) } . Because of attachements, we may have to manipulate very big pieces of text. It would be @@ -229,32 +234,33 @@ define StringTree [StringTree s . StringTree t] = tree_tree(s,t). define Reply_Result send_ehlo ( - RWStream conn, - String our_host_name + RWStream conn, + String our_host_name, + (LogLevel, String) -> One logger ) = - if smtp_send_line(conn,"EHLO "+our_host_name) is + if smtp_send_line(conn,"EHLO "+our_host_name, logger) is { - failure then logError(send_mail_log, "send_ehlo: error sending EHLO"); error, + failure then logger(logError, "send_ehlo: error sending EHLO"); error, success(_) then - with rep = if receive_reply(conn) is + with rep = if receive_reply(conn, logger) is { - error then logError(send_mail_log, "send_ehlo: can't get reply"); error, - timeout then logError(send_mail_log, "send_ehlo: timeout getting reply"); timeout, + error then logger(logError, "send_ehlo: can't get reply"); error, + timeout then logger(logError, "send_ehlo: timeout getting reply"); timeout, reply(code, lines) then //we manage the 500 error, that mean the remote server is not ESMTP //hence we try we with HELO, the old manner RFC 821 if code = 500 | code = 502 then - if smtp_send_line(conn,"HELO "+our_host_name) is + if smtp_send_line(conn,"HELO "+our_host_name, logger) is { - failure then logError(send_mail_log, "send_ehlo: error sending HELO"); error, - success(_) then receive_reply(conn) + failure then logger(logError, "send_ehlo: error sending HELO"); error, + success(_) then receive_reply(conn, logger) } else reply(code, lines) }, if rep is reply(code, lines) then if code = 550 then // some server simply refuse us because it's temporary overloaded (especially try when sending mailings) - logDebug(send_mail_log, "Converting a 550 error for EHLO to a 432 error as distant server may be temporary overloaded."); + logger(logDebug, "Converting a 550 error for EHLO to a 432 error as distant server may be temporary overloaded."); reply(432, lines) else rep @@ -281,14 +287,15 @@ define String define Reply_Result send_mail_from ( - RWStream conn, - String sender + RWStream conn, + String sender, + (LogLevel, String) -> One logger ) = with rfc_sender = check_email_syntax(sender), - if smtp_send_line(conn,"MAIL FROM:"+rfc_sender) is + if smtp_send_line(conn,"MAIL FROM:"+rfc_sender, logger) is { - failure then logError(send_mail_log, "error sending 'MAIL FROM:" + rfc_sender + "'"); error, - success(_) then receive_reply(conn) + failure then logger(logError, "error sending 'MAIL FROM:" + rfc_sender + "'"); error, + success(_) then receive_reply(conn, logger) }. The same one for 'RCPT TO': @@ -298,12 +305,13 @@ define Reply_Result ( RWStream conn, String recipient, + (LogLevel, String) -> One logger ) = with rfc_recipient = check_email_syntax(recipient), - if smtp_send_line(conn,"RCPT TO:"+rfc_recipient) is + if smtp_send_line(conn,"RCPT TO:"+rfc_recipient, logger) is { - failure then logError(send_mail_log, "error sending 'RCPT TO:"+rfc_recipient+"'"); error, - success(_) then receive_reply(conn) + failure then logger(logError, "error sending 'RCPT TO:"+rfc_recipient+"'"); error, + success(_) then receive_reply(conn, logger) }. @@ -311,13 +319,14 @@ define Reply_Result define Reply_Result send_data - ( - RWStream conn - ) = - if smtp_send_line(conn,"DATA") is + ( + RWStream conn, + (LogLevel, String) -> One logger + ) = + if smtp_send_line(conn,"DATA", logger) is { - failure then logError(send_mail_log, "error sending DATA"); error, - success(_) then receive_reply(conn) + failure then logger(logError, "error sending DATA"); error, + success(_) then receive_reply(conn, logger) }. @@ -334,40 +343,52 @@ define Maybe(One) sm_flush ( ByteArray buffer, - WStream target + WStream target, + Int buffer_start_time, + (LogLevel, String) -> One logger )= if write( target , buffer) is { failure then failure, success(nb_write) then - with buffer_size = length(buffer), - if nb_write = buffer_size then - success(unique) + if now - buffer_start_time > 3600 then + // security to avoid queue blocking + logger(logError, "sm_flush: TIMEOUT sending a 64kb buffer (taking more than 1 hour). SendMail canceled."); + failure else - with new_buffer = extract(buffer, nb_write, buffer_size), - sm_flush(new_buffer, target) + with buffer_size = length(buffer), + if nb_write = buffer_size then + success(unique) + else + with new_buffer = extract(buffer, nb_write, buffer_size), + sm_flush(new_buffer, target, buffer_start_time, logger) }. public define SendContentResult sm_copy_Data_IO_to_Stream ( - Data_IO source, - RWStream target, - Int so_far + Data_IO source, + RWStream target, + Int start_time, + Int so_far, + (Int) -> One progress_report, + (LogLevel, String) -> One logger ) = - if receive_reply(weaken(target), [], 0) is + if receive_reply(weaken(target), [], 0, logger) is { error then copy_error(so_far), timeout then if read_bytes(source, 65536) is { - failure then logError(send_mail_log, "send_content: failed to read input data_io"); copy_error(so_far), - time_out then logError(send_mail_log, "send_content: timeout reading input data_io"); copy_error(so_far), + failure then logger(logError, "send_content: failed to read input data_io"); copy_error(so_far), + time_out then logger(logError, "send_content: timeout reading input data_io"); copy_error(so_far), success(buffer) then - if sm_flush( buffer, weaken(target) ) is + if sm_flush( buffer, weaken(target), now, logger ) is { failure then copy_error(so_far), - success(_) then sm_copy_Data_IO_to_Stream(source, target, so_far + 65536) + success(_) then + progress_report(so_far + 65536); + sm_copy_Data_IO_to_Stream(source, target, start_time, so_far + 65536, progress_report, logger) }, truncated(buffer) then @@ -375,10 +396,12 @@ public define SendContentResult if len = 0 then copy_ok(so_far) else - if sm_flush( buffer, weaken(target) ) is + if sm_flush( buffer, weaken(target), now, logger ) is { failure then copy_error(so_far), - success(_) then copy_ok(so_far + len) + success(_) then + progress_report(so_far + len); + copy_ok(so_far + len) } }, reply(code, lines) then @@ -389,17 +412,20 @@ define SendContentResult sm_copy_Data_IO_List_to_Stream ( List(Data_IO) io_list, - RWStream target, - Int so_far + RWStream target, + Int start_time, + Int so_far, + (Int) -> One progress_report, + (LogLevel, String) -> One logger )= if io_list is { [] then copy_ok(so_far), [ h . t ] then if rewind(h)(unique) then - with result = sm_copy_Data_IO_to_Stream(h, target, 0), + with result = sm_copy_Data_IO_to_Stream(h, target, start_time, 0, progress_report, logger), if result is copy_ok(written) then - sm_copy_Data_IO_List_to_Stream(t, target, so_far + written) + sm_copy_Data_IO_List_to_Stream(t, target, start_time, so_far + written, progress_report, logger) else result else @@ -410,29 +436,31 @@ define Reply_Result send_content ( RWStream conn, - List(Data_IO) mail_part + List(Data_IO) mail_part, + (Int) -> One progress_report, + (LogLevel, String) -> One logger ) = with start_time = now, - if sm_copy_Data_IO_List_to_Stream(mail_part, conn, 0) is + if sm_copy_Data_IO_List_to_Stream(mail_part, conn, now, 0, progress_report, logger) is { smtp_reply(reply) then reply copy_error(written) then with finish_time = now, - logError(send_mail_log, "send_content: error sending data"); - logTrace(send_mail_log, send_mail_mask, "send_content: "+written+" bytes sent in "+finish_time - start_time+" second(s)"); + logger(logError, "send_content: error sending data"); + logger(logTrace, "send_content: "+written+" bytes sent in "+finish_time - start_time+" second(s)"); if written > 1024000 then - logInfo(send_mail_log, "send_content: may be due to over sized mail. Convert it to 452 error."); + logger(logInfo, "send_content: may be due to over sized mail. Convert it to 452 error."); reply(452, ["Error sending big email (more than 1 Mb)"]) else error, copy_ok(size) then if reliable_write(tcp(conn), [crlf_dot_crlf]) is { - failure then logError(send_mail_log, "send_content: error writing CRLF.CRLF"); error, + failure then logger(logError, "send_content: error writing CRLF.CRLF"); error, success(_) then with finish_time = (Int)now, - logTrace(send_mail_log, send_mail_mask, "mail sent "+size+" bytes in "+finish_time - start_time+" second(s)"); - receive_reply(conn) + logger(logTrace, "mail sent "+size+" bytes in "+finish_time - start_time+" second(s)"); + receive_reply(conn, logger) } }. @@ -441,52 +469,55 @@ define Reply_Result define One send_quit - ( - RWStream conn - ) = - if smtp_send_line(conn,"QUIT") is + ( + RWStream conn, + (LogLevel, String) -> One logger + ) = + if smtp_send_line(conn,"QUIT", logger) is { - failure then logWarning(send_mail_log, "error sending QUIT"), - success(_) then forget(receive_reply(conn)) + failure then logger(logWarning, "error sending QUIT"), + success(_) then forget(receive_reply(conn, logger)) }. define SendMailResult sm_MAIL_FROM ( - RWStream conn, - Send_Param param, - Send_Mail_Session sm_session + RWStream conn, + Send_Param param, + Send_Mail_Session sm_session, + (Int) -> One progress_report, + (LogLevel, String) -> One logger ) = with enhanced_status = sm_session.enhanced_status, //send MAIL FROM - if send_mail_from(conn, param.sender.str) is + if send_mail_from(conn, param.sender.str, logger) is { error then error, // already logged timeout then error, reply(code, lines) then if code = 250 then //send RCPT TO - if send_recipient(conn,param.recipient.str) is + if send_recipient(conn,param.recipient.str, logger) is { error then error, // already logged timeout then error, reply(code, lines) then if code = 250 then //send DATA - if send_data(conn) is + if send_data(conn, logger) is { error then error, // already logged timeout then error, reply(code, lines) then if code = 354 then - if send_content(conn, param.mail_parts) is + if send_content(conn, param.mail_parts, progress_report, logger) is { error then error, // already logged timeout then error, reply(code, lines) then if code = 250 then - send_quit(conn); - logTrace(send_mail_log,send_mail_mask,"SENT Mail FROM "+param.sender.str+" TO "+param.recipient.str); + send_quit(conn, logger); + logger(logTrace, "SENT Mail FROM "+param.sender.str+" TO "+param.recipient.str); ok else reply_handling(code, lines, enhanced_status) @@ -530,7 +561,8 @@ define SendMailResult RWStream conn, String login, String password, - Bool enhanced_status + Bool enhanced_status, + (LogLevel, String) -> One logger )= with plain_str = "AUTH PLAIN "+ to_string(base64_encode(to_byte_array("")+ @@ -538,11 +570,11 @@ define SendMailResult to_byte_array(login)+ constant_byte_array(1,0)+ to_byte_array(password))), - if smtp_send_line(conn, plain_str) is + if smtp_send_line(conn, plain_str, logger) is { failure then error, // already logged success(_) then - if receive_reply(conn) is + if receive_reply(conn, logger) is { error then error // already logged timeout then error, @@ -561,13 +593,14 @@ define SendMailResult List(String) auth_list, //list of available authentication method String login, String password, - Bool enhanced_status + Bool enhanced_status, + (LogLevel, String) -> One logger )= if member(auth_list, "PLAIN", insensitive_equal) then - do_auth_plain(conn, login, password, enhanced_status) + do_auth_plain(conn, login, password, enhanced_status, logger) else - logError(send_mail_log, "do_login no PLAIN method available"); error. + logger(logError, "do_login no PLAIN method available"); error. define SendMailResult do_auth @@ -575,7 +608,8 @@ define SendMailResult RWStream conn, //tcp connection List(String) lines, //this is the reply lines provided in response to EHLO command Send_Auth auth, //authentication method to use for that session - Bool enhanced_status + Bool enhanced_status, + (LogLevel, String) -> One logger )= if auth is { @@ -584,8 +618,8 @@ define SendMailResult with auth_list = get_auth_method(lines,[]), if auth_list is { - [] then logError(send_mail_log, "do_auth error");error, - [_ . _] then do_login(conn, auth_list, user, password, enhanced_status) + [] then logger(logError, "do_auth error");error, + [_ . _] then do_login(conn, auth_list, user, password, enhanced_status, logger) } }. @@ -596,7 +630,8 @@ define Send_Mail_Session make_sm_session ( List(String) lines, //lines given in ehlo stage - Send_Param param + Send_Param param, + (LogLevel, String) -> One logger )= //looking for ENHANCEDSTATUSCODES with session_enhanced_status = has_smtp_extension(lines, enhanced_status_codes), @@ -609,7 +644,7 @@ define Send_Mail_Session { failure then 0, success(value) then - logTrace(send_mail_log, send_mail_mask, "smtp server with SIZE "+value); + logger(logTrace, "smtp server with SIZE "+value); value } }, @@ -617,41 +652,43 @@ define Send_Mail_Session Apply the whole protocol: - //TODO we can have a list of part file in param type. This can be very useful when we want - //to send file as mailing. For the mailing, in each mail, only the header is different of the - //other mails. The body part is the same. Then, the mailing sender can generate the header in one - //file and keep the body in other file wich can be given to send_mail function as last file in the list + // we can have a list of part file in param type. This can be very useful when we want + // to send file as mailing. For the mailing, in each mail, only the header is different of the + // other mails. The body part is the same. Then, the mailing sender can generate the header in one + // file and keep the body in other file wich can be given to send_mail function as last file in the list public define SendMailResult send_mail ( RWStream conn, Send_Param param, (Send_Mail_Session, Send_Param) -> Result(SendMailResult, Send_Param) prepare_mail_callback, + (Int) -> One progress_report, + (LogLevel, String) -> One logger ) = // // Our connection to the SMTP server is opened. We just have to apply the protocol. // - if receive_reply(conn) is + if receive_reply(conn, logger) is { error then error,// already logged timeout then error, reply(code, lines) then if code = 220 then //send EHLO - if send_ehlo(conn, param.host_name.str) is //"mail."+force_Type(get_main_domain(db), "mailfountain.net")) is + if send_ehlo(conn, param.host_name.str, logger) is //"mail."+force_Type(get_main_domain(db), "mailfountain.net")) is { error then error, // already logged timeout then error, reply(code, lines) then if code = 250 then // parse all available options here, and build a smtp context - with sm_session = make_sm_session(lines, param), - with auth_result = do_auth(conn, lines, param.auth, sm_session.enhanced_status), + with sm_session = make_sm_session(lines, param, logger), + with auth_result = do_auth(conn, lines, param.auth, sm_session.enhanced_status, logger), if auth_result = ok then if prepare_mail_callback(sm_session, param) is { error(result) then result, - ok(new_param) then sm_MAIL_FROM(conn, new_param, sm_session) + ok(new_param) then sm_MAIL_FROM(conn, new_param, sm_session, progress_report, logger) } else auth_result @@ -661,7 +698,7 @@ public define SendMailResult else if code = 550 then //some server answer "550 5.7.1 Client host rejected: cannot find your reverse hostname, [88.181.64.17]" //before anything, then we try to extract the enhanced status if exists for relaying the mail with the ISP - logDebug(send_mail_log, "Converting a 550 error for connection to a 432 error as distant server may be temporary overloaded."); + logger(logDebug, "Converting a 550 error for connection to a 432 error as distant server may be temporary overloaded."); reply_handling(432,lines,true) else reply_handling(code,lines,true) @@ -672,8 +709,26 @@ public define SendMailResult ( RWStream conn, Send_Param param, + (Send_Mail_Session, Send_Param) -> Result(SendMailResult, Send_Param) prepare_mail_callback, + ) = + send_mail(conn, + param, + prepare_mail_callback, + (Int _) |-> unique, + (LogLevel level, String txt) |-> if level is logTrace then logTrace(send_mail_log, send_mail_mask, txt) + else log(level, send_mail_log, txt) + ). + +public define SendMailResult + send_mail + ( + RWStream conn, + Send_Param param, ) = - send_mail(conn, param, (Send_Mail_Session sm_session, Send_Param param) |-> ok(param)). + send_mail(conn, + param, + (Send_Mail_Session sm_session, Send_Param param) |-> ok(param) + ). public define SendMailResult send_mail diff --git a/calexium_lib/net_services_protocols/logger_service.anubis b/calexium_lib/net_services_protocols/logger_service.anubis index d0f0cee..ef844d8 100644 --- a/calexium_lib/net_services_protocols/logger_service.anubis +++ b/calexium_lib/net_services_protocols/logger_service.anubis @@ -305,6 +305,14 @@ define Message . public define One + log( + LogLevel level, + String logger_name, + String log_string + )= + local_net_logger("127.0.0.1", create_log_msg(logger_name, log_string, get_log_level_value(level))). + +public define One logNone( String logger_name, String log_string -- libgit2 0.21.4