Branch data Line data Source code
1 : : // $Id: SMTP.cc 6782 2009-06-28 02:19:03Z vern $
2 : : //
3 : : // See the file "COPYING" in the main distribution directory for copyright.
4 : :
5 : : #include "config.h"
6 : :
7 : : #include <stdlib.h>
8 : :
9 : : #include "NetVar.h"
10 : : #include "SMTP.h"
11 : : #include "Event.h"
12 : : #include "ContentLine.h"
13 : : #include "TCP_Rewriter.h"
14 : :
15 : : #undef SMTP_CMD_DEF
16 : : #define SMTP_CMD_DEF(cmd) #cmd,
17 : :
18 : : static const char* smtp_cmd_word[] = {
19 : : #include "SMTP_cmd.def"
20 : : };
21 : :
22 : : #define SMTP_CMD_WORD(code) ((code >= 0) ? smtp_cmd_word[code] : "(UNKNOWN)")
23 : :
24 : :
25 : 4 : SMTP_Analyzer::SMTP_Analyzer(Connection* conn)
26 : 4 : : TCP_ApplicationAnalyzer(AnalyzerTag::SMTP, conn)
27 : : {
28 : 4 : expect_sender = 0;
29 : 4 : expect_recver = 1;
30 : 4 : state = SMTP_CONNECTED;
31 : 4 : last_replied_cmd = -1;
32 : 4 : first_cmd = SMTP_CMD_CONN_ESTABLISHMENT;
33 : 4 : pending_reply = 0;
34 : :
35 : : // Some clients appear to assume pipelining is always enabled
36 : : // and do not bother to check whether "PIPELINING" appears in
37 : : // the server reply to EHLO.
38 : 4 : pipelining = 1;
39 : :
40 : 4 : skip_data = 0;
41 : 4 : orig_is_sender = true;
42 : 4 : line_after_gap = 0;
43 : 4 : mail = 0;
44 : 4 : UpdateState(first_cmd, 0);
45 : 4 : ContentLine_Analyzer* cl_orig = new ContentLine_Analyzer(conn, true);
46 : 4 : cl_orig->SetIsNULSensitive(true);
47 : 4 : cl_orig->SetSkipPartial(true);
48 : 4 : AddSupportAnalyzer(cl_orig);
49 : :
50 : 4 : ContentLine_Analyzer* cl_resp = new ContentLine_Analyzer(conn, false);
51 : 4 : cl_resp->SetIsNULSensitive(true);
52 : 4 : cl_resp->SetSkipPartial(true);
53 : 4 : AddSupportAnalyzer(cl_resp);
54 : 4 : }
55 : :
56 : 0 : void SMTP_Analyzer::ConnectionFinished(int half_finished)
57 : : {
58 : 0 : TCP_ApplicationAnalyzer::ConnectionFinished(half_finished);
59 : :
60 [ # # # # ]: 0 : if ( ! half_finished && mail )
61 : 0 : EndData();
62 : 0 : }
63 : :
64 : 4 : SMTP_Analyzer::~SMTP_Analyzer()
65 : : {
66 [ - + ][ # # ]: 4 : delete line_after_gap;
[ # # ]
67 [ + - ][ # # ]: 4 : }
[ # # ]
68 : :
69 : 4 : void SMTP_Analyzer::Done()
70 : : {
71 : 4 : TCP_ApplicationAnalyzer::Done();
72 : :
73 [ - + ]: 4 : if ( mail )
74 : 0 : EndData();
75 : 4 : }
76 : :
77 : 0 : void SMTP_Analyzer::Undelivered(int seq, int len, bool is_orig)
78 : : {
79 : 0 : TCP_ApplicationAnalyzer::Undelivered(seq, len, is_orig);
80 : :
81 [ # # ]: 0 : if ( len <= 0 )
82 : 0 : return;
83 : :
84 : 0 : const char* buf = fmt("seq = %d, len = %d", seq, len);
85 : 0 : int buf_len = strlen(buf);
86 : :
87 : 0 : Unexpected(is_orig, "content gap", buf_len, buf);
88 : :
89 [ # # ]: 0 : if ( state == SMTP_IN_DATA )
90 : : // Record the SMTP data gap and terminate the
91 : : // ongoing mail transaction.
92 : 0 : EndData();
93 : :
94 [ # # ]: 0 : if ( line_after_gap )
95 : : {
96 [ # # ]: 0 : delete line_after_gap;
97 : 0 : line_after_gap = 0;
98 : : }
99 : :
100 : 0 : pending_cmd_q.clear();
101 : :
102 : 0 : first_cmd = last_replied_cmd = -1;
103 : :
104 : : // Missing either the sender's packets or their replies
105 : : // (e.g. code 354) is critical, so we set state to SMTP_AFTER_GAP
106 : : // in both cases
107 : 0 : state = SMTP_AFTER_GAP;
108 : : }
109 : :
110 : 0 : void SMTP_Analyzer::DeliverStream(int length, const u_char* line, bool orig)
111 : : {
112 : 0 : TCP_ApplicationAnalyzer::DeliverStream(length, line, orig);
113 : :
114 : : // NOTE: do not use IsOrig() here, because of TURN command.
115 [ # # ]: 0 : int is_sender = orig_is_sender ? orig : ! orig;
116 : :
117 : : #if 0
118 : : ###
119 : : if ( line[length] != '\r' || line[length+1] != '\n' )
120 : : Unexpected(is_sender, "line does not end with <CR><LF>", length, line);
121 : : #endif
122 : :
123 : : // Some weird client uses '\r\r\n' for end-of-line sequence
124 : : // So we make a compromise here to allow /(\r)*\n/ as end-of-line sequences
125 [ # # ][ # # ]: 0 : if ( length > 0 && line[length-1] == '\r' )
126 : : {
127 : 0 : Unexpected(is_sender, "more than one <CR> at the end of line", length, (const char*) line);
128 [ # # ][ # # ]: 0 : do
129 : 0 : --length;
130 : : while ( length > 0 && line[length-1] == '\r' );
131 : : }
132 : :
133 [ # # ]: 0 : for ( int i = 0; i < length; ++i )
134 [ # # ][ # # ]: 0 : if ( line[i] == '\r' || line[i] == '\n' )
135 : : {
136 : : Unexpected(is_sender, "Bare <CR> or <LF> appears in the middle of line",
137 : 0 : length, (const char*) line);
138 : 0 : break;
139 : : }
140 : :
141 : 0 : ProcessLine(length, (const char*) line, orig);
142 : 0 : }
143 : :
144 : :
145 : 0 : void SMTP_Analyzer::ProcessLine(int length, const char* line, bool orig)
146 : : {
147 : 0 : const char* end_of_line = line + length;
148 [ # # ]: 0 : if ( state == SMTP_IN_TLS )
149 : : // Do not try to parse contents after STARTTLS/220.
150 : 0 : return;
151 : :
152 : 0 : int cmd_len = 0;
153 : 0 : const char* cmd = "";
154 : :
155 : : // NOTE: do not use IsOrig() here, because of TURN command.
156 [ # # ]: 0 : int is_sender = orig_is_sender ? orig : ! orig;
157 : :
158 [ # # ][ # # ]: 0 : if ( ! pipelining &&
[ # # ][ # # ]
[ # # ]
159 : : ((is_sender && ! expect_sender) ||
160 : : (! is_sender && ! expect_recver)) )
161 : 0 : Unexpected(is_sender, "out of order", length, line);
162 : :
163 [ # # ]: 0 : if ( is_sender )
164 : : {
165 : 0 : int cmd_code = -1;
166 : :
167 [ # # ]: 0 : if ( state == SMTP_AFTER_GAP )
168 : : {
169 : : // Don't know whether it is a command line or
170 : : // a data line.
171 [ # # ]: 0 : if ( line_after_gap )
172 [ # # ]: 0 : delete line_after_gap;
173 : :
174 : : line_after_gap =
175 : 0 : new BroString((const u_char *) line, length, 1);
176 : : }
177 : :
178 [ # # ][ # # ]: 0 : else if ( state == SMTP_IN_DATA && line[0] == '.' && length == 1 )
[ # # ]
179 : : {
180 : 0 : cmd = ".";
181 : 0 : cmd_len = 1;
182 : 0 : cmd_code = SMTP_CMD_END_OF_DATA;
183 : 0 : NewCmd(cmd_code);
184 : :
185 : 0 : expect_sender = 0;
186 : 0 : expect_recver = 1;
187 : : }
188 : :
189 [ # # ]: 0 : else if ( state == SMTP_IN_DATA )
190 : : {
191 : : // Check "." for end of data.
192 : 0 : expect_recver = 0; // ?? MAY server respond to mail data?
193 : :
194 [ # # ]: 0 : if ( line[0] == '.' )
195 : 0 : ++line;
196 : :
197 : 0 : int data_len = end_of_line - line;
198 : :
199 [ # # ]: 0 : if ( ! mail )
200 : : // This can happen if we're already shut
201 : : // down the connection due to seeing a RST
202 : : // but are now processing packets sent
203 : : // afterwards (because, e.g., the RST was
204 : : // dropped or ignored).
205 : 0 : BeginData();
206 : :
207 : 0 : ProcessData(data_len, line);
208 : :
209 [ # # # # ]: 0 : if ( smtp_data && ! skip_data )
[ # # ]
210 : : {
211 : 0 : val_list* vl = new val_list;
212 : 0 : vl->append(BuildConnVal());
213 : 0 : vl->append(new Val(orig, TYPE_BOOL));
214 : 0 : vl->append(new StringVal(data_len, line));
215 : 0 : ConnectionEvent(smtp_data, vl);
216 : : }
217 : : }
218 : :
219 [ # # ]: 0 : else if ( state == SMTP_IN_AUTH )
220 : : {
221 : 0 : cmd = "***";
222 : 0 : cmd_len = 2;
223 : 0 : cmd_code = SMTP_CMD_AUTH_ANSWER;
224 : 0 : NewCmd(cmd_code);
225 : : }
226 : :
227 : : else
228 : : {
229 : 0 : expect_sender = 0;
230 : 0 : expect_recver = 1;
231 : :
232 : 0 : get_word(length, line, cmd_len, cmd);
233 : 0 : line = skip_whitespace(line + cmd_len, end_of_line);
234 : 0 : cmd_code = ParseCmd(cmd_len, cmd);
235 : :
236 [ # # ]: 0 : if ( cmd_code == -1 )
237 : : {
238 : 0 : Unexpected(1, "unknown command", cmd_len, cmd);
239 : 0 : cmd = 0;
240 : : }
241 : : else
242 : 0 : NewCmd(cmd_code);
243 : : }
244 : :
245 : : // Generate smtp_request event
246 [ # # ]: 0 : if ( cmd_code >= 0 )
247 : : {
248 : : // In order for all MIME events nested
249 : : // between SMTP command DATA and END_OF_DATA,
250 : : // we need to call UpdateState(), which in
251 : : // turn calls BeginData() and EndData(), and
252 : : // RequestEvent() in different orders for the
253 : : // two commands.
254 [ # # ]: 0 : if ( cmd_code == SMTP_CMD_END_OF_DATA )
255 : 0 : UpdateState(cmd_code, 0);
256 : :
257 [ # # ]: 0 : if ( smtp_request )
258 : : {
259 : 0 : int data_len = end_of_line - line;
260 : 0 : RequestEvent(cmd_len, cmd, data_len, line);
261 : : }
262 : :
263 [ # # ]: 0 : if ( cmd_code != SMTP_CMD_END_OF_DATA )
264 : 0 : UpdateState(cmd_code, 0);
265 : : }
266 : : }
267 : :
268 : : else
269 : : {
270 : : int reply_code;
271 : :
272 [ # # ][ # # ]: 0 : if ( length >= 3 &&
[ # # ][ # # ]
273 : : isdigit(line[0]) && isdigit(line[1]) && isdigit(line[2]) )
274 : : {
275 : : reply_code = (line[0] - '0') * 100 +
276 : : (line[1] - '0') * 10 +
277 : 0 : (line[2] - '0');
278 : : }
279 : : else
280 : 0 : reply_code = -1;
281 : :
282 : : // The first digit of reply code must be between 1 and 5,
283 : : // and the second between 0 and 5 (RFC 2821). But sometimes
284 : : // we see 5xx codes larger than 559, so we still tolerate that.
285 [ # # ][ # # ]: 0 : if ( reply_code < 100 || reply_code > 599 )
286 : : {
287 : 0 : reply_code = -1;
288 : 0 : Unexpected(is_sender, "reply code out of range", length, line);
289 : : ProtocolViolation(fmt("reply code %d out of range",
290 : 0 : reply_code), line, length);
291 : : }
292 : :
293 : : else
294 : : { // Valid reply code.
295 [ # # ][ # # ]: 0 : if ( pending_reply && reply_code != pending_reply )
296 : : {
297 : 0 : Unexpected(is_sender, "reply code does not match the continuing reply", length, line);
298 : 0 : pending_reply = 0;
299 : : }
300 : :
301 [ # # ][ # # ]: 0 : if ( ! pending_reply && reply_code >= 0 )
302 : : // It is not a continuation.
303 : 0 : NewReply(reply_code);
304 : :
305 : : // Update pending_reply.
306 [ # # ][ # # ]: 0 : if ( reply_code >= 0 && length > 3 && line[3] == '-' )
[ # # ]
307 : : { // A continued reply.
308 : 0 : pending_reply = reply_code;
309 : 0 : line = skip_whitespace(line+4, end_of_line);
310 : : }
311 : :
312 : : else
313 : : { // This is the end of the reply.
314 : 0 : line = skip_whitespace(line+3, end_of_line);
315 : :
316 : 0 : pending_reply = 0;
317 : 0 : expect_sender = 1;
318 : 0 : expect_recver = 0;
319 : : }
320 : :
321 : : // Generate events.
322 [ # # ][ # # ]: 0 : if ( smtp_reply && reply_code >= 0 )
[ # # ]
323 : : {
324 : 0 : const int cmd_code = last_replied_cmd;
325 [ # # # ]: 0 : switch ( cmd_code ) {
326 : : case SMTP_CMD_CONN_ESTABLISHMENT:
327 : 0 : cmd = ">";
328 : 0 : break;
329 : :
330 : : case SMTP_CMD_END_OF_DATA:
331 : 0 : cmd = ".";
332 : 0 : break;
333 : :
334 : : default:
335 [ # # ]: 0 : cmd = SMTP_CMD_WORD(cmd_code);
336 : : break;
337 : : }
338 : :
339 : 0 : val_list* vl = new val_list;
340 : 0 : vl->append(BuildConnVal());
341 : 0 : vl->append(new Val(orig, TYPE_BOOL));
342 : 0 : vl->append(new Val(reply_code, TYPE_COUNT));
343 : 0 : vl->append(new StringVal(cmd));
344 : 0 : vl->append(new StringVal(end_of_line - line, line));
345 : 0 : vl->append(new Val((pending_reply > 0), TYPE_BOOL));
346 : :
347 : 0 : ConnectionEvent(smtp_reply, vl);
348 : : }
349 : : }
350 : :
351 : : // Process SMTP extensions, e.g. PIPELINING.
352 [ # # ][ # # ]: 0 : if ( last_replied_cmd == SMTP_CMD_EHLO && reply_code == 250 )
353 : : {
354 : : const char* ext;
355 : : int ext_len;
356 : :
357 : 0 : get_word(end_of_line - line, line, ext_len, ext);
358 : 0 : line = skip_whitespace(line + ext_len, end_of_line);
359 : 0 : ProcessExtension(ext_len, ext);
360 : : }
361 : : }
362 : : }
363 : :
364 : 0 : void SMTP_Analyzer::NewCmd(const int cmd_code)
365 : : {
366 [ # # ]: 0 : if ( pipelining )
367 : : {
368 [ # # ]: 0 : if ( first_cmd < 0 )
369 : 0 : first_cmd = cmd_code;
370 : : else
371 : 0 : pending_cmd_q.push_back(cmd_code);
372 : : }
373 : : else
374 : 0 : first_cmd = cmd_code;
375 : 0 : }
376 : :
377 : :
378 : : // Here we keep a SMTP state machine and update it on each reply.
379 : : // However, the purpose is NOT to check correctness of SMTP commands
380 : : // and replies, but to guess the state of the SMTP session and,
381 : : // particularly, to know when we are in the SMTP_IN_DATA state.
382 : : //
383 : : // That is why state transition does not depend on the previous state,
384 : : // but only depend on the <command, reply> pair.
385 : : //
386 : : // Why not simply have two-state machine, IN_DATA/NOT_IN_DATA? Because
387 : : // we want to understand the behavior of SMTP and check how far it may
388 : : // deviate from our knowledge.
389 : :
390 : 0 : void SMTP_Analyzer::NewReply(const int reply_code)
391 : : {
392 [ # # ][ # # ]: 0 : if ( state == SMTP_AFTER_GAP && reply_code > 0 )
393 : : {
394 : 0 : state = SMTP_GAP_RECOVERY;
395 : 0 : const char* unknown_cmd = SMTP_CMD_WORD(-1);
396 : 0 : RequestEvent(strlen(unknown_cmd), unknown_cmd, 0, "");
397 : : /*
398 : : if ( line_after_gap )
399 : : ProcessLine(sender, line_after_gap->Len(), (const char *) line_after_gap->Bytes());
400 : : */
401 : : }
402 : :
403 : : // Make all parameters constants.
404 : 0 : const int cmd_code = first_cmd;
405 : :
406 : : // To recover from a gap, we detect replies -- the critical
407 : : // assumptions here are 1) receiver does not reply during a DATA
408 : : // session; 2) there is no TURN in the gap.
409 : :
410 : 0 : last_replied_cmd = first_cmd;
411 : 0 : first_cmd = -1;
412 : :
413 [ # # ][ # # ]: 0 : if ( pipelining && pending_cmd_q.size() > 0 )
[ # # ]
414 : : {
415 : 0 : first_cmd = pending_cmd_q.front();
416 : 0 : pending_cmd_q.pop_front();
417 : : }
418 : :
419 : 0 : UpdateState(cmd_code, reply_code);
420 : 0 : }
421 : :
422 : : // Note: reply_code == 0 means we haven't seen the reply, in which case we
423 : : // still update the state as if the command will succeed, and later
424 : : // adjust the state if it turns out otherwise. This is because some
425 : : // clients are really aggressive in pipelining (beyond the restrictions
426 : : // in the RPC), and as a result we have to update the state following
427 : : // the commands in addition to the replies.
428 : :
429 : 4 : void SMTP_Analyzer::UpdateState(const int cmd_code, const int reply_code)
430 : : {
431 : 4 : const int st = state;
432 : :
433 [ - + ][ # # ]: 4 : if ( st == SMTP_QUIT && reply_code == 0 )
434 : 0 : UnexpectedCommand(cmd_code, reply_code);
435 : :
436 [ + - - - - : 4 : switch ( cmd_code ) {
- - - - -
- - - ]
437 : : case SMTP_CMD_CONN_ESTABLISHMENT:
438 [ + - - - ]: 4 : switch ( reply_code ) {
439 : : case 0:
440 [ - + ]: 4 : if ( st != SMTP_CONNECTED )
441 : : {
442 : : // Impossible state, because the command
443 : : // CONN_ESTABLISHMENT should only appear
444 : : // in the very beginning.
445 : 0 : UnexpectedCommand(cmd_code, reply_code);
446 : : }
447 : 4 : state = SMTP_INITIATED;
448 : 4 : break;
449 : :
450 : : case 220:
451 : 0 : break;
452 : :
453 : : case 421:
454 : : case 554:
455 : 0 : state = SMTP_NOT_AVAILABLE;
456 : 0 : break;
457 : :
458 : : default:
459 : 0 : UnexpectedReply(cmd_code, reply_code);
460 : : break;
461 : : }
462 : 4 : break;
463 : :
464 : : case SMTP_CMD_EHLO:
465 : : case SMTP_CMD_HELO:
466 [ # # # # ]: 0 : switch ( reply_code ) {
467 : : case 0:
468 [ # # ]: 0 : if ( st != SMTP_INITIATED )
469 : 0 : UnexpectedCommand(cmd_code, reply_code);
470 : 0 : state = SMTP_READY;
471 : 0 : break;
472 : :
473 : : case 250:
474 : 0 : break;
475 : :
476 : : case 421:
477 : : case 500:
478 : : case 501:
479 : : case 504:
480 : : case 550:
481 : 0 : state = SMTP_INITIATED;
482 : 0 : break;
483 : :
484 : : default:
485 : 0 : UnexpectedReply(cmd_code, reply_code);
486 : : break;
487 : : }
488 : 0 : break;
489 : :
490 : : case SMTP_CMD_MAIL:
491 : : case SMTP_CMD_SEND:
492 : : case SMTP_CMD_SOML:
493 : : case SMTP_CMD_SAML:
494 [ # # # # ]: 0 : switch ( reply_code ) {
495 : : case 0:
496 [ # # ]: 0 : if ( st != SMTP_READY )
497 : 0 : UnexpectedCommand(cmd_code, reply_code);
498 : 0 : state = SMTP_MAIL_OK;
499 : 0 : break;
500 : :
501 : : case 250:
502 : 0 : break;
503 : :
504 : : case 421:
505 : : case 451:
506 : : case 452:
507 : : case 500:
508 : : case 501:
509 : : case 503:
510 : : case 550:
511 : : case 552:
512 : : case 553:
513 [ # # ]: 0 : if ( state != SMTP_IN_DATA )
514 : 0 : state = SMTP_READY;
515 : 0 : break;
516 : :
517 : : default:
518 : 0 : UnexpectedReply(cmd_code, reply_code);
519 : : break;
520 : : }
521 : 0 : break;
522 : :
523 : : case SMTP_CMD_RCPT:
524 [ # # # # ]: 0 : switch ( reply_code ) {
525 : : case 0:
526 [ # # ][ # # ]: 0 : if ( st != SMTP_MAIL_OK && st != SMTP_RCPT_OK )
527 : 0 : UnexpectedCommand(cmd_code, reply_code);
528 : 0 : state = SMTP_RCPT_OK;
529 : 0 : break;
530 : :
531 : : case 250:
532 : : case 251: // ?? Shall we catch 251? (RFC 2821)
533 : 0 : break;
534 : :
535 : : case 421:
536 : : case 450:
537 : : case 451:
538 : : case 452:
539 : : case 500:
540 : : case 501:
541 : : case 503:
542 : : case 550:
543 : : case 551: // ?? Shall we catch 551?
544 : : case 552:
545 : : case 553:
546 : : case 554: // = transaction failed/recipient refused
547 : 0 : break;
548 : :
549 : : default:
550 : 0 : UnexpectedReply(cmd_code, reply_code);
551 : : break;
552 : : }
553 : 0 : break;
554 : :
555 : : case SMTP_CMD_DATA:
556 [ # # # # : 0 : switch ( reply_code ) {
# ]
557 : : case 0:
558 [ # # ]: 0 : if ( state != SMTP_RCPT_OK )
559 : 0 : UnexpectedCommand(cmd_code, reply_code);
560 : 0 : BeginData();
561 : 0 : break;
562 : :
563 : : case 354:
564 : 0 : break;
565 : :
566 : : case 421:
567 [ # # ]: 0 : if ( state == SMTP_IN_DATA )
568 : 0 : EndData();
569 : 0 : state = SMTP_QUIT;
570 : 0 : break;
571 : :
572 : : case 500:
573 : : case 501:
574 : : case 503:
575 : : case 451:
576 : : case 554:
577 [ # # ]: 0 : if ( state == SMTP_IN_DATA )
578 : 0 : EndData();
579 : 0 : state = SMTP_READY;
580 : 0 : break;
581 : :
582 : : default:
583 : 0 : UnexpectedReply(cmd_code, reply_code);
584 [ # # ]: 0 : if ( state == SMTP_IN_DATA )
585 : 0 : EndData();
586 : 0 : state = SMTP_READY;
587 : : break;
588 : : }
589 : 0 : break;
590 : :
591 : : case SMTP_CMD_END_OF_DATA:
592 [ # # # # ]: 0 : switch ( reply_code ) {
593 : : case 0:
594 [ # # ]: 0 : if ( st != SMTP_IN_DATA )
595 : 0 : UnexpectedCommand(cmd_code, reply_code);
596 : 0 : EndData();
597 : 0 : state = SMTP_AFTER_DATA;
598 : 0 : break;
599 : :
600 : : case 250:
601 : 0 : break;
602 : :
603 : : case 421:
604 : : case 451:
605 : : case 452:
606 : : case 552:
607 : : case 554:
608 : 0 : break;
609 : :
610 : : default:
611 : 0 : UnexpectedReply(cmd_code, reply_code);
612 : : break;
613 : : }
614 : :
615 [ # # ]: 0 : if ( reply_code > 0 )
616 : 0 : state = SMTP_READY;
617 : 0 : break;
618 : :
619 : : case SMTP_CMD_RSET:
620 [ # # # ]: 0 : switch ( reply_code ) {
621 : : case 0:
622 : 0 : state = SMTP_READY;
623 : 0 : break;
624 : :
625 : : case 250:
626 : 0 : break;
627 : :
628 : : default:
629 : 0 : UnexpectedReply(cmd_code, reply_code);
630 : : break;
631 : : }
632 : :
633 : 0 : break;
634 : :
635 : : case SMTP_CMD_QUIT:
636 [ # # # ]: 0 : switch ( reply_code ) {
637 : : case 0:
638 : 0 : state = SMTP_QUIT;
639 : 0 : break;
640 : :
641 : : case 221:
642 : 0 : break;
643 : :
644 : : default:
645 : 0 : UnexpectedReply(cmd_code, reply_code);
646 : : break;
647 : : }
648 : :
649 : 0 : break;
650 : :
651 : : case SMTP_CMD_AUTH:
652 [ # # ]: 0 : if ( st != SMTP_READY )
653 : 0 : UnexpectedCommand(cmd_code, reply_code);
654 : :
655 [ # # # # ]: 0 : switch ( reply_code ) {
656 : : case 0:
657 : : // Here we wait till there's a reply.
658 : 0 : break;
659 : :
660 : : case 334:
661 : 0 : state = SMTP_IN_AUTH;
662 : 0 : break;
663 : :
664 : : case 235:
665 : 0 : state = SMTP_INITIATED;
666 : 0 : break;
667 : :
668 : : case 432:
669 : : case 454:
670 : : case 501:
671 : : case 503:
672 : : case 504:
673 : : case 534:
674 : : case 535:
675 : : case 538:
676 : : default:
677 : 0 : state = SMTP_INITIATED;
678 : : break;
679 : : }
680 : 0 : break;
681 : :
682 : : case SMTP_CMD_AUTH_ANSWER:
683 [ # # ]: 0 : if ( st != SMTP_IN_AUTH )
684 : 0 : UnexpectedCommand(cmd_code, reply_code);
685 : :
686 [ # # # ]: 0 : switch ( reply_code ) {
687 : : case 0:
688 : : // Here we wait till there's a reply.
689 : 0 : break;
690 : :
691 : : case 334:
692 : 0 : state = SMTP_IN_AUTH;
693 : 0 : break;
694 : :
695 : : case 235:
696 : : case 535:
697 : : default:
698 : 0 : state = SMTP_INITIATED;
699 : : break;
700 : : }
701 : 0 : break;
702 : :
703 : : case SMTP_CMD_TURN:
704 [ # # ]: 0 : if ( st != SMTP_READY )
705 : 0 : UnexpectedCommand(cmd_code, reply_code);
706 : :
707 [ # # # ]: 0 : switch ( reply_code ) {
708 : : case 0:
709 : : // Here we wait till there's a reply.
710 : 0 : break;
711 : :
712 : : case 250:
713 : : // flip-side
714 : 0 : orig_is_sender = ! orig_is_sender;
715 : :
716 : 0 : state = SMTP_CONNECTED;
717 : 0 : expect_sender = 0;
718 : 0 : expect_recver = 1;
719 : : break;
720 : :
721 : : case 502:
722 : : default:
723 : : break;
724 : : }
725 : 0 : break;
726 : :
727 : : case SMTP_CMD_STARTTLS:
728 [ # # ]: 0 : if ( st != SMTP_READY )
729 : 0 : UnexpectedCommand(cmd_code, reply_code);
730 : :
731 [ # # # ]: 0 : switch ( reply_code ) {
732 : : case 0:
733 : : // Here we wait till there's a reply.
734 : 0 : break;
735 : :
736 : : case 220:
737 : 0 : state = SMTP_IN_TLS;
738 : 0 : expect_sender = expect_recver = 1;
739 : : break;
740 : :
741 : : case 454:
742 : : case 501:
743 : : default:
744 : : break;
745 : : }
746 : 0 : break;
747 : :
748 : : case SMTP_CMD_VRFY:
749 : : case SMTP_CMD_EXPN:
750 : : case SMTP_CMD_HELP:
751 : : case SMTP_CMD_NOOP:
752 : : // These commands do not affect state.
753 : : // ?? However, later we may want to add reply
754 : : // and state check code.
755 : :
756 : : default:
757 [ # # ][ # # ]: 0 : if ( st == SMTP_GAP_RECOVERY && reply_code == 354 )
758 : : {
759 : 0 : BeginData();
760 : : }
761 : : break;
762 : : }
763 : :
764 : : // A hack: whenever the server makes a valid reply during a DATA
765 : : // section, we assume that the DATA section has ended (the end
766 : : // of data line might have been lost due to gaps in trace). Note,
767 : : // BeginData() won't be called till the next DATA command.
768 : : #if 0
769 : : if ( state == SMTP_IN_DATA && reply_code >= 400 )
770 : : {
771 : : EndData();
772 : : state = SMTP_READY;
773 : : }
774 : : #endif
775 : 4 : }
776 : :
777 : 0 : void SMTP_Analyzer::ProcessExtension(int ext_len, const char* ext)
778 : : {
779 [ # # ]: 0 : if ( ! ext )
780 : 0 : return;
781 : :
782 [ # # ]: 0 : if ( ! strcasecmp_n(ext_len, ext, "PIPELINING") )
783 : 0 : pipelining = 1;
784 : : }
785 : :
786 : 0 : int SMTP_Analyzer::ParseCmd(int cmd_len, const char* cmd)
787 : : {
788 [ # # ]: 0 : if ( ! cmd )
789 : 0 : return -1;
790 : :
791 [ # # ]: 0 : for ( int code = SMTP_CMD_EHLO; code < SMTP_CMD_LAST; ++code )
792 [ # # ]: 0 : if ( ! strcasecmp_n(cmd_len, cmd, smtp_cmd_word[code - SMTP_CMD_EHLO]) )
793 : 0 : return code;
794 : :
795 : 0 : return -1;
796 : : }
797 : :
798 : : void SMTP_Analyzer::RequestEvent(int cmd_len, const char* cmd,
799 : 0 : int arg_len, const char* arg)
800 : : {
801 : 0 : ProtocolConfirmation();
802 : 0 : val_list* vl = new val_list;
803 : :
804 : 0 : vl->append(BuildConnVal());
805 : 0 : vl->append(new Val(orig_is_sender, TYPE_BOOL));
806 : 0 : vl->append((new StringVal(cmd_len, cmd))->ToUpper());
807 : 0 : vl->append(new StringVal(arg_len, arg));
808 : :
809 : 0 : ConnectionEvent(smtp_request, vl);
810 : 0 : }
811 : :
812 : : void SMTP_Analyzer::Unexpected(const int is_sender, const char* msg,
813 : 0 : int detail_len, const char* detail)
814 : : {
815 : : // Either party can send a line after an unexpected line.
816 : 0 : expect_sender = expect_recver = 1;
817 : :
818 [ # # ]: 0 : if ( smtp_unexpected )
819 : : {
820 : 0 : val_list* vl = new val_list;
821 : 0 : int is_orig = is_sender;
822 [ # # ]: 0 : if ( ! orig_is_sender )
823 : 0 : is_orig = ! is_orig;
824 : :
825 : 0 : vl->append(BuildConnVal());
826 : 0 : vl->append(new Val(is_orig, TYPE_BOOL));
827 : 0 : vl->append(new StringVal(msg));
828 : 0 : vl->append(new StringVal(detail_len, detail));
829 : :
830 : 0 : ConnectionEvent(smtp_unexpected, vl);
831 : : }
832 : 0 : }
833 : :
834 : 0 : void SMTP_Analyzer::UnexpectedCommand(const int cmd_code, const int reply_code)
835 : : {
836 : : // If this happens, please fix the SMTP state machine!
837 : : // ### Eventually, these should be turned into "weird" events.
838 : : static char buf[512];
839 : : int len = safe_snprintf(buf, sizeof(buf),
840 : : "%s reply = %d state = %d",
841 [ # # ]: 0 : SMTP_CMD_WORD(cmd_code), reply_code, state);
842 [ # # ]: 0 : if ( len > (int) sizeof(buf) )
843 : 0 : len = sizeof(buf);
844 : 0 : Unexpected (1, "unexpected command", len, buf);
845 : 0 : }
846 : :
847 : 0 : void SMTP_Analyzer::UnexpectedReply(const int cmd_code, const int reply_code)
848 : : {
849 : : // If this happens, please fix the SMTP state machine!
850 : : // ### Eventually, these should be turned into "weird" events.
851 : : static char buf[512];
852 : : int len = safe_snprintf(buf, sizeof(buf),
853 : : "%d state = %d, last command = %s",
854 [ # # ]: 0 : reply_code, state, SMTP_CMD_WORD(cmd_code));
855 : 0 : Unexpected (1, "unexpected reply", len, buf);
856 : 0 : }
857 : :
858 : 0 : void SMTP_Analyzer::ProcessData(int length, const char* line)
859 : : {
860 : 0 : mail->Deliver(length, line, 1 /* trailing_CRLF */);
861 : 0 : }
862 : :
863 : 0 : void SMTP_Analyzer::BeginData()
864 : : {
865 : 0 : state = SMTP_IN_DATA;
866 : 0 : skip_data = 0; // reset the flag at the beginning of the mail
867 [ # # ]: 0 : if ( mail != 0 )
868 : : {
869 : 0 : warn("nested mail transaction");
870 : 0 : mail->Done();
871 [ # # ]: 0 : delete mail;
872 : : }
873 : :
874 : 0 : mail = new MIME_Mail(this);
875 : 0 : }
876 : :
877 : 0 : void SMTP_Analyzer::EndData()
878 : : {
879 [ # # ]: 0 : if ( ! mail )
880 : 0 : warn("Unmatched end of data");
881 : : else
882 : : {
883 : 0 : mail->Done();
884 [ # # ]: 0 : delete mail;
885 : 0 : mail = 0;
886 : : }
887 : 0 : }
888 : :
889 : : #include "smtp-rw.bif.func_def"
|