smtp_server.anubis 29 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979

                                  The Anubis Project. 
                                  A SMTP Mail Server. 
   
                           Copyright (c) Alain Proute' 2003. 
                                 All rights reserved. 
   

   In this file we define a SMTP (Simple Mail Transfert Protocol) server. This is the sort
   of server that you  register as the 'outgoing mail server' in  your mail software (like
   Netscape or Outlook Express).

   
   
   *** SMTP server. 
   
   In order to start a SMTP server, you must describe it. 
   
public type SMTP_ServerDescription:
   smtp_server_description
     (
       Int32       ip_address,           // 0 is OK, meaning listen on all interfaces
       Int32       ip_port,              // Official value is 25
       String      server_common_name,   // for example "mail.our-business.com"
                                         //          or "smtp.our-business.com"
       String      server_directory,     // where the SMTP server stores its data (and mail)
       Int32       max_mail_size         // maximal size (bytes) of mail the server will accept
     ). 
   
   
   
   The SMTP server is started by:
   
public define One
   start_smtp_server
     (
       SMTP_ServerDescription description
     ). 
   

   
   If  you are  impatient  to  test it,  just  create a  directory  for  your SMTP  server
   ('/home/georges/smtp_server' in our example), customize the following function, compile
   and run.  You  must also create users, i.e.  create  subdirectories in the subdirectory
   '/users' (which is created automatically by the SMTP server at first start) of the SMTP
   server directory.   For each  user create a  directory whose  name is exactly  the mail
   address. For example create the following directories:
   
         /home/georges/smtp_server/users/georges@our-business.com/
         /home/georges/smtp_server/users/alfred@our-business.com/
         /home/georges/smtp_server/users/mina@our-business.com/
   
   
