Commit 90524aaa7f223f9be6524c43fe03d18a3817fc0e
1 parent
5b8d2d40
Added send_mail() function
Showing
3 changed files
with
982 additions
and
0 deletions
Show diff stats
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * User: David RENE | ||
| 4 | + * Date: 30/05/2008 | ||
| 5 | + * Time: 03:30 | ||
| 6 | + * (c) Calexium | ||
| 7 | + * | ||
| 8 | + */ | ||
| 9 | + | ||
| 10 | + | ||
| 11 | + | ||
| 12 | + | ||
| 13 | +read tools/basis.anubis | ||
| 14 | + | ||
| 15 | + | ||
| 16 | + We want to test email addresses. Below is a regular expression for that | ||
| 17 | + purpose. Actually, this expression is too nave. A real one would be more complicated. | ||
| 18 | + | ||
| 19 | + | ||
| 20 | +read tools/streams.anubis | ||
| 21 | +read system/string.anubis | ||
| 22 | + | ||
| 23 | +type LM_TokenOrError_EnhancedStatus: | ||
| 24 | + end_of_file, | ||
| 25 | + token(List(Word8)), | ||
| 26 | + error. | ||
| 27 | + | ||
| 28 | +type LM_LexerState_EnhancedStatus: ... | ||
| 29 | + | ||
| 30 | +type LM_Match_EnhancedStatus: | ||
| 31 | + match(List(Word8) characters, | ||
| 32 | + (LM_LexerState_EnhancedStatus,List(Word8)) -> (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) action, | ||
| 33 | + Bool aeol, | ||
| 34 | + Bool abol). | ||
| 35 | + | ||
| 36 | +type LM_LexerState_EnhancedStatus: | ||
| 37 | + lexer_state(Stream input, | ||
| 38 | + List(Word8) unput, // in natural order | ||
| 39 | + List(Word8) more, // in reverse order | ||
| 40 | + LM_LexerState_EnhancedStatus -> (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) lexer, | ||
| 41 | + Bool at_end_of_line, | ||
| 42 | + Bool at_beginning_of_line, | ||
| 43 | + Maybe(LM_Match_EnhancedStatus) match). | ||
| 44 | + | ||
| 45 | +define LM_LexerState_EnhancedStatus | ||
| 46 | + lm_initial_state | ||
| 47 | + ( | ||
| 48 | + Stream input, | ||
| 49 | + LM_LexerState_EnhancedStatus -> (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) lexer | ||
| 50 | + ) = | ||
| 51 | + lexer_state(input,[],[],lexer,false,true,failure). | ||
| 52 | + | ||
| 53 | +define (LM_LexerState_EnhancedStatus,Word8) | ||
| 54 | + lm_next_char | ||
| 55 | + ( | ||
| 56 | + LM_LexerState_EnhancedStatus ls | ||
| 57 | + ) = | ||
| 58 | + if ls is lexer_state(input,unput,more,lex,aeol,abol,match) then | ||
| 59 | + if unput is | ||
| 60 | + { | ||
| 61 | + [ ] then | ||
| 62 | + if read_byte(input) is | ||
| 63 | + { | ||
| 64 | + failure then | ||
| 65 | + (lexer_state(input, | ||
| 66 | + [], | ||
| 67 | + more, | ||
| 68 | + lex, | ||
| 69 | + true, | ||
| 70 | + aeol, | ||
| 71 | + match), | ||
| 72 | + -1), | ||
| 73 | + | ||
| 74 | + success(c) then | ||
| 75 | + (lexer_state(input, | ||
| 76 | + [], | ||
| 77 | + [c . more], | ||
| 78 | + lex, | ||
| 79 | + c = '\n', | ||
| 80 | + aeol, | ||
| 81 | + match), | ||
| 82 | + c), | ||
| 83 | + }, | ||
| 84 | + | ||
| 85 | + [h . t] then | ||
| 86 | + (lexer_state(input, | ||
| 87 | + t, | ||
| 88 | + [h . more], | ||
| 89 | + lex, | ||
| 90 | + h = '\n', | ||
| 91 | + aeol, | ||
| 92 | + match), | ||
| 93 | + h) | ||
| 94 | + }. | ||
| 95 | + | ||
| 96 | +define LM_LexerState_EnhancedStatus | ||
| 97 | + lm_remember_match | ||
| 98 | + ( | ||
| 99 | + (LM_LexerState_EnhancedStatus,List(Word8)) -> (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) action, | ||
| 100 | + LM_LexerState_EnhancedStatus ls | ||
| 101 | + ) = | ||
| 102 | + if ls is lexer_state(input,unput,more,lex,aeol,abol,m) then | ||
| 103 | + with chars = if m is | ||
| 104 | + { | ||
| 105 | + failure then [], | ||
| 106 | + success(match) then | ||
| 107 | + if match is match(chars,_,_,_) then chars | ||
| 108 | + }, | ||
| 109 | + lexer_state(input, | ||
| 110 | + unput, | ||
| 111 | + [], | ||
| 112 | + lex, | ||
| 113 | + aeol, | ||
| 114 | + abol, | ||
| 115 | + success(match(append(more,chars),action,aeol,abol))). | ||
| 116 | + | ||
| 117 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 118 | + lm_find_match | ||
| 119 | + ( | ||
| 120 | + LM_LexerState_EnhancedStatus ls | ||
| 121 | + ) = | ||
| 122 | + if ls is lexer_state(input,unput,more,lex,_,_,mb_m) then | ||
| 123 | + if mb_m is | ||
| 124 | + { | ||
| 125 | + failure then (ls,error), | ||
| 126 | + success(m) then | ||
| 127 | + if m is match(chars,action,aeol,abol) then | ||
| 128 | + action(lexer_state(input, | ||
| 129 | + append(more,unput), | ||
| 130 | + [], | ||
| 131 | + lex, | ||
| 132 | + aeol, | ||
| 133 | + abol, | ||
| 134 | + failure), | ||
| 135 | + reverse(chars)) | ||
| 136 | + }. | ||
| 137 | + | ||
| 138 | +define LM_LexerState_EnhancedStatus | ||
| 139 | + change_lexer | ||
| 140 | + ( | ||
| 141 | + LM_LexerState_EnhancedStatus ls, | ||
| 142 | + LM_LexerState_EnhancedStatus -> (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) lex | ||
| 143 | + ) = | ||
| 144 | + if ls is lexer_state(input,unput,more,_,aeol,abol,mb_m) then | ||
| 145 | + lexer_state(input,unput,more,lex,aeol,abol,mb_m). | ||
| 146 | + | ||
| 147 | +define LM_LexerState_EnhancedStatus | ||
| 148 | + clear_abol | ||
| 149 | + ( | ||
| 150 | + LM_LexerState_EnhancedStatus ls | ||
| 151 | + ) = | ||
| 152 | + if ls is lexer_state(input,unput,more,lex,aeol,_,mb_m) then | ||
| 153 | + lexer_state(input,unput,more,lex,aeol,false,mb_m). | ||
| 154 | + | ||
| 155 | + Declarations of all lexer states. | ||
| 156 | + | ||
| 157 | + | ||
| 158 | +public define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester(LM_LexerState_EnhancedStatus ls). | ||
| 159 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_state_1(LM_LexerState_EnhancedStatus ls). | ||
| 160 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_state_2(LM_LexerState_EnhancedStatus ls). | ||
| 161 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_state_3(LM_LexerState_EnhancedStatus ls). | ||
| 162 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_state_4(LM_LexerState_EnhancedStatus ls). | ||
| 163 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_state_5(LM_LexerState_EnhancedStatus ls). | ||
| 164 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_state_6(LM_LexerState_EnhancedStatus ls). | ||
| 165 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_state_7(LM_LexerState_EnhancedStatus ls). | ||
| 166 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_state_8(LM_LexerState_EnhancedStatus ls). | ||
| 167 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_action_0 | ||
| 168 | + (LM_LexerState_EnhancedStatus ls, List(Word8) text). | ||
| 169 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_action_1 | ||
| 170 | + (LM_LexerState_EnhancedStatus ls, List(Word8) text). | ||
| 171 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) enhanced_status_tester_action_2 | ||
| 172 | + (LM_LexerState_EnhancedStatus ls, List(Word8) text). | ||
| 173 | + | ||
| 174 | + | ||
| 175 | + Lexer states. | ||
| 176 | + | ||
| 177 | + | ||
| 178 | +public define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 179 | + enhanced_status_tester | ||
| 180 | + ( | ||
| 181 | + LM_LexerState_EnhancedStatus ls, | ||
| 182 | + ) = | ||
| 183 | + if at_beginning_of_line(ls) | ||
| 184 | + then enhanced_status_tester_state_3(clear_abol(ls)) else | ||
| 185 | + if lm_next_char(ls) is (ls,c) then | ||
| 186 | + if c = -1 then enhanced_status_tester_state_1(ls) else | ||
| 187 | + if c = 50 then enhanced_status_tester_state_4(ls) else | ||
| 188 | + if (52 +=< c & c +=< 53) then enhanced_status_tester_state_4(ls) else | ||
| 189 | + lm_find_match(ls). | ||
| 190 | + | ||
| 191 | + | ||
| 192 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 193 | + enhanced_status_tester_state_1 | ||
| 194 | + ( | ||
| 195 | + LM_LexerState_EnhancedStatus ls, | ||
| 196 | + ) = | ||
| 197 | + with ls = lm_remember_match(enhanced_status_tester_action_2,ls), | ||
| 198 | + if lm_next_char(ls) is (ls,c) then | ||
| 199 | + lm_find_match(ls). | ||
| 200 | + | ||
| 201 | + | ||
| 202 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 203 | + enhanced_status_tester_state_2 | ||
| 204 | + ( | ||
| 205 | + LM_LexerState_EnhancedStatus ls, | ||
| 206 | + ) = | ||
| 207 | + if lm_next_char(ls) is (ls,c) then | ||
| 208 | + lm_find_match(ls). | ||
| 209 | + | ||
| 210 | + | ||
| 211 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 212 | + enhanced_status_tester_state_3 | ||
| 213 | + ( | ||
| 214 | + LM_LexerState_EnhancedStatus ls, | ||
| 215 | + ) = | ||
| 216 | + with ls = lm_remember_match(enhanced_status_tester_action_1,ls), | ||
| 217 | + if lm_next_char(ls) is (ls,c) then | ||
| 218 | + lm_find_match(ls). | ||
| 219 | + | ||
| 220 | + | ||
| 221 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 222 | + enhanced_status_tester_state_4 | ||
| 223 | + ( | ||
| 224 | + LM_LexerState_EnhancedStatus ls, | ||
| 225 | + ) = | ||
| 226 | + if lm_next_char(ls) is (ls,c) then | ||
| 227 | + if c = 46 then enhanced_status_tester_state_5(ls) else | ||
| 228 | + if c = 50 then enhanced_status_tester_state_4(ls) else | ||
| 229 | + if (52 +=< c & c +=< 53) then enhanced_status_tester_state_4(ls) else | ||
| 230 | + lm_find_match(ls). | ||
| 231 | + | ||
| 232 | + | ||
| 233 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 234 | + enhanced_status_tester_state_5 | ||
| 235 | + ( | ||
| 236 | + LM_LexerState_EnhancedStatus ls, | ||
| 237 | + ) = | ||
| 238 | + if lm_next_char(ls) is (ls,c) then | ||
| 239 | + if c = 46 then enhanced_status_tester_state_5(ls) else | ||
| 240 | + if (48 +=< c & c +=< 57) then enhanced_status_tester_state_6(ls) else | ||
| 241 | + lm_find_match(ls). | ||
| 242 | + | ||
| 243 | + | ||
| 244 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 245 | + enhanced_status_tester_state_6 | ||
| 246 | + ( | ||
| 247 | + LM_LexerState_EnhancedStatus ls, | ||
| 248 | + ) = | ||
| 249 | + if lm_next_char(ls) is (ls,c) then | ||
| 250 | + if c = 46 then enhanced_status_tester_state_7(ls) else | ||
| 251 | + if (48 +=< c & c +=< 57) then enhanced_status_tester_state_6(ls) else | ||
| 252 | + lm_find_match(ls). | ||
| 253 | + | ||
| 254 | + | ||
| 255 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 256 | + enhanced_status_tester_state_7 | ||
| 257 | + ( | ||
| 258 | + LM_LexerState_EnhancedStatus ls, | ||
| 259 | + ) = | ||
| 260 | + if lm_next_char(ls) is (ls,c) then | ||
| 261 | + if c = 46 then enhanced_status_tester_state_7(ls) else | ||
| 262 | + if (48 +=< c & c +=< 57) then enhanced_status_tester_state_8(ls) else | ||
| 263 | + lm_find_match(ls). | ||
| 264 | + | ||
| 265 | + | ||
| 266 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 267 | + enhanced_status_tester_state_8 | ||
| 268 | + ( | ||
| 269 | + LM_LexerState_EnhancedStatus ls, | ||
| 270 | + ) = | ||
| 271 | + with ls = lm_remember_match(enhanced_status_tester_action_0,ls), | ||
| 272 | + if lm_next_char(ls) is (ls,c) then | ||
| 273 | + if (48 +=< c & c +=< 57) then enhanced_status_tester_state_8(ls) else | ||
| 274 | + lm_find_match(ls). | ||
| 275 | + | ||
| 276 | + | ||
| 277 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 278 | + enhanced_status_tester_action_0 | ||
| 279 | + ( | ||
| 280 | + LM_LexerState_EnhancedStatus ls, | ||
| 281 | + List(Word8) text | ||
| 282 | + ) = | ||
| 283 | + (ls,token(text)). | ||
| 284 | + | ||
| 285 | + | ||
| 286 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 287 | + enhanced_status_tester_action_1 | ||
| 288 | + ( | ||
| 289 | + LM_LexerState_EnhancedStatus ls, | ||
| 290 | + List(Word8) text | ||
| 291 | + ) = | ||
| 292 | + /* default action */ enhanced_status_tester(ls) . | ||
| 293 | + | ||
| 294 | + | ||
| 295 | +define (LM_LexerState_EnhancedStatus,LM_TokenOrError_EnhancedStatus) | ||
| 296 | + enhanced_status_tester_action_2 | ||
| 297 | + ( | ||
| 298 | + LM_LexerState_EnhancedStatus ls, | ||
| 299 | + List(Word8) text | ||
| 300 | + ) = | ||
| 301 | + /* default action */ (ls,end_of_file) . | ||
| 302 | + | ||
| 303 | + | ||
| 304 | + Since '@' is a normal character, a string needs to contain exactly one '@' for being | ||
| 305 | + accepted. What is accepted before and after this '@' is described by: | ||
| 306 | + | ||
| 307 | + [a-zA-Z]+(\.[a-zA-Z]+)* | ||
| 308 | + | ||
| 309 | + The first part: [a-zA-Z]+ means ``at least one letter''. The last part: (\.[a-zA-Z]+)* | ||
| 310 | + means: ``a dot followed by at least one letter, and this may be repeated any number of | ||
| 311 | + times (including zero)''. | ||
| 312 | + | ||
| 313 | + | ||
| 314 | + This part of the source file is the 'postambule' (just Anubis text, which is copied 'as | ||
| 315 | + is' to the lexer_maker output file). | ||
| 316 | + | ||
| 317 | + The above stuff produces a function named 'email_tester' into the lexer_maker output | ||
| 318 | + file. This function is used below: | ||
| 319 | + | ||
| 320 | +define Maybe(String) | ||
| 321 | + _extract_enhanced_status | ||
| 322 | + ( | ||
| 323 | + List(String) lines | ||
| 324 | + ) = | ||
| 325 | + if lines is | ||
| 326 | + { | ||
| 327 | + [ ] then failure, | ||
| 328 | + [h . t] then | ||
| 329 | + with ls = lexer_state(make_stream(h),[],[],enhanced_status_tester,true,false,failure), | ||
| 330 | + if enhanced_status_tester(ls) is (_,result) then | ||
| 331 | + if result is | ||
| 332 | + { | ||
| 333 | + end_of_file then _extract_enhanced_status(t), | ||
| 334 | + token(tok) then success(implode(tok)) | ||
| 335 | + error then _extract_enhanced_status(t) | ||
| 336 | + } | ||
| 337 | + }. | ||
| 338 | + | ||
| 339 | +public define Maybe(String) | ||
| 340 | + extract_enhanced_status | ||
| 341 | + ( | ||
| 342 | + List(String) lines | ||
| 343 | + ) = | ||
| 344 | + if lines is | ||
| 345 | + { | ||
| 346 | + [ ] then failure, | ||
| 347 | + [h . t] then | ||
| 348 | + if _extract_enhanced_status(split_by_token(h, ' ')) is | ||
| 349 | + { | ||
| 350 | + failure then extract_enhanced_status(t), | ||
| 351 | + success(r) then success(r) | ||
| 352 | + } | ||
| 353 | + }. | ||
| 354 | + | ||
| 355 | + |
| 1 | +/* | ||
| 2 | + * Created by PyramIDE. | ||
| 3 | + * User: ricard | ||
| 4 | + * Date: 22/12/2008 | ||
| 5 | + * Time: 17:49 | ||
| 6 | + * | ||
| 7 | + */ | ||
| 8 | + | ||
| 9 | + | ||
| 10 | +read tools/basis.anubis | ||
| 11 | +read tools/connections.anubis | ||
| 12 | +read tools/findstring.anubis | ||
| 13 | +read tools/printable_tree.anubis | ||
| 14 | +read data_base/sqlite.anubis | ||
| 15 | +read network/tools.anubis | ||
| 16 | +read system/files.anubis | ||
| 17 | +read system/string.anubis | ||
| 18 | +read system/data_io.anubis | ||
| 19 | +read system/logger.anubis | ||
| 20 | + | ||
| 21 | +read calexium_lib/net_services_protocols/logger_service.anubis | ||
| 22 | + | ||
| 23 | +read lexers/enhanced_status.anubis | ||
| 24 | +read smtp_server_extensions.anubis | ||
| 25 | + | ||
| 26 | +public define String send_mail_log = "SendMail". | ||
| 27 | +public define LogMask send_mail_mask = logMask("send_mail"). | ||
| 28 | + | ||
| 29 | +define Int smtp_time_out = 300. //5 minutes of timeout | ||
| 30 | +define ByteArray crlf_dot_crlf = to_byte_array(implode([13,10,'.',13,10])). | ||
| 31 | + | ||
| 32 | +public type SendMailResult: | ||
| 33 | + ok, | ||
| 34 | + error, | ||
| 35 | + reply(Int code, String enhanced_status, List(String) lines). | ||
| 36 | + | ||
| 37 | +public type Send_Status: | ||
| 38 | + ready, //ready to send | ||
| 39 | + finished, //the mail is delivered correctly | ||
| 40 | + timeout, //the mail was not sent in enough time, this is a permanent error | ||
| 41 | + general_error, | ||
| 42 | + error(Int code, String enhanced_status, String text), //this is SMTP permanent error 5xx, we can't deliver the mail | ||
| 43 | + warning(Int code, String enhanced_status, String text), //this is SMTP temporary error 4xx, we can retry with same mail later | ||
| 44 | + in_progress. //We are in sending process, it's like a lock | ||
| 45 | + | ||
| 46 | +public type Str_HostName: | ||
| 47 | + str_host_name(String str). | ||
| 48 | + | ||
| 49 | +public type Str_Sender: | ||
| 50 | + str_sender(String str). | ||
| 51 | + | ||
| 52 | +public type Str_Recipient: | ||
| 53 | + str_recipient(String str). | ||
| 54 | + | ||
| 55 | +public type Str_UID: | ||
| 56 | + str_UID(String str). | ||
| 57 | + | ||
| 58 | +public type Send_Auth: | ||
| 59 | + none, | ||
| 60 | + login( String login, | ||
| 61 | + String password). | ||
| 62 | + | ||
| 63 | +public type Send_Param: | ||
| 64 | + send_param( Str_HostName host_name, | ||
| 65 | + Str_Sender sender, | ||
| 66 | + Str_Recipient recipient, | ||
| 67 | + List(Data_IO) mail_parts, | ||
| 68 | + Send_Auth auth, | ||
| 69 | + Str_UID mail_uid | ||
| 70 | + ). | ||
| 71 | + | ||
| 72 | +public type Send_Timeout: | ||
| 73 | + send_timeout(Int when). | ||
| 74 | + | ||
| 75 | +public type Send_Tries: | ||
| 76 | + send_tries(Int number). | ||
| 77 | + | ||
| 78 | + | ||
| 79 | + | ||
| 80 | + | ||
| 81 | +public type Send_Mail_Session: | ||
| 82 | + send_mail_session( | ||
| 83 | + Bool enhanced_status, | ||
| 84 | + Int size | ||
| 85 | + ). | ||
| 86 | + | ||
| 87 | +define Maybe(String) | ||
| 88 | + receive_command | ||
| 89 | + ( | ||
| 90 | + RWStream conn, | ||
| 91 | + List(Word8) so_far, | ||
| 92 | + Word8 previous | ||
| 93 | + ) = | ||
| 94 | + if read_network_byte(weaken(conn), smtp_time_out) is | ||
| 95 | + { | ||
| 96 | + failure then logTrace(send_mail_log, send_mail_mask, "receive_command read_network_byte 0 failure");failure, | ||
| 97 | + timeout then logWarning(send_mail_log, "[send_mail] receive_command timeout"); failure, | ||
| 98 | + success(c) then | ||
| 99 | + if c = 10 & previous = 13 then // <LF> | ||
| 100 | + with result = implode(reverse(so_far)), | ||
| 101 | + logTrace(send_mail_log, send_mail_mask,"<-S-"+result); | ||
| 102 | + success(result) | ||
| 103 | + else | ||
| 104 | + if previous = 13 then | ||
| 105 | + receive_command(conn, so_far, c) | ||
| 106 | + else | ||
| 107 | + receive_command(conn, [previous . so_far], c) | ||
| 108 | + }. | ||
| 109 | + | ||
| 110 | +define Maybe(String) | ||
| 111 | + receive_command | ||
| 112 | + ( | ||
| 113 | + RWStream conn, | ||
| 114 | + List(Word8) so_far, | ||
| 115 | + ) = | ||
| 116 | + if read_network_byte(weaken(conn), smtp_time_out) is | ||
| 117 | + { | ||
| 118 | + failure then logTrace(send_mail_log, send_mail_mask, "receive_command read_network_byte 0 failure"); failure, | ||
| 119 | + timeout then logWarning(send_mail_log, "[send_mail] receive_command timeout"); failure, | ||
| 120 | + success(c) then receive_command(conn,[], c) | ||
| 121 | + }. | ||
| 122 | + | ||
| 123 | + | ||
| 124 | +type Reply_Result: | ||
| 125 | + error, | ||
| 126 | + reply(Int code, List(String) lines). | ||
| 127 | + | ||
| 128 | +define SendMailResult | ||
| 129 | + reply_handling | ||
| 130 | + ( | ||
| 131 | + Int code, | ||
| 132 | + List(String) lines, | ||
| 133 | + Bool enhanced_status | ||
| 134 | + ) = | ||
| 135 | + with result = | ||
| 136 | + if enhanced_status then | ||
| 137 | + if extract_enhanced_status(lines) is | ||
| 138 | + { | ||
| 139 | + failure then "", | ||
| 140 | + success(enh) then enh | ||
| 141 | + } | ||
| 142 | + else | ||
| 143 | + "", | ||
| 144 | + reply(code, result, lines). | ||
| 145 | + | ||
| 146 | + | ||
| 147 | +define Reply_Result | ||
| 148 | + receive_reply | ||
| 149 | + ( | ||
| 150 | + RWStream conn, | ||
| 151 | + List(String) so_far | ||
| 152 | + ) = | ||
| 153 | + if receive_command(conn, []) is | ||
| 154 | + { | ||
| 155 | + failure then logError(send_mail_log, "receive_reply: error receiving command"); error, | ||
| 156 | + success(line) then | ||
| 157 | + //check if we must read another line by presence of hyphen after the reply code | ||
| 158 | + //220-bla bla bla | ||
| 159 | + //220 end of bla bla | ||
| 160 | + if length(line) =< 3 then | ||
| 161 | + if decimal_scan(line) is | ||
| 162 | + { | ||
| 163 | + failure then logError(send_mail_log, "receive_reply: can't extract reply code from '" + line + "'"); error, | ||
| 164 | + success(code) then reply(code, reverse(so_far)) | ||
| 165 | + } | ||
| 166 | + else if nth(3, line) is | ||
| 167 | + { | ||
| 168 | + failure then logError(send_mail_log, "receive_reply: error getting 4th character from '" + line +"'"); error, | ||
| 169 | + success(char) then | ||
| 170 | + if char = '-' then | ||
| 171 | + receive_reply(conn, [line . so_far]) | ||
| 172 | + else | ||
| 173 | + //decode the code | ||
| 174 | + if sub_string(line, 0, 3) is | ||
| 175 | + { | ||
| 176 | + failure then logError(send_mail_log, "receive_reply: error extracting reply code from '" + line+"'"); error, // should never occure | ||
| 177 | + success(code_str) then | ||
| 178 | + if decimal_scan(code_str) is | ||
| 179 | + { | ||
| 180 | + failure then logError(send_mail_log, "receive_reply: can't extract reply code from '" + code_str + "'"); error, //unreadable code | ||
| 181 | + success(code) then reply(code , reverse([line . so_far])) | ||
| 182 | + } | ||
| 183 | + } | ||
| 184 | + } | ||
| 185 | + }. | ||
| 186 | + | ||
| 187 | +define Reply_Result | ||
| 188 | + receive_reply | ||
| 189 | + ( | ||
| 190 | + RWStream conn, | ||
| 191 | + ) = | ||
| 192 | + receive_reply(conn, []). | ||
| 193 | + | ||
| 194 | + | ||
| 195 | + Sending a piece of text (String) from the begining. | ||
| 196 | + | ||
| 197 | +define Maybe(One) | ||
| 198 | + smtp_send_line | ||
| 199 | + ( | ||
| 200 | + RWStream conn, | ||
| 201 | + String text | ||
| 202 | + ) = | ||
| 203 | + if reliable_write(tcp(conn),[text + crlf]) is | ||
| 204 | + { | ||
| 205 | + failure then logError(send_mail_log, "smtp_send_line: error writing '" + text + "'"); failure, | ||
| 206 | + success(_) then | ||
| 207 | + logTrace(send_mail_log,send_mail_mask, "-C->"+text);success(unique) | ||
| 208 | + } . | ||
| 209 | + | ||
| 210 | + Because of attachements, we may have to manipulate very big pieces of text. It would be | ||
| 211 | + unreasonable to concatenate all pieces into a single String. Hence, we use the | ||
| 212 | + following type: | ||
| 213 | + | ||
| 214 | +type StringTree: | ||
| 215 | + [ ], | ||
| 216 | + str_tree(String,StringTree), | ||
| 217 | + ba_tree(ByteArray,StringTree), | ||
| 218 | + tree_tree(StringTree,StringTree). | ||
| 219 | + | ||
| 220 | +define StringTree [String s . StringTree t] = str_tree(s,t). | ||
| 221 | +define StringTree [ByteArray s . StringTree t] = ba_tree(s,t). | ||
| 222 | +define StringTree [StringTree s . StringTree t] = tree_tree(s,t). | ||
| 223 | + | ||
| 224 | + Try to send the 'HELO' command and get the reply. Return 'true' if you cannot. | ||
| 225 | + | ||
| 226 | +define Reply_Result | ||
| 227 | + send_ehlo | ||
| 228 | + ( | ||
| 229 | + RWStream conn, | ||
| 230 | + String our_host_name | ||
| 231 | + ) = | ||
| 232 | + if smtp_send_line(conn,"EHLO "+our_host_name) is | ||
| 233 | + { | ||
| 234 | + failure then logError(send_mail_log, "send_ehlo: error sending EHLO"); error, | ||
| 235 | + success(_) then | ||
| 236 | + with rep = receive_reply(conn), | ||
| 237 | + if rep is | ||
| 238 | + { | ||
| 239 | + error then logError(send_mail_log, "send_ehlo: can't get reply"); error, | ||
| 240 | + reply(code, lines) then | ||
| 241 | + //we manage the 500 error, that mean the remote server is not ESMTP | ||
| 242 | + //hence we try we with HELO, the old manner RFC 821 | ||
| 243 | + if code = 500 | code = 502 then | ||
| 244 | + if smtp_send_line(conn,"HELO "+our_host_name) is | ||
| 245 | + { | ||
| 246 | + failure then logError(send_mail_log, "send_ehlo: error sending HELO"); error, | ||
| 247 | + success(_) then receive_reply(conn) | ||
| 248 | + } | ||
| 249 | + else | ||
| 250 | + rep | ||
| 251 | + } | ||
| 252 | + }. | ||
| 253 | + | ||
| 254 | + The same one for 'MAIL FROM': | ||
| 255 | + | ||
| 256 | +define Reply_Result | ||
| 257 | + send_mail_from | ||
| 258 | + ( | ||
| 259 | + RWStream conn, | ||
| 260 | + String sender | ||
| 261 | + ) = | ||
| 262 | + if smtp_send_line(conn,"MAIL FROM:"+sender) is | ||
| 263 | + { | ||
| 264 | + failure then logError(send_mail_log, "error sending 'MAIL FROM:" + sender + "'"); error, | ||
| 265 | + success(_) then receive_reply(conn) | ||
| 266 | + }. | ||
| 267 | + | ||
| 268 | + The same one for 'RCPT TO': | ||
| 269 | + | ||
| 270 | +define Reply_Result | ||
| 271 | + send_recipient | ||
| 272 | + ( | ||
| 273 | + RWStream conn, | ||
| 274 | + String recipient, | ||
| 275 | + ) = | ||
| 276 | + if smtp_send_line(conn,"RCPT TO:<"+recipient+">") is | ||
| 277 | + { | ||
| 278 | + failure then logError(send_mail_log, "error sending 'RCPT TO:<"+recipient+">'"); error, | ||
| 279 | + success(_) then receive_reply(conn) | ||
| 280 | + }. | ||
| 281 | + | ||
| 282 | + | ||
| 283 | + Try to send 'DATA' and get the reply. Answer 'true' if you cannot. | ||
| 284 | + | ||
| 285 | +define Reply_Result | ||
| 286 | + send_data | ||
| 287 | + ( | ||
| 288 | + RWStream conn | ||
| 289 | + ) = | ||
| 290 | + if smtp_send_line(conn,"DATA") is | ||
| 291 | + { | ||
| 292 | + failure then logError(send_mail_log, "error sending DATA"); error, | ||
| 293 | + success(_) then receive_reply(conn) | ||
| 294 | + }. | ||
| 295 | + | ||
| 296 | + | ||
| 297 | + | ||
| 298 | + The same for the content of the message. We need base64 encoding. | ||
| 299 | + | ||
| 300 | +read tools/base64.anubis | ||
| 301 | + | ||
| 302 | +define Reply_Result | ||
| 303 | + send_content | ||
| 304 | + ( | ||
| 305 | + RWStream conn, | ||
| 306 | + List(Data_IO) mail_part | ||
| 307 | + ) = | ||
| 308 | + //open the message file from the drive | ||
| 309 | + if copy_Data_IO_List_to_Stream(mail_part, weaken(conn)) is copy_ok(_) then | ||
| 310 | + if reliable_write(tcp(conn), [crlf_dot_crlf]) is | ||
| 311 | + { | ||
| 312 | + failure then logError(send_mail_log, "send_content: error writing CRLF.CRLF"); error, | ||
| 313 | + success(_) then receive_reply(conn) | ||
| 314 | + } | ||
| 315 | + else | ||
| 316 | + logError(send_mail_log, "send_content: error sending data"); error. | ||
| 317 | + | ||
| 318 | + | ||
| 319 | + Almost the same for 'QUIT': | ||
| 320 | + | ||
| 321 | +define One | ||
| 322 | + send_quit | ||
| 323 | + ( | ||
| 324 | + RWStream conn | ||
| 325 | + ) = | ||
| 326 | + if smtp_send_line(conn,"QUIT") is | ||
| 327 | + { | ||
| 328 | + failure then logWarning(send_mail_log, "error sending QUIT"), | ||
| 329 | + success(_) then forget(receive_reply(conn)) | ||
| 330 | + }. | ||
| 331 | + | ||
| 332 | +define SendMailResult | ||
| 333 | + sm_MAIL_FROM | ||
| 334 | + ( | ||
| 335 | + RWStream conn, | ||
| 336 | + Send_Param param, | ||
| 337 | + Send_Mail_Session sm_session | ||
| 338 | + ) = | ||
| 339 | + with enhanced_status = sm_session.enhanced_status, | ||
| 340 | + //send MAIL FROM | ||
| 341 | + if send_mail_from(conn, param.sender.str) is | ||
| 342 | + { | ||
| 343 | + error then error, // already logged | ||
| 344 | + reply(code, lines) then | ||
| 345 | + if code = 250 then | ||
| 346 | + //send RCPT TO | ||
| 347 | + if send_recipient(conn,param.recipient.str) is | ||
| 348 | + { | ||
| 349 | + error then error, // already logged | ||
| 350 | + reply(code, lines) then | ||
| 351 | + if code = 250 then | ||
| 352 | + //send DATA | ||
| 353 | + if send_data(conn) is | ||
| 354 | + { | ||
| 355 | + error then error, // already logged | ||
| 356 | + reply(code, lines) then | ||
| 357 | + if code = 354 then | ||
| 358 | + if send_content(conn, param.mail_parts) is | ||
| 359 | + { | ||
| 360 | + error then error, // already logged | ||
| 361 | + reply(code, lines) then | ||
| 362 | + if code = 250 then | ||
| 363 | + send_quit(conn); | ||
| 364 | + logInfo(send_mail_log,"SENT Mail FROM "+param.sender.str+" TO "+param.recipient.str); | ||
| 365 | + ok | ||
| 366 | + else | ||
| 367 | + reply_handling(code, lines, enhanced_status) | ||
| 368 | + } | ||
| 369 | + else | ||
| 370 | + reply_handling(code, lines, enhanced_status) | ||
| 371 | + } | ||
| 372 | + else | ||
| 373 | + reply_handling(code, lines, enhanced_status) | ||
| 374 | + } | ||
| 375 | + else | ||
| 376 | + reply_handling(code, lines, enhanced_status) | ||
| 377 | + } | ||
| 378 | +. | ||
| 379 | + | ||
| 380 | +define List(String) | ||
| 381 | + get_auth_method | ||
| 382 | + ( | ||
| 383 | + List(String) lines, | ||
| 384 | + List(String) so_far | ||
| 385 | + )= | ||
| 386 | + if lines is | ||
| 387 | + { | ||
| 388 | + [] then so_far, | ||
| 389 | + [h . t] then | ||
| 390 | + if sub_string(h, 4, 4) is | ||
| 391 | + { | ||
| 392 | + failure then get_auth_method(t, so_far), | ||
| 393 | + success(s) then | ||
| 394 | + with current = if insensitive_equal(s, "AUTH") then | ||
| 395 | + force_Type(list_word(h,9),[]) | ||
| 396 | + else | ||
| 397 | + [], | ||
| 398 | + get_auth_method(t, current + so_far) | ||
| 399 | + } | ||
| 400 | + }. | ||
| 401 | + | ||
| 402 | +define SendMailResult | ||
| 403 | + do_auth_plain | ||
| 404 | + ( | ||
| 405 | + RWStream conn, | ||
| 406 | + String login, | ||
| 407 | + String password, | ||
| 408 | + Bool enhanced_status | ||
| 409 | + )= | ||
| 410 | + | ||
| 411 | + with plain_str = "AUTH PLAIN "+ to_string(base64_encode(to_byte_array(login)+ | ||
| 412 | + constant_byte_array(1,0)+ | ||
| 413 | + to_byte_array(login)+ | ||
| 414 | + constant_byte_array(1,0)+ | ||
| 415 | + to_byte_array(password))), | ||
| 416 | + if smtp_send_line(conn, plain_str) is | ||
| 417 | + { | ||
| 418 | + failure then error, // already logged | ||
| 419 | + success(_) then | ||
| 420 | + if receive_reply(conn) is | ||
| 421 | + { | ||
| 422 | + error then error // already logged | ||
| 423 | + reply(code, lines) then | ||
| 424 | + if code = 235 then | ||
| 425 | + ok | ||
| 426 | + else | ||
| 427 | + reply_handling(code,lines,enhanced_status) | ||
| 428 | + } | ||
| 429 | + }. | ||
| 430 | + | ||
| 431 | +define SendMailResult | ||
| 432 | + do_login | ||
| 433 | + ( | ||
| 434 | + RWStream conn, //tcp connection | ||
| 435 | + List(String) auth_list, //list of available authentication method | ||
| 436 | + String login, | ||
| 437 | + String password, | ||
| 438 | + Bool enhanced_status | ||
| 439 | + )= | ||
| 440 | + | ||
| 441 | + if member(auth_list, "PLAIN", insensitive_equal) then | ||
| 442 | + do_auth_plain(conn, login, password, enhanced_status) | ||
| 443 | + else | ||
| 444 | + logError(send_mail_log, "do_login no PLAIN method available"); error. | ||
| 445 | + | ||
| 446 | +define SendMailResult | ||
| 447 | + do_auth | ||
| 448 | + ( | ||
| 449 | + RWStream conn, //tcp connection | ||
| 450 | + List(String) lines, //this is the reply lines provided in response to EHLO command | ||
| 451 | + Send_Auth auth, //authentication method to use for that session | ||
| 452 | + Bool enhanced_status | ||
| 453 | + )= | ||
| 454 | + if auth is | ||
| 455 | + { | ||
| 456 | + none then ok, //no need to authenticate, this is a "panties festival" | ||
| 457 | + login(user, password) then | ||
| 458 | + with auth_list = get_auth_method(lines,[]), | ||
| 459 | + if auth_list is | ||
| 460 | + { | ||
| 461 | + [] then logError(send_mail_log, "do_auth error");error, | ||
| 462 | + [_ . _] then do_login(conn, auth_list, user, password, enhanced_status) | ||
| 463 | + } | ||
| 464 | + }. | ||
| 465 | + | ||
| 466 | + /** | ||
| 467 | + * make_sm_session | ||
| 468 | + */ | ||
| 469 | +define Send_Mail_Session | ||
| 470 | + make_sm_session | ||
| 471 | + ( | ||
| 472 | + List(String) lines, //lines given in ehlo stage | ||
| 473 | + Send_Param param | ||
| 474 | + )= | ||
| 475 | + //looking for ENHANCEDSTATUSCODES | ||
| 476 | + with session_enhanced_status = has_smtp_extension(lines, enhanced_status_codes), | ||
| 477 | + //looking for SIZE | ||
| 478 | + with session_size = if get_smtp_extension_value(lines, size) is | ||
| 479 | + { | ||
| 480 | + failure then 0, | ||
| 481 | + success(str_value) then | ||
| 482 | + if decimal_scan(str_value) is | ||
| 483 | + { | ||
| 484 | + failure then 0, | ||
| 485 | + success(value) then | ||
| 486 | + logDebug(send_mail_log, "smtp server with SIZE "+value); | ||
| 487 | + value | ||
| 488 | + } | ||
| 489 | + }, | ||
| 490 | + send_mail_session(session_enhanced_status, session_size). | ||
| 491 | + | ||
| 492 | + Apply the whole protocol: | ||
| 493 | + | ||
| 494 | + //TODO we can have a list of part file in param type. This can be very useful when we want | ||
| 495 | + //to send file as mailing. For the mailing, in each mail, only the header is different of the | ||
| 496 | + //other mails. The body part is the same. Then, the mailing sender can generate the header in one | ||
| 497 | + //file and keep the body in other file wich can be given to send_mail function as last file in the list | ||
| 498 | +public define SendMailResult | ||
| 499 | + send_mail | ||
| 500 | + ( | ||
| 501 | + RWStream conn, | ||
| 502 | + Send_Param param, | ||
| 503 | + (Send_Mail_Session, Send_Param) -> Result(SendMailResult, Send_Param) prepare_mail_callback, | ||
| 504 | + ) = | ||
| 505 | + // | ||
| 506 | + // Our connection to the SMTP server is opened. We just have to apply the protocol. | ||
| 507 | + // | ||
| 508 | + if receive_reply(conn) is | ||
| 509 | + { | ||
| 510 | + error then error,// already logged | ||
| 511 | + reply(code, lines) then | ||
| 512 | + if code = 220 then | ||
| 513 | + //send EHLO | ||
| 514 | + if send_ehlo(conn, param.host_name.str) is //"mail."+force_Type(get_main_domain(db), "mailfountain.net")) is | ||
| 515 | + { | ||
| 516 | + error then error, // already logged | ||
| 517 | + reply(code, lines) then | ||
| 518 | + if code = 250 then | ||
| 519 | + // parse all available options here, and build a smtp context | ||
| 520 | + with sm_session = make_sm_session(lines, param), | ||
| 521 | + with auth_result = do_auth(conn, lines, param.auth, sm_session.enhanced_status), | ||
| 522 | + if auth_result = ok then | ||
| 523 | + if prepare_mail_callback(sm_session, param) is | ||
| 524 | + { | ||
| 525 | + error(result) then result, | ||
| 526 | + ok(new_param) then sm_MAIL_FROM(conn, new_param, sm_session) | ||
| 527 | + } | ||
| 528 | + else | ||
| 529 | + auth_result | ||
| 530 | + else | ||
| 531 | + reply_handling(code,lines,true) | ||
| 532 | + } | ||
| 533 | + else | ||
| 534 | + //some server answer "550 5.7.1 Client host rejected: cannot find your reverse hostname, [88.181.64.17]" | ||
| 535 | + //before anything, then we try to extract the enhanced status if exists for relaying the mail with the ISP | ||
| 536 | + reply_handling(code,lines,true) | ||
| 537 | + }. |
| 1 | +/* | ||
| 2 | + * | ||
| 3 | + * User: David RENE | ||
| 4 | + * Date: 30/05/2008 | ||
| 5 | + * Time: 01:18 | ||
| 6 | + * (c) Calexium | ||
| 7 | + * | ||
| 8 | + */ | ||
| 9 | + | ||
| 10 | +read tools/basis.anubis | ||
| 11 | +read system/string.anubis | ||
| 12 | + | ||
| 13 | +public type SMTP_Extension_verb: | ||
| 14 | + smtp_extension_verb | ||
| 15 | + ( | ||
| 16 | + String verb | ||
| 17 | + ). | ||
| 18 | + | ||
| 19 | +public define SMTP_Extension_verb enhanced_status_codes = smtp_extension_verb("ENHANCEDSTATUSCODES"). | ||
| 20 | +public define SMTP_Extension_verb size = smtp_extension_verb("SIZE"). | ||
| 21 | + | ||
| 22 | +define Bool | ||
| 23 | + has_smtp_extension | ||
| 24 | + ( | ||
| 25 | + List(String) lines, //left lines given in ehlo stage | ||
| 26 | + Int length, | ||
| 27 | + String search_ext | ||
| 28 | + )= | ||
| 29 | + if lines is | ||
| 30 | + { | ||
| 31 | + [] then false, | ||
| 32 | + [h . t] then | ||
| 33 | + if sub_string(h, 4, length) is | ||
| 34 | + { | ||
| 35 | + failure then has_smtp_extension(t, length, search_ext), | ||
| 36 | + success(s) then | ||
| 37 | + if insensitive_equal(s, search_ext) then | ||
| 38 | + true | ||
| 39 | + else | ||
| 40 | + has_smtp_extension(t, length, search_ext), | ||
| 41 | + } | ||
| 42 | + }. | ||
| 43 | + | ||
| 44 | +public define Bool | ||
| 45 | + has_smtp_extension | ||
| 46 | + ( | ||
| 47 | + List(String) lines, //left lines given in ehlo stage | ||
| 48 | + SMTP_Extension_verb search_ext | ||
| 49 | + )= | ||
| 50 | + has_smtp_extension(lines, length(search_ext.verb), search_ext.verb). | ||
| 51 | + | ||
| 52 | +define Maybe(String) | ||
| 53 | + get_smtp_extension_value | ||
| 54 | + ( | ||
| 55 | + List(String) lines, //left lines given in ehlo stage | ||
| 56 | + Int len, | ||
| 57 | + String search_ext | ||
| 58 | + )= | ||
| 59 | + if lines is | ||
| 60 | + { | ||
| 61 | + [] then failure, | ||
| 62 | + [h . t] then | ||
| 63 | + if sub_string(h, 4, len) is | ||
| 64 | + { | ||
| 65 | + failure then get_smtp_extension_value(t, len, search_ext), | ||
| 66 | + success(s) then | ||
| 67 | + if insensitive_equal(s, search_ext) then | ||
| 68 | + sub_string(h, 5+len, length(h)-(5+len)) | ||
| 69 | + else | ||
| 70 | + get_smtp_extension_value(t, len, search_ext), | ||
| 71 | + } | ||
| 72 | + }. | ||
| 73 | + | ||
| 74 | + /** | ||
| 75 | + * return the value of wanted smtp extension name | ||
| 76 | + * for "SIZE 10000" it return 10000. | ||
| 77 | + * if the smtp verb doesn't exist, it return failure | ||
| 78 | + */ | ||
| 79 | + | ||
| 80 | +public define Maybe(String) | ||
| 81 | + get_smtp_extension_value | ||
| 82 | + ( | ||
| 83 | + List(String) lines, //left lines given in ehlo stage | ||
| 84 | + SMTP_Extension_verb search_ext | ||
| 85 | + )= | ||
| 86 | + if has_smtp_extension(lines, length(search_ext.verb), search_ext.verb) then | ||
| 87 | + get_smtp_extension_value(lines, length(search_ext.verb), search_ext.verb) | ||
| 88 | + else | ||
| 89 | + failure. | ||
| 90 | + |