global define One
   smtp_server
     (
       List(String) args
     ) =
   start_smtp_server(
     smtp_server_description(
        0,                              // listen on all interfaces
        25,                             // 25 is the official port number for SMTP
        "apis.com",             // your domain name
        "/home/alp/smtp_server",    // the SMTP server directory (must have been created by hand)
        5000000                         // about 5 Mbytes per mail is already a lot
                            )
                    ).
   
   
   
   *** MX records. 
   
   The domain name  resolution for SMTP does not  follow the usual rules (as  for HTTP for
   example). The program (also known as the 'sender-SMTP', actually in most cases a 'relay
   SMTP server') which calls your SMTP server (also known as the 'receiver-SMTP'), queries
   the DNS (Domain  Name Server) in order to  get a so-called 'MX record'  ('MX' for 'Mail
   eXchange'). This may  give an IP address  different from the usual one  (defined by the
   'A' (Address)  record).  You must ensure  that this address is  correct, otherwise your
   SMTP server will not be reachable.
   
   To be informed about  the values of the records of your  domain name, you must normally
   query your  registrar. However, there are UNIX  tools for querying the  values. You can
   use  'nslookup'  or  preferably  'dig'   (these  tools  are  comming  with  the  'bind'
   package). For example, the command:
   
      dig ietf.org MX

   will produce the following (verbose) result:
   
   
   ; <<>> DiG 9.2.0 <<>> ietf.org MX
   ;; global options:  printcmd
   ;; Got answer:
   ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 22408
   ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 5, ADDITIONAL: 6

   ;; QUESTION SECTION:
   ;ietf.org.                      IN      MX

   ;; ANSWER SECTION:
   ietf.org.               3600    IN      MX      20 mx2.foretec.com.
   ietf.org.               3600    IN      MX      10 ietf-mx.ietf.org.

   ;; AUTHORITY SECTION:
   ietf.org.               150     IN      NS      ns2.cw.net.
   ietf.org.               150     IN      NS      ehs2.handle.net.
   ietf.org.               150     IN      NS      ns.cw.net.
   ietf.org.               150     IN      NS      ns.CNRI.Reston.VA.US.
   ietf.org.               150     IN      NS      ns.ietf.org.

   ;; ADDITIONAL SECTION:
   ietf-mx.ietf.org.       3600    IN      A       132.151.6.1
   ns2.cw.net.             113518  IN      A       204.70.57.242
   ehs2.handle.net.        114237  IN      A       209.225.25.25
   ns.cw.net.              1493    IN      A       204.70.128.1
   ns.CNRI.Reston.VA.US.   3750    IN      A       132.151.1.1
   ns.ietf.org.            114237  IN      A       132.151.1.19

   ;; Query time: 290 msec
   ;; SERVER: 213.36.80.1#53(213.36.80.1)
   ;; WHEN: Tue Dec  9 10:26:52 2003
   ;; MSG SIZE  rcvd: 303

   
   The ANSWER section  has two lines (there are  two MX records). This means  that if some
   mail is  sent to 'someone@ietf.org' the  SMTP relay server  will try to relay  the mail
   either to 'ietf-mx.ietf.org'  (preferably, because priority '10' is  higher then '20'),
   and if not successful to 'mx2.foretec.com'.  The actual addresses of these SMTP servers
   are normally given in the ADDITIONAL section (it's not the case here !).

   'dig' allows you  to query for MX records, but  not to modify them. If  they need to be
   modified, you must contact your domain name registrar. However, good registrars provide
   internet tools (through  HTTP/HTTPS) for modifying your records  yourself.  Notice that
   you may also add a new MX record instead of modifying the existing ones.
   
   
   
   
   
   
   --- That's all for the public part ! --------------------------------------------------
   
   
   
   
   
   
   *** Source documentation. 
   
   SMTP (Simple  Mail Transfert Protocol) was first  defined in RFC 821  (obsoleted by RFC
   2821),  which  describes  the  conversation  (protocol) between  a  sender-SMTP  and  a
   receiver-SMTP, but  not how messages are  formated. The format of  Internet messages is
   defined in RFC 822 (obsoleted by  RFC 2822).  The 'SIZE' extension (used by sender-SMTP
   for knowing  which size  of messages is  accepted by  receiver-SMTP) is defined  in RFC
   1870.  Delivery Status Notifications  (DSN) extension is defined in  RFC 3461. Standard
   mail boxes are defined in RFC 2142. Other extensions are defined in several other RFCs.
   Anti-SPAM recommendations are in RFC 2505. Securisation of Mail may be acheived through
   PEM  (Privacy Enhanced  Mail),  which  is a  end-to-end  encryption method  essentially
   transparent to SMTP. It is described in RFCs 1421, 1422, 1423 and 1424.
   
   We have tried to be as compliant as possible with RFCs 2821, 2822, 1870 and 2142.  RFCs
   are available at http://www.ietf.org.
   
  
   
   
   *** Summary of SMTP. 
   
   Below is a summary of essential  informations taken from the above documents. All lines
   are terminated by <CRLF> (i.e. ASCII 13 10 in this order). 
   
   The  client (sender-SMTP)  connects to  the  server (receiver-SMTP),  normally on  port
   25. The server sends a greeting message like this one:
   
 220 smtp.our-business.com SMTP Service Ready

   The client sends the first command, which is normally 'EHLO' (Extended Hello). However,
   the server must  also handle the old  style 'HELO' (Hello). The argument  is the domain
   name of the sender.
   
 EHLO somewhere.net               // new style (extended)
 HELO somewhere.net               // old style

   The   server  does  not   verify  that   the  connection   is  actually   comming  from
   'somewhere.net'. No safe verification procedure is available.
   
   If 'HELO' is received, the server just sends a 250 (OK) reply:
   
 250 somewhere.net welcome
   
   If 'EHLO' is received the server  sends a multiline response, indicating the extensions
   its support (the brackets [ ] meaning 'optional'):
   
 250-somewhere.net welcome
 250-SIZE [5000000]
 250-DSN
 250 HELP
   
   which  means  that  the 'SIZE',  'DSN'  and  'HELP'  extensions  are available  on  the
   server. Then the client sends the 'MAIL' command:
   
 MAIL FROM:<someone@somewhere.net> [SIZE=100000]
   
   The server answers:
   
 250 Sender OK
   
   The client sends the 'RCPT' command:
   
 RCPT TO:<myfriend@otherplace.com>
   
   At that point  the server may verifies either  that the user is local (not  the case in
   the example above:  a local user should be  something like <john@our-business.com>), or
   that there is a MX record for 'otherplace.com'. The server answers one of these:
   
 250 Recipient OK                  // continue to accept recipients
 550 No such user here             // continue to accept recipients
 553 Syntax error                  // continue to accept recipients
 503 Bad sequence of commands      // abort transaction
 552 Too many recipients           // abort transaction
   
   Several recipient  commands may be  sent by  the client, and  the server must  impose a
   limit on the number of recipients  (otherwise an attacker may send an infinite sequence
   of  recipients,  compromising  the  server).  A  minimum of  100  is  required  by  the
   protocol. A maximum of 1000 seems already a lot. Then the client sends the command:
   
 DATA
   
   The server answers:
   
 354 Start mail input; end with <CRLF>.<CRLF>
   
   The client sends the data, which end by the sequence <CRLF>.<CRLF>. However, the server
   checks that the amount of data does not exceed the limits. The answer is:
   
 250 Data Received OK
 552 Data too long  
   
   Then the client says:
   
 QUIT
   
   and the server replies:
   
 221 smtp.our-business.com Closing transmission channel
   
   
   
   
   
         
   
   
   
   
   
   
   
   
   
   
   
   
   
read tools/basis.anubis   
read tools/connections.anubis   
read tools/findstring.anubis   
     
   
   *** Converting integers to strings of length 2. 
   
   'zero_pad_2' transforms  an integer (which is  assumed to be  between 0 and 99)  into a
   string with  exactly two digits.  This is used  for formating days, hours,  minutes and
   seconds.
   
define String zero_pad_2
     (
       Int32 n
     ) =
   with s = integer_to_string(n), 
   if length(s) < 2
   then "0"+s 
   else s. 
   
   
   
   

   *** Naming journal files. 
   
   Since  journal messages  are rather  prolific, we  should have  at least  one  file per
   hour. Hence,  the name of  a journal  file must be  constructed from the  current year,
   month, day and hour. For example, it may be:
   
       2003_03_12_19
   
   (this is for the journal of 7 PM to 8 PM, 2003/mar/12). 
   
define String 
   make_current_journal_file_name
     =
   if convert_time(now) is date_and_time(y,m,d,h,_,_,_,_,_) then 
   integer_to_string(y)+"_"+
   zero_pad_2(m)+"_"+
   zero_pad_2(d)+"_"+
   zero_pad_2(h). 
   
   
   
   
   
   *** Storing a message into the journal (and showing it on the console). 
   
define One 
   log_journal_msg
     (
       SMTP_ServerDescription desc,
       String msg,
     ) =
   with msg = to_byte_array("["+virtual_machine_id+"] "+msg), 
   protect
     (
       if file(server_directory(desc)+"/journal/"+make_current_journal_file_name,append) is
         {
           failure then unique,
           success(journal_file) then 
             forget(reliable_write(file(journal_file),msg))
         };
       forget(reliable_write(file(stdout),msg))
     ).
   
   
  

   
   
   
   *** Receiving SMTP commands. 
   
   We have to receive and parse SMTP commands.  According to RFC 821, they are:
   
   
            HELO <SP> <domain> <CRLF>
            MAIL <SP> FROM:<reverse-path> <CRLF>
            RCPT <SP> TO:<forward-path> <CRLF>
            DATA <CRLF>
            RSET <CRLF>
            SEND <SP> FROM:<reverse-path> <CRLF>
            SOML <SP> FROM:<reverse-path> <CRLF>
            SAML <SP> FROM:<reverse-path> <CRLF>
            VRFY <SP> <string> <CRLF>
            EXPN <SP> <string> <CRLF>
            HELP [<SP> <string>] <CRLF>
            NOOP <CRLF>
            QUIT <CRLF>
            TURN <CRLF>

   
   Now, here  is the grammar  (copy-pasted from RFC  821), which is  in pseudo-Backus-Naur
   form (terminals are double quoted, | means 'or', [ ] means 'may be'):

            <reverse-path> ::= <path>
            <forward-path> ::= <path>
            <path> ::= "<" [ <a-d-l> ":" ] <mailbox> ">"
            <a-d-l> ::= <at-domain> | <at-domain> "," <a-d-l>
            <at-domain> ::= "@" <domain>
            <domain> ::=  <element> | <element> "." <domain>
            <element> ::= <name> | "#" <number> | "[" <dotnum> "]"
            <mailbox> ::= <local-part> "@" <domain>
            <local-part> ::= <dot-string> | <quoted-string>
            <name> ::= <a> <ldh-str> <let-dig>
            <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
            <let-dig> ::= <a> | <d>
            <let-dig-hyp> ::= <a> | <d> | "-"
            <dot-string> ::= <string> | <string> "." <dot-string>
            <string> ::= <char> | <char> <string>
            <quoted-string> ::=  """ <qtext> """
            <qtext> ::=  "\" <x> | "\" <x> <qtext> | <q> | <q> <qtext>
            <char> ::= <c> | "\" <x>
            <dotnum> ::= <snum> "." <snum> "." <snum> "." <snum>
            <number> ::= <d> | <d> <number>
            <CRLF> ::= <CR> <LF>
            <CR> ::= the carriage return character (ASCII code 13)
            <LF> ::= the line feed character (ASCII code 10)
            <SP> ::= the space character (ASCII code 32)
            <snum> ::= one, two, or three digits representing a decimal
                      integer value in the range 0 through 255
            <a> ::= any one of the 52 alphabetic characters A through Z
                      in upper case and a through z in lower case
            <c> ::= any one of the 128 ASCII characters, but not any
                      <special> or <SP>
            <d> ::= any one of the ten digits 0 through 9
            <q> ::= any one of the 128 ASCII characters except <CR>,
                      <LF>, quote ("), or backslash (\)
            <x> ::= any one of the 128 ASCII characters (no exceptions)
            <special> ::= "<" | ">" | "(" | ")" | "[" | "]" | "\" | "."
                      | "," | ";" | ":" | "@"  """ | the control
                      characters (ASCII codes 0 through 31 inclusive and
                      127)


   
   Reply codes are (copy-pasted from RFC 821): 
   
         211 System status, or system help reply
         214 Help message
            [Information on how to use the receiver or the meaning of a
            particular non-standard command; this reply is useful only
            to the human user]
         220 <domain> Service ready
         221 <domain> Service closing transmission channel
         250 Requested mail action okay, completed
         251 User not local; will forward to <forward-path>
          
         354 Start mail input; end with <CRLF>.<CRLF>
          
         421 <domain> Service not available,
             closing transmission channel
            [This may be a reply to any command if the service knows it
            must shut down]
         450 Requested mail action not taken: mailbox unavailable
            [E.g., mailbox busy]
         451 Requested action aborted: local error in processing
         452 Requested action not taken: insufficient system storage
          
         500 Syntax error, command unrecognized
            [This may include errors such as command line too long]
         501 Syntax error in parameters or arguments
         502 Command not implemented
         503 Bad sequence of commands
         504 Command parameter not implemented
         550 Requested action not taken: mailbox unavailable
            [E.g., mailbox not found, no access]
         551 User not local; please try <forward-path>
         552 Requested mail action aborted: exceeded storage allocation
         553 Requested action not taken: mailbox name not allowed
            [E.g., mailbox syntax incorrect]
         554 Transaction failed
         

   
   
   
   
   Well, in any case the command begins by  a 4 letters keyword, and is right delimited by
   <CRLF>. Hence the following first function:
   
define Maybe(String)   
   receive_command
     (
       RWStream conn,
       List(Word8) so_far
     ) =
   if *conn is 
     {
       failure then failure, 
       success(c) then 
         if c = 13    // <CR>
         then if *conn is 
           {
             failure then failure, 
             success(d) then 
               if d = 10   // <LF>
               then with result = implode(reverse(so_far)),
                  print("--> "+result+"\n"); success(result)
               else failure
           }  
         else receive_command(conn,[c . so_far])
     }. 
   
   
   


   
   *** Reading a word from a string. 
   
define Maybe(String)
   read_word
     (
       String s, 
       Int32 start,
       List(Word8) so_far
     ) =
   if nth(start,s) is 
     {
       failure then success(implode(reverse(so_far))),
       success(c) then 
         print(implode([c])+" "); 
         if c = 32    // <SP>
         then success(implode(reverse(so_far)))
         else read_word(s,start+1,[c . so_far])
     }.
   
   
   
   
   
   
   *** Sending the 'Service Ready' message. 
   
define Maybe(One)
   send_service_ready
     (
       SMTP_ServerDescription desc,
       RWStream conn,
     ) =
   if reliable_write(tcp(conn),["220 ",server_common_name(desc)," Service Ready",crlf]) is 
     {
       failure then if remote_IP_address_and_port(conn) is (peer,_) then 
         log_journal_msg(desc,"Cannot write into connection with "+peer+".\n"); failure, 
       success(_) then success(unique)
     }.
   
   
   
   
   
   
   *** Sending a 'syntax error' (500) reply. 
   
define Maybe($T)
   syntax_error
     (
       RWStream conn
     ) =
   print("Syntax error.\n"); 
   forget(reliable_write(tcp(conn),
                         ["500 Syntax error or bad sequence of commands."+crlf]));
   failure. 
   
   
   
   
   
   
   
   *** Receiving the 'HELO' command. 
   
define Maybe(String)
   receive_helo
     (
       RWStream conn
     ) = 
   if receive_command(conn,[]) is 
     {
       failure then failure, 
       success(com) then if sub_string(com,0,4) is 
         {
           failure then failure, 
           success(prefix) then 
             with p = to_lower(prefix), 
             if (p = "helo" | p = "ehlo")
             then read_word(com,5,[]) 
             else syntax_error(conn)
         }
     }.
   
   

   
   
   
   *** Sending the 'OK' message. 
   
define Maybe(One)   
   send_ok
     (
       RWStream conn
     ) =
   if reliable_write(tcp(conn),["250 OK"+crlf]) is 
     {
       failure then failure,
       success(_) then success(unique)
     }.
   
   
   
   
   
      
   
   *** Receiving the 'MAIL FROM' command. 
   
define Maybe(String)   
   receive_mail_from
     (
       RWStream conn
     ) =
   if receive_command(conn,[]) is
     {
       failure then failure, 
       success(com) then if sub_string(com,0,4) is 
         {
           failure then failure,
           success(prefix) then 
             if to_lower(prefix) = "mail"
             then if find(":",com,7) is 
               {
                 failure then syntax_error(conn),
                 success(position) then read_word(com,position,[])
               }
             else syntax_error(conn)
         }
     }.
   
   
   
   *** Sending a 'no such user' (550) reply. 
   
define Maybe($T)
   no_such_user
     (
       RWStream conn
     ) =
   forget(reliable_write(tcp(conn),
                         ["550 No such user here."+crlf]));
   failure. 
   

   
   
   
   
   *** Sending a 'start mail input' (354) reply. 
   
define Maybe(One)   
   send_start_mail_input
     (
       RWStream conn
     ) =
   if reliable_write(tcp(conn),["354 Start mail input; end with <CRLF>.<CRLF>"+crlf]) is 
     {
       failure then failure,
       success(_) then success(unique)
     }.
   

   
   
   
   
   
   *** Checking a mailbox. 
   
   The mailbox  is valid if  it is a  directory name in  the subdirectory '/users'  of the
   server directory.
   
define Maybe(String)
   check_mailbox
     (
       SMTP_ServerDescription desc, 
       String mailbox
     ) =
   if directory_full_list(server_directory(desc)+"/users","","",mailbox) is 
     {
       [ ] then failure, 
       [h . t] then 
         if h is directory(name,_,_)
         then if name = mailbox
              then print("check_mailbox OK: ---"+mailbox+"---\n"); success(mailbox)
              else failure
         else failure
     }.
   
   
     
   
   
   *** Checking a recipient.
   
define Maybe(String)
   check_recipient
     (
       SMTP_ServerDescription desc, 
       String com
     ) =
   if find(":",com,4) is 
     {
       failure then print("(check_recipient) ':' not found.\n"); 
                    failure,
       success(position) then if read_word(com,position+1,[]) is 
         {
           failure then print("(check_recipient) Cannot read recipient.\n");
                        failure,
           success(forward_path) then print("path = "+forward_path+"\n");   
             if nth(0,forward_path) is 
               {
                 failure then print("(check_recipient) forward path empty.\n"); 
                              failure, 
                 success(first) then 
                   if first = '<'
                   then if sub_string(forward_path,1,length(forward_path)-2) is 
                     {
                       failure then print("(check_recipient) !!!\n");
                                    failure, 
                       success(s) then check_mailbox(desc,s)
                     }
                   else (print("(check_recipient) ???\n"); failure)
               }
         }
     }. 
   
   
   
   
   
 
   
   *** Receiving the list of recipients. 

   We  have to  receive all  the 'RCPT  TO' commands,  sending replies,  and we  must also
   receive the 'DATA' command.
   
define Maybe(List(String))   
   receive_recipients
     (
       SMTP_ServerDescription desc, 
       RWStream conn,
       List(String) so_far
     ) =
   if receive_command(conn,[]) is 
     {
       failure then syntax_error(conn), 
       success(com) then if sub_string(com,0,4) is 
         {
           failure then syntax_error(conn), 
           success(prefix) then with prefix = to_lower(prefix), 
             if prefix = "data"
             then if send_start_mail_input(conn) is 
               {
                 failure then failure, 
                 success(_) then success(reverse(so_far))
               }
             else if prefix = "rcpt"
                  then if check_recipient(desc,com) is 
                    {
                      failure then no_such_user(conn),
                      success(mailbox) then 
                        if send_ok(conn) is 
                          {
                            failure then failure,
                            success(_) then receive_recipients(desc,conn,[mailbox . so_far])
                          }
                    }
                  else syntax_error(conn)
         }
     }.   

   
   
   
   
   
   
   *** Receiving the content of the mail. 
   
   We must read data until the sequence <CRLF> "." <CRLF>.
   
define ByteArray
   crlf_dot_crlf   
     =
   to_byte_array(implode([13,10,'.',13,10])). 
   
define Maybe(ByteArray)   
   receive_data
     (
       RWStream conn,
       ByteArray so_far
     ) =
   if read(weaken(conn),1000,480) is 
     {
       error    then failure, 
       timeout  then failure,
       ok(bytes)then 
         with new = so_far+bytes, 
                l = length(new), 
         if extract(new,l-5,l) = crlf_dot_crlf
         then success(extract(new,0,l-5))
         else receive_data(conn,new)
     }.
   
   
   
   
   
   *** Receiving the 'QUIT' command. 
   
define Maybe(One)   
   receive_quit
     (
       RWStream conn
     ) =
   if receive_command(conn,[]) is 
     {
       failure then failure, 
       success(com) then if sub_string(com,0,4) is 
         {
           failure then failure, 
           success(prefix) then with prefix = to_lower(prefix), 
             if prefix = "quit"
             then success(unique)
             else failure
         }
     }.
   
   
   
   
   
   *** Storing the mail on the server's disk. 

   
define One
   store_mail
     (
       SMTP_ServerDescription desc, 
       String peer_name, 
       String mail_from, 
       String mailbox, 
       ByteArray data
     ) =
   if (Maybe(RWStream))file(server_directory(desc)+"/users/"+mailbox+"/mail",append) is
     {
       failure then unique, 
       success(f) then 
         forget(reliable_write(file(f),data))
     }. 
   
   
   
   
define Maybe(One)   
   store_mail
     (
       SMTP_ServerDescription desc, 
       String peer_name, 
       String mail_from, 
       List(String) mailboxes, 
       ByteArray data
     ) =
   if mailboxes is
     {
       [ ] then success(unique), 
       [h . t] then 
         store_mail(desc,peer_name,mail_from,h,data);
         store_mail(desc,peer_name,mail_from,t,data)
     }. 
   
   
   
   
   
define One
   print_recipients
     (
       List(String) l
     ) =
   if l is 
     {
       [ ] then unique, 
       [h . t] then 
         print("Recipient: "+h+"\n"); print_recipients(t)
     }.
   
  
   
   *** Applying the whole SMTP protocol. 
   
define Server -> (RWStream) -> One
   smtp_handler
     (
       SMTP_ServerDescription desc
     ) = 
   (Server server) |-> (RWStream conn) |->
   if remote_IP_address_and_port(conn) is (num_peer,_) then 
   with peer = ip_addr_to_string(num_peer), 
   print("Accepting connection with "+peer+"\n"); 
   if send_service_ready(desc,conn) is 
     {
     failure then log_journal_msg(desc,"Cannot send the banner to "+peer+".\n"), 
     success(_) then if receive_helo(conn) is 
       {
       failure then log_journal_msg(desc,"Cannot receive 'HELO' from "+peer+".\n"), 
       success(peer_name) then if send_ok(conn) is 
         {
         failure then log_journal_msg(desc,
           "Cannot send 'OK' to "+peer+" ("+peer_name+").\n"),
         success(_) then if receive_mail_from(conn) is 
           {
           failure then log_journal_msg(desc,
             "Cannot receive 'MAIL FROM' from "+peer+" ("+peer_name+").\n"), 
           success(mail_from) then if send_ok(conn) is 
             {
             failure then log_journal_msg(desc,
               "Cannot send 'OK' to "+peer+" ("+peer_name+").\n"), 
             success(_) then if receive_recipients(desc,conn,[]) is 
               {
               failure then log_journal_msg(desc,
                 "Cannot receive recipients from "+peer+" ("+peer_name+").\n"),
               success(recipients) then print_recipients(recipients);
                 if receive_data(conn,constant_byte_array(0,0)) is 
                 {
                 failure then log_journal_msg(desc,
                   "Cannot receive mail data from "+peer+" ("+peer_name+").\n"),
                 success(data) then if send_ok(conn) is 
                   {
                     failure then log_journal_msg(desc,
                       "Cannot send OK to "+peer+" ("+peer_name+").\n"), 
                     success(_) then if receive_quit(conn) is 
                       {
                         failure then log_journal_msg(desc,
                            "Did not receive 'QUIT' from "+peer+" ("+peer_name+").\n"),
                         success(_) then unique
                       }; 
                       if store_mail(desc,
                                     peer_name,
                                     mail_from,
                                     recipients,
                                     data) is
                         {
                           failure then log_journal_msg(desc,
                            "Cannot store mail received from "+peer+" ("+peer_name+").\n"),
                           success(_) then unique
                         }
                       }
                   }
                 }
               }
             }
           }
         }
       }.
   
   
   
   
   *** Creating the server's directories. 
   
define One 
   create_directories
     (
       SMTP_ServerDescription desc
     ) =
   forget(make_directory(server_directory(desc)+"/journal",default_directory_mode));
   forget(make_directory(server_directory(desc)+"/users",default_directory_mode)). 
   
   
   
   
   
   *** Starting the SMTP server. 
   
   
public define One
   start_smtp_server
     (
       SMTP_ServerDescription desc
     ) =
   create_directories(desc);
   if start_server(ip_address(desc),
                   ip_port(desc), 
                   smtp_handler(desc),
                   (One u) |-> unique) is 
     {
       cannot_create_the_socket then print("Cannot create the listening socket.\n"), 
       cannot_bind_to_port      then print("Cannot bind to port "+ip_port(desc)+".\n"), 
       cannot_listen_on_port    then print("Cannot listen on port "+ip_port(desc)+".\n"),
       ok(server) then 
         print("SMTP Mail Server started on "+ip_addr_to_string(ip_address(desc))+
                  ":"+integer_to_string(ip_port(desc))+".\n")
     }.