Fossil SCM
The "fossil smtpd" command stores incoming messages in the database and routes them according to the emailroute table.
Commit
e4144ced8dc73a310f00d4a4f392023dc30598c3b6cc280c53486b57fcc3fb6f
Parent
45939f5181f7dc5…
2 files changed
+12
-12
+252
-33
+12
-12
| --- src/email.c | ||
| +++ src/email.c | ||
| @@ -13,11 +13,11 @@ | ||
| 13 | 13 | ** [email protected] |
| 14 | 14 | ** http://www.hwaci.com/drh/ |
| 15 | 15 | ** |
| 16 | 16 | ******************************************************************************* |
| 17 | 17 | ** |
| 18 | -** Email notification features | |
| 18 | +** Logic for email notification, also known as "alerts". | |
| 19 | 19 | */ |
| 20 | 20 | #include "config.h" |
| 21 | 21 | #include "email.h" |
| 22 | 22 | #include <assert.h> |
| 23 | 23 | |
| @@ -393,11 +393,11 @@ | ||
| 393 | 393 | p->db = 0; |
| 394 | 394 | p->zDb = 0; |
| 395 | 395 | p->zDir = 0; |
| 396 | 396 | p->zCmd = 0; |
| 397 | 397 | p->zDest = "off"; |
| 398 | - blob_zero(&p->out); | |
| 398 | + blob_reset(&p->out); | |
| 399 | 399 | } |
| 400 | 400 | |
| 401 | 401 | /* |
| 402 | 402 | ** Put the EmailSender into an error state. |
| 403 | 403 | */ |
| @@ -571,11 +571,11 @@ | ||
| 571 | 571 | blob_write_to_file(&all, zFile); |
| 572 | 572 | fossil_free(zFile); |
| 573 | 573 | }else if( strcmp(p->zDest, "stdout")==0 ){ |
| 574 | 574 | fossil_print("%s\n", blob_str(&all)); |
| 575 | 575 | } |
| 576 | - blob_zero(&all); | |
| 576 | + blob_reset(&all); | |
| 577 | 577 | } |
| 578 | 578 | |
| 579 | 579 | /* |
| 580 | 580 | ** Analyze and act on a received email. |
| 581 | 581 | ** |
| @@ -585,11 +585,11 @@ | ||
| 585 | 585 | ** This routine acts on all email messages received from the |
| 586 | 586 | ** "fossil email inbound" command. |
| 587 | 587 | */ |
| 588 | 588 | void email_receive(Blob *pMsg){ |
| 589 | 589 | /* To Do: Look for bounce messages and possibly disable subscriptions */ |
| 590 | - blob_zero(pMsg); | |
| 590 | + blob_reset(pMsg); | |
| 591 | 591 | } |
| 592 | 592 | |
| 593 | 593 | /* |
| 594 | 594 | ** SETTING: email-send-method width=5 default=off |
| 595 | 595 | ** Determine the method used to send email. Allowed values are |
| @@ -715,11 +715,11 @@ | ||
| 715 | 715 | "This will erase all content in the repository tables, thus\n" |
| 716 | 716 | "deleting all subscriber information. The information will be\n" |
| 717 | 717 | "unrecoverable.\n"); |
| 718 | 718 | prompt_user("Continue? (y/N) ", &yn); |
| 719 | 719 | c = blob_str(&yn)[0]; |
| 720 | - blob_zero(&yn); | |
| 720 | + blob_reset(&yn); | |
| 721 | 721 | } |
| 722 | 722 | if( c=='y' ){ |
| 723 | 723 | email_triggers_disable(); |
| 724 | 724 | db_multi_exec( |
| 725 | 725 | "DROP TABLE IF EXISTS subscriber;\n" |
| @@ -756,13 +756,13 @@ | ||
| 756 | 756 | } |
| 757 | 757 | blob_add_final_newline(&body); |
| 758 | 758 | pSender = email_sender_new(zDest, 1); |
| 759 | 759 | email_send(pSender, &hdr, &body); |
| 760 | 760 | email_sender_free(pSender); |
| 761 | - blob_zero(&hdr); | |
| 762 | - blob_zero(&body); | |
| 763 | - blob_zero(&prompt); | |
| 761 | + blob_reset(&hdr); | |
| 762 | + blob_reset(&body); | |
| 763 | + blob_reset(&prompt); | |
| 764 | 764 | }else |
| 765 | 765 | if( strncmp(zCmd, "settings", nCmd)==0 ){ |
| 766 | 766 | int isGlobal = find_option("global",0,0)!=0; |
| 767 | 767 | int nSetting; |
| 768 | 768 | const Setting *pSetting = setting_info(&nSetting); |
| @@ -1567,11 +1567,11 @@ | ||
| 1567 | 1567 | ** Free a linked list of EmailEvent objects |
| 1568 | 1568 | */ |
| 1569 | 1569 | void email_free_eventlist(EmailEvent *p){ |
| 1570 | 1570 | while( p ){ |
| 1571 | 1571 | EmailEvent *pNext = p->pNext; |
| 1572 | - blob_zero(&p->txt); | |
| 1572 | + blob_reset(&p->txt); | |
| 1573 | 1573 | fossil_free(p); |
| 1574 | 1574 | p = pNext; |
| 1575 | 1575 | } |
| 1576 | 1576 | } |
| 1577 | 1577 | |
| @@ -1700,11 +1700,11 @@ | ||
| 1700 | 1700 | blob_append(&out, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 1701 | 1701 | } |
| 1702 | 1702 | email_free_eventlist(pEvent); |
| 1703 | 1703 | email_footer(&out); |
| 1704 | 1704 | fossil_print("%s", blob_str(&out)); |
| 1705 | - blob_zero(&out); | |
| 1705 | + blob_reset(&out); | |
| 1706 | 1706 | db_end_transaction(0); |
| 1707 | 1707 | } |
| 1708 | 1708 | |
| 1709 | 1709 | /* |
| 1710 | 1710 | ** COMMAND: test-add-alerts |
| @@ -1822,12 +1822,12 @@ | ||
| 1822 | 1822 | zUrl, zCode); |
| 1823 | 1823 | email_send(pSender,&hdr,&body); |
| 1824 | 1824 | blob_truncate(&hdr, 0); |
| 1825 | 1825 | blob_truncate(&body, 0); |
| 1826 | 1826 | } |
| 1827 | - blob_zero(&hdr); | |
| 1828 | - blob_zero(&body); | |
| 1827 | + blob_reset(&hdr); | |
| 1828 | + blob_reset(&body); | |
| 1829 | 1829 | db_finalize(&q); |
| 1830 | 1830 | email_free_eventlist(pEvents); |
| 1831 | 1831 | if( (flags & SENDALERT_PRESERVE)==0 ){ |
| 1832 | 1832 | if( flags & SENDALERT_DIGEST ){ |
| 1833 | 1833 | db_multi_exec("UPDATE pending_alert SET sentDigest=true"); |
| 1834 | 1834 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -13,11 +13,11 @@ | |
| 13 | ** [email protected] |
| 14 | ** http://www.hwaci.com/drh/ |
| 15 | ** |
| 16 | ******************************************************************************* |
| 17 | ** |
| 18 | ** Email notification features |
| 19 | */ |
| 20 | #include "config.h" |
| 21 | #include "email.h" |
| 22 | #include <assert.h> |
| 23 | |
| @@ -393,11 +393,11 @@ | |
| 393 | p->db = 0; |
| 394 | p->zDb = 0; |
| 395 | p->zDir = 0; |
| 396 | p->zCmd = 0; |
| 397 | p->zDest = "off"; |
| 398 | blob_zero(&p->out); |
| 399 | } |
| 400 | |
| 401 | /* |
| 402 | ** Put the EmailSender into an error state. |
| 403 | */ |
| @@ -571,11 +571,11 @@ | |
| 571 | blob_write_to_file(&all, zFile); |
| 572 | fossil_free(zFile); |
| 573 | }else if( strcmp(p->zDest, "stdout")==0 ){ |
| 574 | fossil_print("%s\n", blob_str(&all)); |
| 575 | } |
| 576 | blob_zero(&all); |
| 577 | } |
| 578 | |
| 579 | /* |
| 580 | ** Analyze and act on a received email. |
| 581 | ** |
| @@ -585,11 +585,11 @@ | |
| 585 | ** This routine acts on all email messages received from the |
| 586 | ** "fossil email inbound" command. |
| 587 | */ |
| 588 | void email_receive(Blob *pMsg){ |
| 589 | /* To Do: Look for bounce messages and possibly disable subscriptions */ |
| 590 | blob_zero(pMsg); |
| 591 | } |
| 592 | |
| 593 | /* |
| 594 | ** SETTING: email-send-method width=5 default=off |
| 595 | ** Determine the method used to send email. Allowed values are |
| @@ -715,11 +715,11 @@ | |
| 715 | "This will erase all content in the repository tables, thus\n" |
| 716 | "deleting all subscriber information. The information will be\n" |
| 717 | "unrecoverable.\n"); |
| 718 | prompt_user("Continue? (y/N) ", &yn); |
| 719 | c = blob_str(&yn)[0]; |
| 720 | blob_zero(&yn); |
| 721 | } |
| 722 | if( c=='y' ){ |
| 723 | email_triggers_disable(); |
| 724 | db_multi_exec( |
| 725 | "DROP TABLE IF EXISTS subscriber;\n" |
| @@ -756,13 +756,13 @@ | |
| 756 | } |
| 757 | blob_add_final_newline(&body); |
| 758 | pSender = email_sender_new(zDest, 1); |
| 759 | email_send(pSender, &hdr, &body); |
| 760 | email_sender_free(pSender); |
| 761 | blob_zero(&hdr); |
| 762 | blob_zero(&body); |
| 763 | blob_zero(&prompt); |
| 764 | }else |
| 765 | if( strncmp(zCmd, "settings", nCmd)==0 ){ |
| 766 | int isGlobal = find_option("global",0,0)!=0; |
| 767 | int nSetting; |
| 768 | const Setting *pSetting = setting_info(&nSetting); |
| @@ -1567,11 +1567,11 @@ | |
| 1567 | ** Free a linked list of EmailEvent objects |
| 1568 | */ |
| 1569 | void email_free_eventlist(EmailEvent *p){ |
| 1570 | while( p ){ |
| 1571 | EmailEvent *pNext = p->pNext; |
| 1572 | blob_zero(&p->txt); |
| 1573 | fossil_free(p); |
| 1574 | p = pNext; |
| 1575 | } |
| 1576 | } |
| 1577 | |
| @@ -1700,11 +1700,11 @@ | |
| 1700 | blob_append(&out, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 1701 | } |
| 1702 | email_free_eventlist(pEvent); |
| 1703 | email_footer(&out); |
| 1704 | fossil_print("%s", blob_str(&out)); |
| 1705 | blob_zero(&out); |
| 1706 | db_end_transaction(0); |
| 1707 | } |
| 1708 | |
| 1709 | /* |
| 1710 | ** COMMAND: test-add-alerts |
| @@ -1822,12 +1822,12 @@ | |
| 1822 | zUrl, zCode); |
| 1823 | email_send(pSender,&hdr,&body); |
| 1824 | blob_truncate(&hdr, 0); |
| 1825 | blob_truncate(&body, 0); |
| 1826 | } |
| 1827 | blob_zero(&hdr); |
| 1828 | blob_zero(&body); |
| 1829 | db_finalize(&q); |
| 1830 | email_free_eventlist(pEvents); |
| 1831 | if( (flags & SENDALERT_PRESERVE)==0 ){ |
| 1832 | if( flags & SENDALERT_DIGEST ){ |
| 1833 | db_multi_exec("UPDATE pending_alert SET sentDigest=true"); |
| 1834 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -13,11 +13,11 @@ | |
| 13 | ** [email protected] |
| 14 | ** http://www.hwaci.com/drh/ |
| 15 | ** |
| 16 | ******************************************************************************* |
| 17 | ** |
| 18 | ** Logic for email notification, also known as "alerts". |
| 19 | */ |
| 20 | #include "config.h" |
| 21 | #include "email.h" |
| 22 | #include <assert.h> |
| 23 | |
| @@ -393,11 +393,11 @@ | |
| 393 | p->db = 0; |
| 394 | p->zDb = 0; |
| 395 | p->zDir = 0; |
| 396 | p->zCmd = 0; |
| 397 | p->zDest = "off"; |
| 398 | blob_reset(&p->out); |
| 399 | } |
| 400 | |
| 401 | /* |
| 402 | ** Put the EmailSender into an error state. |
| 403 | */ |
| @@ -571,11 +571,11 @@ | |
| 571 | blob_write_to_file(&all, zFile); |
| 572 | fossil_free(zFile); |
| 573 | }else if( strcmp(p->zDest, "stdout")==0 ){ |
| 574 | fossil_print("%s\n", blob_str(&all)); |
| 575 | } |
| 576 | blob_reset(&all); |
| 577 | } |
| 578 | |
| 579 | /* |
| 580 | ** Analyze and act on a received email. |
| 581 | ** |
| @@ -585,11 +585,11 @@ | |
| 585 | ** This routine acts on all email messages received from the |
| 586 | ** "fossil email inbound" command. |
| 587 | */ |
| 588 | void email_receive(Blob *pMsg){ |
| 589 | /* To Do: Look for bounce messages and possibly disable subscriptions */ |
| 590 | blob_reset(pMsg); |
| 591 | } |
| 592 | |
| 593 | /* |
| 594 | ** SETTING: email-send-method width=5 default=off |
| 595 | ** Determine the method used to send email. Allowed values are |
| @@ -715,11 +715,11 @@ | |
| 715 | "This will erase all content in the repository tables, thus\n" |
| 716 | "deleting all subscriber information. The information will be\n" |
| 717 | "unrecoverable.\n"); |
| 718 | prompt_user("Continue? (y/N) ", &yn); |
| 719 | c = blob_str(&yn)[0]; |
| 720 | blob_reset(&yn); |
| 721 | } |
| 722 | if( c=='y' ){ |
| 723 | email_triggers_disable(); |
| 724 | db_multi_exec( |
| 725 | "DROP TABLE IF EXISTS subscriber;\n" |
| @@ -756,13 +756,13 @@ | |
| 756 | } |
| 757 | blob_add_final_newline(&body); |
| 758 | pSender = email_sender_new(zDest, 1); |
| 759 | email_send(pSender, &hdr, &body); |
| 760 | email_sender_free(pSender); |
| 761 | blob_reset(&hdr); |
| 762 | blob_reset(&body); |
| 763 | blob_reset(&prompt); |
| 764 | }else |
| 765 | if( strncmp(zCmd, "settings", nCmd)==0 ){ |
| 766 | int isGlobal = find_option("global",0,0)!=0; |
| 767 | int nSetting; |
| 768 | const Setting *pSetting = setting_info(&nSetting); |
| @@ -1567,11 +1567,11 @@ | |
| 1567 | ** Free a linked list of EmailEvent objects |
| 1568 | */ |
| 1569 | void email_free_eventlist(EmailEvent *p){ |
| 1570 | while( p ){ |
| 1571 | EmailEvent *pNext = p->pNext; |
| 1572 | blob_reset(&p->txt); |
| 1573 | fossil_free(p); |
| 1574 | p = pNext; |
| 1575 | } |
| 1576 | } |
| 1577 | |
| @@ -1700,11 +1700,11 @@ | |
| 1700 | blob_append(&out, blob_buffer(&p->txt), blob_size(&p->txt)); |
| 1701 | } |
| 1702 | email_free_eventlist(pEvent); |
| 1703 | email_footer(&out); |
| 1704 | fossil_print("%s", blob_str(&out)); |
| 1705 | blob_reset(&out); |
| 1706 | db_end_transaction(0); |
| 1707 | } |
| 1708 | |
| 1709 | /* |
| 1710 | ** COMMAND: test-add-alerts |
| @@ -1822,12 +1822,12 @@ | |
| 1822 | zUrl, zCode); |
| 1823 | email_send(pSender,&hdr,&body); |
| 1824 | blob_truncate(&hdr, 0); |
| 1825 | blob_truncate(&body, 0); |
| 1826 | } |
| 1827 | blob_reset(&hdr); |
| 1828 | blob_reset(&body); |
| 1829 | db_finalize(&q); |
| 1830 | email_free_eventlist(pEvents); |
| 1831 | if( (flags & SENDALERT_PRESERVE)==0 ){ |
| 1832 | if( flags & SENDALERT_DIGEST ){ |
| 1833 | db_multi_exec("UPDATE pending_alert SET sentDigest=true"); |
| 1834 |
+252
-33
| --- src/smtp.c | ||
| +++ src/smtp.c | ||
| @@ -135,11 +135,11 @@ | ||
| 135 | 135 | /* |
| 136 | 136 | ** Shutdown an SmtpSession |
| 137 | 137 | */ |
| 138 | 138 | void smtp_session_free(SmtpSession *pSession){ |
| 139 | 139 | socket_close(); |
| 140 | - blob_zero(&pSession->inbuf); | |
| 140 | + blob_reset(&pSession->inbuf); | |
| 141 | 141 | fossil_free(pSession->zHostname); |
| 142 | 142 | fossil_free(pSession->zErr); |
| 143 | 143 | fossil_free(pSession); |
| 144 | 144 | } |
| 145 | 145 | |
| @@ -228,11 +228,11 @@ | ||
| 228 | 228 | } |
| 229 | 229 | if( p->smtpFlags & SMTP_TRACE_BLOB ){ |
| 230 | 230 | blob_appendf(p->pTranscript, "C: %.*s\n", n-2, z); |
| 231 | 231 | } |
| 232 | 232 | socket_send(0, z, n); |
| 233 | - blob_zero(&b); | |
| 233 | + blob_reset(&b); | |
| 234 | 234 | } |
| 235 | 235 | |
| 236 | 236 | /* |
| 237 | 237 | ** Read a line of input received from the SMTP server. Make in point |
| 238 | 238 | ** to the next input line. |
| @@ -439,12 +439,12 @@ | ||
| 439 | 439 | blob_append(&out, "\r\n", 2); |
| 440 | 440 | } |
| 441 | 441 | } |
| 442 | 442 | blob_append(&out, ".\r\n", 3); |
| 443 | 443 | xSend(pArg, blob_buffer(&out), blob_size(&out)); |
| 444 | - blob_zero(&out); | |
| 445 | - blob_zero(&line); | |
| 444 | + blob_reset(&out); | |
| 445 | + blob_reset(&line); | |
| 446 | 446 | } |
| 447 | 447 | |
| 448 | 448 | /* A sender function appropriate for use by smtp_send_email_body() to |
| 449 | 449 | ** send all content to the console, for testing. |
| 450 | 450 | */ |
| @@ -464,11 +464,11 @@ | ||
| 464 | 464 | void test_smtp_senddata(void){ |
| 465 | 465 | Blob f; |
| 466 | 466 | if( g.argc!=3 ) usage("FILE"); |
| 467 | 467 | blob_read_from_file(&f, g.argv[2], ExtFILE); |
| 468 | 468 | smtp_send_email_body(blob_str(&f), smtp_test_sender, 0); |
| 469 | - blob_zero(&f); | |
| 469 | + blob_reset(&f); | |
| 470 | 470 | } |
| 471 | 471 | |
| 472 | 472 | /* |
| 473 | 473 | ** Send a single email message to the SMTP server. |
| 474 | 474 | ** |
| @@ -588,30 +588,104 @@ | ||
| 588 | 588 | smtp_client_quit(p); |
| 589 | 589 | if( p->zErr ){ |
| 590 | 590 | fossil_fatal("ERROR: %s\n", p->zErr); |
| 591 | 591 | } |
| 592 | 592 | smtp_session_free(p); |
| 593 | - blob_zero(&body); | |
| 593 | + blob_reset(&body); | |
| 594 | 594 | } |
| 595 | 595 | |
| 596 | 596 | /***************************************************************************** |
| 597 | 597 | ** Server implementation |
| 598 | 598 | *****************************************************************************/ |
| 599 | + | |
| 600 | +/* | |
| 601 | +** Schema used by the email processing system. | |
| 602 | +*/ | |
| 603 | +static const char zEmailSchema[] = | |
| 604 | +@ -- bulk storage is in a separate table. This table can store either | |
| 605 | +@ -- the body of email messages or transcripts of smtp sessions. | |
| 606 | +@ CREATE TABLE IF NOT EXISTS repository.emailblob( | |
| 607 | +@ emailid INTEGER PRIMARY KEY, -- numeric idea for the entry | |
| 608 | +@ ets INT, -- Corresponding transcript, or NULL | |
| 609 | +@ etime INT, -- insertion time, secs since 1970 | |
| 610 | +@ etxt TEXT -- content of this entry | |
| 611 | +@ ); | |
| 612 | +@ | |
| 613 | +@ -- One row for each mailbox entry. All users emails are stored in | |
| 614 | +@ -- this same table. | |
| 615 | +@ CREATE TABLE IF NOT EXISTS repository.emailbox( | |
| 616 | +@ euser TEXT, -- User who received this email | |
| 617 | +@ edate INT, -- Date received. Seconds since 1970 | |
| 618 | +@ efrom TEXT, -- Who is the email from | |
| 619 | +@ emsgid INT, -- Raw email text | |
| 620 | +@ ets INT, -- Transcript of the receiving SMTP session | |
| 621 | +@ estate INT, -- Unread, read, starred, etc. | |
| 622 | +@ esubject TEXT -- Subject line for display | |
| 623 | +@ ); | |
| 624 | +@ | |
| 625 | +@ -- Information on how to deliver incoming email. | |
| 626 | +@ CREATE TABLE IF NOT EXISTS repository.emailroute( | |
| 627 | +@ eaddr TEXT PRIMARY KEY, -- Email address | |
| 628 | +@ epolicy TEXT -- How to handle email sent to this address | |
| 629 | +@ ) WITHOUT ROWID; | |
| 630 | +@ | |
| 631 | +@ -- Outgoing email queue | |
| 632 | +@ CREATE TABLE IF NOT EXISTS repository.emailoutq( | |
| 633 | +@ edomain TEXT, -- Destination domain. (ex: "fossil-scm.org") | |
| 634 | +@ efrom TEXT, -- Sender email address | |
| 635 | +@ eto TEXT, -- Receipient email address | |
| 636 | +@ emsgid INT, -- Message body in the emailblob table | |
| 637 | +@ ectime INT, -- Time enqueued. Seconds since 1970 | |
| 638 | +@ emtime INT, -- Time of last send attempt. Sec since 1970 | |
| 639 | +@ ensend INT, -- Number of send attempts | |
| 640 | +@ ets INT -- Transcript of last failed attempt | |
| 641 | +@ ); | |
| 642 | +; | |
| 643 | + | |
| 644 | +/* | |
| 645 | +** Code used to delete the email tables. | |
| 646 | +*/ | |
| 647 | +static const char zEmailDrop[] = | |
| 648 | +@ DROP TABLE IF EXISTS emailblob; | |
| 649 | +@ DROP TABLE IF EXISTS emailbox; | |
| 650 | +@ DROP TABLE IF EXISTS emailroute; | |
| 651 | +@ DROP TABLE IF EXISTS emailqueue; | |
| 652 | +; | |
| 653 | + | |
| 654 | +/* | |
| 655 | +** Populate the schema of a database. | |
| 656 | +** | |
| 657 | +** eForce==0 Fast | |
| 658 | +** eForce==1 Run CREATE TABLE statements every time | |
| 659 | +** eForce==2 DROP then rerun CREATE TABLE | |
| 660 | +*/ | |
| 661 | +void smtp_server_schema(int eForce){ | |
| 662 | + if( eForce==2 ){ | |
| 663 | + db_multi_exec(zEmailDrop/*works-like:""*/); | |
| 664 | + } | |
| 665 | + if( eForce==1 || !db_table_exists("repository","emailblob") ){ | |
| 666 | + db_multi_exec(zEmailSchema/*works-like:""*/); | |
| 667 | + } | |
| 668 | +} | |
| 599 | 669 | |
| 600 | 670 | #if LOCAL_INTERFACE |
| 601 | 671 | /* |
| 602 | 672 | ** State information for the server |
| 603 | 673 | */ |
| 604 | 674 | struct SmtpServer { |
| 605 | - sqlite3 *db; /* Database into which the email is delivered */ | |
| 606 | - char *zEhlo; /* Client domain on the EHLO line */ | |
| 607 | - char *zFrom; /* MAIL FROM: argument */ | |
| 608 | - int nTo; /* Number of RCPT TO: lines seen */ | |
| 609 | - char **azTo; /* Address in each RCPT TO line */ | |
| 610 | - u32 srvrFlags; /* Control flags */ | |
| 611 | - Blob msg; /* Content following DATA */ | |
| 612 | - Blob transcript; /* Session transcript */ | |
| 675 | + sqlite3_int64 idTranscript; /* Transcript ID number */ | |
| 676 | + sqlite3_int64 idMsg; /* Message ID number */ | |
| 677 | + char *zEhlo; /* Client domain on the EHLO line */ | |
| 678 | + char *zFrom; /* MAIL FROM: argument */ | |
| 679 | + int nTo; /* Number of RCPT TO: lines seen */ | |
| 680 | + struct SmtpTo { | |
| 681 | + char *z; /* Address in each RCPT TO line */ | |
| 682 | + int okRemote; /* zTo can be in another domain */ | |
| 683 | + } *aTo; | |
| 684 | + u32 srvrFlags; /* Control flags */ | |
| 685 | + Blob msg; /* Content following DATA */ | |
| 686 | + Blob transcript; /* Session transcript */ | |
| 613 | 687 | }; |
| 614 | 688 | |
| 615 | 689 | #define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */ |
| 616 | 690 | #define SMTPSRV_CLEAR_ALL 2 /* smtp_server_clear() everything */ |
| 617 | 691 | #define SMTPSRV_LOG 0x001 /* Record a transcript of the interaction */ |
| @@ -626,20 +700,20 @@ | ||
| 626 | 700 | static void smtp_server_clear(SmtpServer *p, int eHowMuch){ |
| 627 | 701 | int i; |
| 628 | 702 | if( eHowMuch>=SMTPSRV_CLEAR_MSG ){ |
| 629 | 703 | fossil_free(p->zFrom); |
| 630 | 704 | p->zFrom = 0; |
| 631 | - for(i=0; i<p->nTo; i++) fossil_free(p->azTo[0]); | |
| 632 | - fossil_free(p->azTo); | |
| 633 | - p->azTo = 0; | |
| 705 | + for(i=0; i<p->nTo; i++) fossil_free(p->aTo[i].z); | |
| 706 | + fossil_free(p->aTo); | |
| 707 | + p->aTo = 0; | |
| 634 | 708 | p->nTo = 0; |
| 635 | - blob_zero(&p->msg); | |
| 709 | + blob_reset(&p->msg); | |
| 710 | + p->idMsg = 0; | |
| 636 | 711 | } |
| 637 | 712 | if( eHowMuch>=SMTPSRV_CLEAR_ALL ){ |
| 638 | - blob_zero(&p->transcript); | |
| 639 | - sqlite3_close(p->db); | |
| 640 | - p->db = 0; | |
| 713 | + blob_reset(&p->transcript); | |
| 714 | + p->idTranscript = 0; | |
| 641 | 715 | fossil_free(p->zEhlo); |
| 642 | 716 | p->zEhlo = 0; |
| 643 | 717 | } |
| 644 | 718 | } |
| 645 | 719 | |
| @@ -649,10 +723,33 @@ | ||
| 649 | 723 | static void smtp_server_init(SmtpServer *p){ |
| 650 | 724 | memset(p, 0, sizeof(*p)); |
| 651 | 725 | blob_init(&p->msg, 0, 0); |
| 652 | 726 | blob_init(&p->transcript, 0, 0); |
| 653 | 727 | } |
| 728 | + | |
| 729 | +/* | |
| 730 | +** Append a new TO entry to the SmtpServer object. Do not do the | |
| 731 | +** append if the same entry is already on the list. | |
| 732 | +** | |
| 733 | +** The zAddr argument is obtained from fossil_malloc(). This | |
| 734 | +** routine assumes ownership of the allocation. | |
| 735 | +*/ | |
| 736 | +static void smtp_append_to(SmtpServer *p, char *zAddr, int okRemote){ | |
| 737 | + int i; | |
| 738 | + for(i=0; zAddr[i]; i++){ zAddr[i] = fossil_tolower(zAddr[i]); } | |
| 739 | + for(i=0; i<p->nTo; i++){ | |
| 740 | + if( strcmp(zAddr, p->aTo[i].z)==0 ){ | |
| 741 | + fossil_free(zAddr); | |
| 742 | + if( p->aTo[i].okRemote==0 ) p->aTo[i].okRemote = okRemote; | |
| 743 | + return; | |
| 744 | + } | |
| 745 | + } | |
| 746 | + p->aTo = fossil_realloc(p->aTo, (p->nTo+1)*sizeof(p->aTo[0])); | |
| 747 | + p->aTo[p->nTo].z = zAddr; | |
| 748 | + p->aTo[p->nTo].okRemote = okRemote; | |
| 749 | + p->nTo++; | |
| 750 | +} | |
| 654 | 751 | |
| 655 | 752 | /* |
| 656 | 753 | ** Send a single line of output from the server to the client. |
| 657 | 754 | */ |
| 658 | 755 | static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){ |
| @@ -674,11 +771,11 @@ | ||
| 674 | 771 | if( p->srvrFlags & SMTPSRV_STDERR ){ |
| 675 | 772 | fprintf(stderr, "S: %.*s\n", n-2, z); |
| 676 | 773 | } |
| 677 | 774 | fwrite(z, n, 1, stdout); |
| 678 | 775 | fflush(stdout); |
| 679 | - blob_zero(&b); | |
| 776 | + blob_reset(&b); | |
| 680 | 777 | } |
| 681 | 778 | |
| 682 | 779 | /* |
| 683 | 780 | ** Read a single line from the client. |
| 684 | 781 | */ |
| @@ -718,33 +815,147 @@ | ||
| 718 | 815 | nLine, blob_size(&p->msg)); |
| 719 | 816 | } |
| 720 | 817 | } |
| 721 | 818 | |
| 722 | 819 | /* |
| 723 | -** COMMAND: smtp | |
| 724 | -** | |
| 725 | -** Usage: %fossil smtp [options] DBNAME | |
| 726 | -** | |
| 727 | -** Begin a SMTP conversation with a client using stdin/stdout. (This | |
| 728 | -** command is expected to be launched from xinetd or the equivalent.) | |
| 729 | -** Use information in the SQLite database at DBNAME to find configuration | |
| 730 | -** information and as a place to store the incoming content. | |
| 820 | +** Send an email to a single email addess that is registered with | |
| 821 | +** this system, according to the instructions in emailroute. If | |
| 822 | +** zAddr is not in the emailroute table, then this routine is a | |
| 823 | +** no-op. Or if zAddr has already been processed, then this | |
| 824 | +** routine is a no-op. | |
| 825 | +*/ | |
| 826 | +static void smtp_server_send_one_user( | |
| 827 | + SmtpServer *p, /* The current inbound email */ | |
| 828 | + const char *zAddr, /* Who to forward this to */ | |
| 829 | + int okRemote /* True if ok to foward to another domain */ | |
| 830 | +){ | |
| 831 | + char *zPolicy; | |
| 832 | + Blob policy, line, token, tail; | |
| 833 | + | |
| 834 | + zPolicy = db_text(0, | |
| 835 | + "SELECT epolicy FROM emailroute WHERE eaddr=%Q", zAddr); | |
| 836 | + if( zPolicy==0 ){ | |
| 837 | + if( okRemote ){ | |
| 838 | + int i; | |
| 839 | + for(i=0; zAddr[i] && zAddr[i]!='@'; i++){} | |
| 840 | + if( zAddr[i]=='@' && zAddr[i+1]!=0 ){ | |
| 841 | + db_multi_exec( | |
| 842 | + "INSERT INTO emailoutq(edomain,efrom,eto,emsgid,ectime," | |
| 843 | + "emtime,ensend)" | |
| 844 | + "VALUES(%Q,%Q,%Q,%lld,now(),0,0)", | |
| 845 | + zAddr+i+1, p->zFrom, zAddr, p->idMsg | |
| 846 | + ); | |
| 847 | + } | |
| 848 | + } | |
| 849 | + return; | |
| 850 | + } | |
| 851 | + blob_init(&policy, zPolicy, -1); | |
| 852 | + while( blob_line(&policy, &line) ){ | |
| 853 | + blob_trim(&line); | |
| 854 | + blob_token(&line, &token); | |
| 855 | + blob_tail(&line, &tail); | |
| 856 | + if( blob_size(&tail)==0 ) continue; | |
| 857 | + if( blob_eq_str(&token, "mbox", 4) ){ | |
| 858 | + db_multi_exec( | |
| 859 | + "INSERT INTO emailbox(euser,edate,efrom,emsgid,ets,estate)" | |
| 860 | + " VALUES(%Q,now(),%Q,%lld,%lld,0)", | |
| 861 | + blob_str(&tail), p->zFrom, p->idMsg, p->idTranscript | |
| 862 | + ); | |
| 863 | + } | |
| 864 | + if( blob_eq_str(&token, "forward", 7) ){ | |
| 865 | + smtp_append_to(p, fossil_strdup(blob_str(&tail)), 1); | |
| 866 | + } | |
| 867 | + blob_reset(&tail); | |
| 868 | + } | |
| 869 | +} | |
| 870 | + | |
| 871 | +/* | |
| 872 | +** The SmtpServer object contains a complete incoming email. | |
| 873 | +** Add this email to the database. | |
| 874 | +*/ | |
| 875 | +static void smtp_server_route_incoming(SmtpServer *p, int bFinish){ | |
| 876 | + Stmt s; | |
| 877 | + int i, j; | |
| 878 | + if( p->zFrom && p->nTo && blob_size(&p->msg) ){ | |
| 879 | + db_begin_transaction(); | |
| 880 | + if( p->idTranscript==0 ) smtp_server_schema(0); | |
| 881 | + db_prepare(&s, | |
| 882 | + "INSERT INTO emailblob(ets,etime,etxt)" | |
| 883 | + " VALUES(:ets,now(),:etxt)" | |
| 884 | + ); | |
| 885 | + if( !bFinish && p->idTranscript==0 ){ | |
| 886 | + db_bind_null(&s, ":ets"); | |
| 887 | + db_bind_null(&s, ":etxt"); | |
| 888 | + db_step(&s); | |
| 889 | + db_reset(&s); | |
| 890 | + p->idTranscript = db_last_insert_rowid(); | |
| 891 | + }else if( bFinish ){ | |
| 892 | + if( p->idTranscript ){ | |
| 893 | + db_multi_exec("UPDATE emailblob SET etxt=%Q WHERE emailid=%lld", | |
| 894 | + blob_str(&p->transcript), p->idTranscript); | |
| 895 | + }else{ | |
| 896 | + db_bind_null(&s, ":ets"); | |
| 897 | + db_bind_str(&s, ":etxt", &p->transcript); | |
| 898 | + db_step(&s); | |
| 899 | + db_reset(&s); | |
| 900 | + p->idTranscript = db_last_insert_rowid(); | |
| 901 | + } | |
| 902 | + } | |
| 903 | + db_bind_int64(&s, ":ets", p->idTranscript); | |
| 904 | + db_bind_str(&s, ":etxt", &p->msg); | |
| 905 | + db_step(&s); | |
| 906 | + db_finalize(&s); | |
| 907 | + p->idMsg = db_last_insert_rowid(); | |
| 908 | + | |
| 909 | + /* make entries in emailbox and emailoutq */ | |
| 910 | + for(i=0; i<p->nTo; i++){ | |
| 911 | + int okRemote = p->aTo[i].okRemote; | |
| 912 | + p->aTo[i].okRemote = 1; | |
| 913 | + smtp_server_send_one_user(p, p->aTo[i].z, okRemote); | |
| 914 | + } | |
| 915 | + | |
| 916 | + /* Finish the transaction after all changes are implemented */ | |
| 917 | + db_end_transaction(0); | |
| 918 | + } | |
| 919 | + smtp_server_clear(p, SMTPSRV_CLEAR_MSG); | |
| 920 | +} | |
| 921 | + | |
| 922 | +/* | |
| 923 | +** Make a copy of the input string up to but not including the | |
| 924 | +** first ">" character. | |
| 925 | +*/ | |
| 926 | +static char *extractEmail(const char *z){ | |
| 927 | + int i; | |
| 928 | + for(i=0; z[i] && z[i]!='>'; i++){} | |
| 929 | + return mprintf("%.*s", i, z); | |
| 930 | +} | |
| 931 | + | |
| 932 | +/* | |
| 933 | +** COMMAND: smtpd | |
| 934 | +** | |
| 935 | +** Usage: %fossil smtpd [OPTIONS] REPOSITORY | |
| 936 | +** | |
| 937 | +** Begin a SMTP conversation with a client using stdin/stdout. The | |
| 938 | +** received email is stored in REPOSITORY | |
| 939 | +** | |
| 731 | 940 | */ |
| 732 | 941 | void smtp_server(void){ |
| 733 | 942 | char *zDbName; |
| 734 | 943 | const char *zDomain; |
| 735 | 944 | SmtpServer x; |
| 736 | 945 | char z[5000]; |
| 737 | 946 | |
| 738 | 947 | smtp_server_init(&x); |
| 739 | 948 | zDomain = find_option("domain",0,1); |
| 740 | - if( zDomain==0 ) zDomain = "unspecified.domain"; | |
| 949 | + if( zDomain==0 ) zDomain = ""; | |
| 950 | + x.srvrFlags = SMTPSRV_LOG; | |
| 741 | 951 | if( find_option("trace",0,0)!=0 ) x.srvrFlags |= SMTPSRV_STDERR; |
| 742 | 952 | verify_all_options(); |
| 743 | 953 | if( g.argc!=3 ) usage("DBNAME"); |
| 744 | 954 | zDbName = g.argv[2]; |
| 745 | 955 | zDbName = enter_chroot_jail(zDbName, 0); |
| 956 | + db_open_repository(zDbName); | |
| 746 | 957 | smtp_server_send(&x, "220 %s ESMTP https://fossil-scm.org/ %s\r\n", |
| 747 | 958 | zDomain, MANIFEST_VERSION); |
| 748 | 959 | while( smtp_server_gets(&x, z, sizeof(z)) ){ |
| 749 | 960 | if( strncmp(z, "EHLO ", 5)==0 ){ |
| 750 | 961 | smtp_server_send(&x, "250 ok\r\n"); |
| @@ -751,27 +962,35 @@ | ||
| 751 | 962 | }else |
| 752 | 963 | if( strncmp(z, "HELO ", 5)==0 ){ |
| 753 | 964 | smtp_server_send(&x, "250 ok\r\n"); |
| 754 | 965 | }else |
| 755 | 966 | if( strncmp(z, "MAIL FROM:<", 11)==0 ){ |
| 967 | + smtp_server_route_incoming(&x, 0); | |
| 968 | + smtp_server_clear(&x, SMTPSRV_CLEAR_MSG); | |
| 969 | + x.zFrom = extractEmail(z+11); | |
| 756 | 970 | smtp_server_send(&x, "250 ok\r\n"); |
| 757 | 971 | }else |
| 758 | 972 | if( strncmp(z, "RCPT TO:<", 9)==0 ){ |
| 973 | + char *zAddr = extractEmail(z+9); | |
| 974 | + smtp_append_to(&x, zAddr, 0); | |
| 975 | + if( x.nTo>=100 ){ | |
| 976 | + smtp_server_send(&x, "452 too many recipients\r\n"); | |
| 977 | + continue; | |
| 978 | + } | |
| 759 | 979 | smtp_server_send(&x, "250 ok\r\n"); |
| 760 | 980 | }else |
| 761 | 981 | if( strncmp(z, "DATA", 4)==0 ){ |
| 762 | 982 | smtp_server_send(&x, "354 ready\r\n"); |
| 763 | 983 | smtp_server_capture_data(&x, z, sizeof(z)); |
| 764 | 984 | smtp_server_send(&x, "250 ok\r\n"); |
| 765 | - smtp_server_clear(&x, SMTPSRV_CLEAR_MSG); | |
| 766 | 985 | }else |
| 767 | 986 | if( strncmp(z, "QUIT", 4)==0 ){ |
| 768 | 987 | smtp_server_send(&x, "221 closing connection\r\n"); |
| 769 | 988 | break; |
| 770 | 989 | }else |
| 771 | 990 | { |
| 772 | 991 | smtp_server_send(&x, "500 unknown command\r\n"); |
| 773 | 992 | } |
| 774 | 993 | } |
| 775 | - fclose(stdin); | |
| 994 | + smtp_server_route_incoming(&x, 1); | |
| 776 | 995 | smtp_server_clear(&x, SMTPSRV_CLEAR_ALL); |
| 777 | 996 | } |
| 778 | 997 |
| --- src/smtp.c | |
| +++ src/smtp.c | |
| @@ -135,11 +135,11 @@ | |
| 135 | /* |
| 136 | ** Shutdown an SmtpSession |
| 137 | */ |
| 138 | void smtp_session_free(SmtpSession *pSession){ |
| 139 | socket_close(); |
| 140 | blob_zero(&pSession->inbuf); |
| 141 | fossil_free(pSession->zHostname); |
| 142 | fossil_free(pSession->zErr); |
| 143 | fossil_free(pSession); |
| 144 | } |
| 145 | |
| @@ -228,11 +228,11 @@ | |
| 228 | } |
| 229 | if( p->smtpFlags & SMTP_TRACE_BLOB ){ |
| 230 | blob_appendf(p->pTranscript, "C: %.*s\n", n-2, z); |
| 231 | } |
| 232 | socket_send(0, z, n); |
| 233 | blob_zero(&b); |
| 234 | } |
| 235 | |
| 236 | /* |
| 237 | ** Read a line of input received from the SMTP server. Make in point |
| 238 | ** to the next input line. |
| @@ -439,12 +439,12 @@ | |
| 439 | blob_append(&out, "\r\n", 2); |
| 440 | } |
| 441 | } |
| 442 | blob_append(&out, ".\r\n", 3); |
| 443 | xSend(pArg, blob_buffer(&out), blob_size(&out)); |
| 444 | blob_zero(&out); |
| 445 | blob_zero(&line); |
| 446 | } |
| 447 | |
| 448 | /* A sender function appropriate for use by smtp_send_email_body() to |
| 449 | ** send all content to the console, for testing. |
| 450 | */ |
| @@ -464,11 +464,11 @@ | |
| 464 | void test_smtp_senddata(void){ |
| 465 | Blob f; |
| 466 | if( g.argc!=3 ) usage("FILE"); |
| 467 | blob_read_from_file(&f, g.argv[2], ExtFILE); |
| 468 | smtp_send_email_body(blob_str(&f), smtp_test_sender, 0); |
| 469 | blob_zero(&f); |
| 470 | } |
| 471 | |
| 472 | /* |
| 473 | ** Send a single email message to the SMTP server. |
| 474 | ** |
| @@ -588,30 +588,104 @@ | |
| 588 | smtp_client_quit(p); |
| 589 | if( p->zErr ){ |
| 590 | fossil_fatal("ERROR: %s\n", p->zErr); |
| 591 | } |
| 592 | smtp_session_free(p); |
| 593 | blob_zero(&body); |
| 594 | } |
| 595 | |
| 596 | /***************************************************************************** |
| 597 | ** Server implementation |
| 598 | *****************************************************************************/ |
| 599 | |
| 600 | #if LOCAL_INTERFACE |
| 601 | /* |
| 602 | ** State information for the server |
| 603 | */ |
| 604 | struct SmtpServer { |
| 605 | sqlite3 *db; /* Database into which the email is delivered */ |
| 606 | char *zEhlo; /* Client domain on the EHLO line */ |
| 607 | char *zFrom; /* MAIL FROM: argument */ |
| 608 | int nTo; /* Number of RCPT TO: lines seen */ |
| 609 | char **azTo; /* Address in each RCPT TO line */ |
| 610 | u32 srvrFlags; /* Control flags */ |
| 611 | Blob msg; /* Content following DATA */ |
| 612 | Blob transcript; /* Session transcript */ |
| 613 | }; |
| 614 | |
| 615 | #define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */ |
| 616 | #define SMTPSRV_CLEAR_ALL 2 /* smtp_server_clear() everything */ |
| 617 | #define SMTPSRV_LOG 0x001 /* Record a transcript of the interaction */ |
| @@ -626,20 +700,20 @@ | |
| 626 | static void smtp_server_clear(SmtpServer *p, int eHowMuch){ |
| 627 | int i; |
| 628 | if( eHowMuch>=SMTPSRV_CLEAR_MSG ){ |
| 629 | fossil_free(p->zFrom); |
| 630 | p->zFrom = 0; |
| 631 | for(i=0; i<p->nTo; i++) fossil_free(p->azTo[0]); |
| 632 | fossil_free(p->azTo); |
| 633 | p->azTo = 0; |
| 634 | p->nTo = 0; |
| 635 | blob_zero(&p->msg); |
| 636 | } |
| 637 | if( eHowMuch>=SMTPSRV_CLEAR_ALL ){ |
| 638 | blob_zero(&p->transcript); |
| 639 | sqlite3_close(p->db); |
| 640 | p->db = 0; |
| 641 | fossil_free(p->zEhlo); |
| 642 | p->zEhlo = 0; |
| 643 | } |
| 644 | } |
| 645 | |
| @@ -649,10 +723,33 @@ | |
| 649 | static void smtp_server_init(SmtpServer *p){ |
| 650 | memset(p, 0, sizeof(*p)); |
| 651 | blob_init(&p->msg, 0, 0); |
| 652 | blob_init(&p->transcript, 0, 0); |
| 653 | } |
| 654 | |
| 655 | /* |
| 656 | ** Send a single line of output from the server to the client. |
| 657 | */ |
| 658 | static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){ |
| @@ -674,11 +771,11 @@ | |
| 674 | if( p->srvrFlags & SMTPSRV_STDERR ){ |
| 675 | fprintf(stderr, "S: %.*s\n", n-2, z); |
| 676 | } |
| 677 | fwrite(z, n, 1, stdout); |
| 678 | fflush(stdout); |
| 679 | blob_zero(&b); |
| 680 | } |
| 681 | |
| 682 | /* |
| 683 | ** Read a single line from the client. |
| 684 | */ |
| @@ -718,33 +815,147 @@ | |
| 718 | nLine, blob_size(&p->msg)); |
| 719 | } |
| 720 | } |
| 721 | |
| 722 | /* |
| 723 | ** COMMAND: smtp |
| 724 | ** |
| 725 | ** Usage: %fossil smtp [options] DBNAME |
| 726 | ** |
| 727 | ** Begin a SMTP conversation with a client using stdin/stdout. (This |
| 728 | ** command is expected to be launched from xinetd or the equivalent.) |
| 729 | ** Use information in the SQLite database at DBNAME to find configuration |
| 730 | ** information and as a place to store the incoming content. |
| 731 | */ |
| 732 | void smtp_server(void){ |
| 733 | char *zDbName; |
| 734 | const char *zDomain; |
| 735 | SmtpServer x; |
| 736 | char z[5000]; |
| 737 | |
| 738 | smtp_server_init(&x); |
| 739 | zDomain = find_option("domain",0,1); |
| 740 | if( zDomain==0 ) zDomain = "unspecified.domain"; |
| 741 | if( find_option("trace",0,0)!=0 ) x.srvrFlags |= SMTPSRV_STDERR; |
| 742 | verify_all_options(); |
| 743 | if( g.argc!=3 ) usage("DBNAME"); |
| 744 | zDbName = g.argv[2]; |
| 745 | zDbName = enter_chroot_jail(zDbName, 0); |
| 746 | smtp_server_send(&x, "220 %s ESMTP https://fossil-scm.org/ %s\r\n", |
| 747 | zDomain, MANIFEST_VERSION); |
| 748 | while( smtp_server_gets(&x, z, sizeof(z)) ){ |
| 749 | if( strncmp(z, "EHLO ", 5)==0 ){ |
| 750 | smtp_server_send(&x, "250 ok\r\n"); |
| @@ -751,27 +962,35 @@ | |
| 751 | }else |
| 752 | if( strncmp(z, "HELO ", 5)==0 ){ |
| 753 | smtp_server_send(&x, "250 ok\r\n"); |
| 754 | }else |
| 755 | if( strncmp(z, "MAIL FROM:<", 11)==0 ){ |
| 756 | smtp_server_send(&x, "250 ok\r\n"); |
| 757 | }else |
| 758 | if( strncmp(z, "RCPT TO:<", 9)==0 ){ |
| 759 | smtp_server_send(&x, "250 ok\r\n"); |
| 760 | }else |
| 761 | if( strncmp(z, "DATA", 4)==0 ){ |
| 762 | smtp_server_send(&x, "354 ready\r\n"); |
| 763 | smtp_server_capture_data(&x, z, sizeof(z)); |
| 764 | smtp_server_send(&x, "250 ok\r\n"); |
| 765 | smtp_server_clear(&x, SMTPSRV_CLEAR_MSG); |
| 766 | }else |
| 767 | if( strncmp(z, "QUIT", 4)==0 ){ |
| 768 | smtp_server_send(&x, "221 closing connection\r\n"); |
| 769 | break; |
| 770 | }else |
| 771 | { |
| 772 | smtp_server_send(&x, "500 unknown command\r\n"); |
| 773 | } |
| 774 | } |
| 775 | fclose(stdin); |
| 776 | smtp_server_clear(&x, SMTPSRV_CLEAR_ALL); |
| 777 | } |
| 778 |
| --- src/smtp.c | |
| +++ src/smtp.c | |
| @@ -135,11 +135,11 @@ | |
| 135 | /* |
| 136 | ** Shutdown an SmtpSession |
| 137 | */ |
| 138 | void smtp_session_free(SmtpSession *pSession){ |
| 139 | socket_close(); |
| 140 | blob_reset(&pSession->inbuf); |
| 141 | fossil_free(pSession->zHostname); |
| 142 | fossil_free(pSession->zErr); |
| 143 | fossil_free(pSession); |
| 144 | } |
| 145 | |
| @@ -228,11 +228,11 @@ | |
| 228 | } |
| 229 | if( p->smtpFlags & SMTP_TRACE_BLOB ){ |
| 230 | blob_appendf(p->pTranscript, "C: %.*s\n", n-2, z); |
| 231 | } |
| 232 | socket_send(0, z, n); |
| 233 | blob_reset(&b); |
| 234 | } |
| 235 | |
| 236 | /* |
| 237 | ** Read a line of input received from the SMTP server. Make in point |
| 238 | ** to the next input line. |
| @@ -439,12 +439,12 @@ | |
| 439 | blob_append(&out, "\r\n", 2); |
| 440 | } |
| 441 | } |
| 442 | blob_append(&out, ".\r\n", 3); |
| 443 | xSend(pArg, blob_buffer(&out), blob_size(&out)); |
| 444 | blob_reset(&out); |
| 445 | blob_reset(&line); |
| 446 | } |
| 447 | |
| 448 | /* A sender function appropriate for use by smtp_send_email_body() to |
| 449 | ** send all content to the console, for testing. |
| 450 | */ |
| @@ -464,11 +464,11 @@ | |
| 464 | void test_smtp_senddata(void){ |
| 465 | Blob f; |
| 466 | if( g.argc!=3 ) usage("FILE"); |
| 467 | blob_read_from_file(&f, g.argv[2], ExtFILE); |
| 468 | smtp_send_email_body(blob_str(&f), smtp_test_sender, 0); |
| 469 | blob_reset(&f); |
| 470 | } |
| 471 | |
| 472 | /* |
| 473 | ** Send a single email message to the SMTP server. |
| 474 | ** |
| @@ -588,30 +588,104 @@ | |
| 588 | smtp_client_quit(p); |
| 589 | if( p->zErr ){ |
| 590 | fossil_fatal("ERROR: %s\n", p->zErr); |
| 591 | } |
| 592 | smtp_session_free(p); |
| 593 | blob_reset(&body); |
| 594 | } |
| 595 | |
| 596 | /***************************************************************************** |
| 597 | ** Server implementation |
| 598 | *****************************************************************************/ |
| 599 | |
| 600 | /* |
| 601 | ** Schema used by the email processing system. |
| 602 | */ |
| 603 | static const char zEmailSchema[] = |
| 604 | @ -- bulk storage is in a separate table. This table can store either |
| 605 | @ -- the body of email messages or transcripts of smtp sessions. |
| 606 | @ CREATE TABLE IF NOT EXISTS repository.emailblob( |
| 607 | @ emailid INTEGER PRIMARY KEY, -- numeric idea for the entry |
| 608 | @ ets INT, -- Corresponding transcript, or NULL |
| 609 | @ etime INT, -- insertion time, secs since 1970 |
| 610 | @ etxt TEXT -- content of this entry |
| 611 | @ ); |
| 612 | @ |
| 613 | @ -- One row for each mailbox entry. All users emails are stored in |
| 614 | @ -- this same table. |
| 615 | @ CREATE TABLE IF NOT EXISTS repository.emailbox( |
| 616 | @ euser TEXT, -- User who received this email |
| 617 | @ edate INT, -- Date received. Seconds since 1970 |
| 618 | @ efrom TEXT, -- Who is the email from |
| 619 | @ emsgid INT, -- Raw email text |
| 620 | @ ets INT, -- Transcript of the receiving SMTP session |
| 621 | @ estate INT, -- Unread, read, starred, etc. |
| 622 | @ esubject TEXT -- Subject line for display |
| 623 | @ ); |
| 624 | @ |
| 625 | @ -- Information on how to deliver incoming email. |
| 626 | @ CREATE TABLE IF NOT EXISTS repository.emailroute( |
| 627 | @ eaddr TEXT PRIMARY KEY, -- Email address |
| 628 | @ epolicy TEXT -- How to handle email sent to this address |
| 629 | @ ) WITHOUT ROWID; |
| 630 | @ |
| 631 | @ -- Outgoing email queue |
| 632 | @ CREATE TABLE IF NOT EXISTS repository.emailoutq( |
| 633 | @ edomain TEXT, -- Destination domain. (ex: "fossil-scm.org") |
| 634 | @ efrom TEXT, -- Sender email address |
| 635 | @ eto TEXT, -- Receipient email address |
| 636 | @ emsgid INT, -- Message body in the emailblob table |
| 637 | @ ectime INT, -- Time enqueued. Seconds since 1970 |
| 638 | @ emtime INT, -- Time of last send attempt. Sec since 1970 |
| 639 | @ ensend INT, -- Number of send attempts |
| 640 | @ ets INT -- Transcript of last failed attempt |
| 641 | @ ); |
| 642 | ; |
| 643 | |
| 644 | /* |
| 645 | ** Code used to delete the email tables. |
| 646 | */ |
| 647 | static const char zEmailDrop[] = |
| 648 | @ DROP TABLE IF EXISTS emailblob; |
| 649 | @ DROP TABLE IF EXISTS emailbox; |
| 650 | @ DROP TABLE IF EXISTS emailroute; |
| 651 | @ DROP TABLE IF EXISTS emailqueue; |
| 652 | ; |
| 653 | |
| 654 | /* |
| 655 | ** Populate the schema of a database. |
| 656 | ** |
| 657 | ** eForce==0 Fast |
| 658 | ** eForce==1 Run CREATE TABLE statements every time |
| 659 | ** eForce==2 DROP then rerun CREATE TABLE |
| 660 | */ |
| 661 | void smtp_server_schema(int eForce){ |
| 662 | if( eForce==2 ){ |
| 663 | db_multi_exec(zEmailDrop/*works-like:""*/); |
| 664 | } |
| 665 | if( eForce==1 || !db_table_exists("repository","emailblob") ){ |
| 666 | db_multi_exec(zEmailSchema/*works-like:""*/); |
| 667 | } |
| 668 | } |
| 669 | |
| 670 | #if LOCAL_INTERFACE |
| 671 | /* |
| 672 | ** State information for the server |
| 673 | */ |
| 674 | struct SmtpServer { |
| 675 | sqlite3_int64 idTranscript; /* Transcript ID number */ |
| 676 | sqlite3_int64 idMsg; /* Message ID number */ |
| 677 | char *zEhlo; /* Client domain on the EHLO line */ |
| 678 | char *zFrom; /* MAIL FROM: argument */ |
| 679 | int nTo; /* Number of RCPT TO: lines seen */ |
| 680 | struct SmtpTo { |
| 681 | char *z; /* Address in each RCPT TO line */ |
| 682 | int okRemote; /* zTo can be in another domain */ |
| 683 | } *aTo; |
| 684 | u32 srvrFlags; /* Control flags */ |
| 685 | Blob msg; /* Content following DATA */ |
| 686 | Blob transcript; /* Session transcript */ |
| 687 | }; |
| 688 | |
| 689 | #define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */ |
| 690 | #define SMTPSRV_CLEAR_ALL 2 /* smtp_server_clear() everything */ |
| 691 | #define SMTPSRV_LOG 0x001 /* Record a transcript of the interaction */ |
| @@ -626,20 +700,20 @@ | |
| 700 | static void smtp_server_clear(SmtpServer *p, int eHowMuch){ |
| 701 | int i; |
| 702 | if( eHowMuch>=SMTPSRV_CLEAR_MSG ){ |
| 703 | fossil_free(p->zFrom); |
| 704 | p->zFrom = 0; |
| 705 | for(i=0; i<p->nTo; i++) fossil_free(p->aTo[i].z); |
| 706 | fossil_free(p->aTo); |
| 707 | p->aTo = 0; |
| 708 | p->nTo = 0; |
| 709 | blob_reset(&p->msg); |
| 710 | p->idMsg = 0; |
| 711 | } |
| 712 | if( eHowMuch>=SMTPSRV_CLEAR_ALL ){ |
| 713 | blob_reset(&p->transcript); |
| 714 | p->idTranscript = 0; |
| 715 | fossil_free(p->zEhlo); |
| 716 | p->zEhlo = 0; |
| 717 | } |
| 718 | } |
| 719 | |
| @@ -649,10 +723,33 @@ | |
| 723 | static void smtp_server_init(SmtpServer *p){ |
| 724 | memset(p, 0, sizeof(*p)); |
| 725 | blob_init(&p->msg, 0, 0); |
| 726 | blob_init(&p->transcript, 0, 0); |
| 727 | } |
| 728 | |
| 729 | /* |
| 730 | ** Append a new TO entry to the SmtpServer object. Do not do the |
| 731 | ** append if the same entry is already on the list. |
| 732 | ** |
| 733 | ** The zAddr argument is obtained from fossil_malloc(). This |
| 734 | ** routine assumes ownership of the allocation. |
| 735 | */ |
| 736 | static void smtp_append_to(SmtpServer *p, char *zAddr, int okRemote){ |
| 737 | int i; |
| 738 | for(i=0; zAddr[i]; i++){ zAddr[i] = fossil_tolower(zAddr[i]); } |
| 739 | for(i=0; i<p->nTo; i++){ |
| 740 | if( strcmp(zAddr, p->aTo[i].z)==0 ){ |
| 741 | fossil_free(zAddr); |
| 742 | if( p->aTo[i].okRemote==0 ) p->aTo[i].okRemote = okRemote; |
| 743 | return; |
| 744 | } |
| 745 | } |
| 746 | p->aTo = fossil_realloc(p->aTo, (p->nTo+1)*sizeof(p->aTo[0])); |
| 747 | p->aTo[p->nTo].z = zAddr; |
| 748 | p->aTo[p->nTo].okRemote = okRemote; |
| 749 | p->nTo++; |
| 750 | } |
| 751 | |
| 752 | /* |
| 753 | ** Send a single line of output from the server to the client. |
| 754 | */ |
| 755 | static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){ |
| @@ -674,11 +771,11 @@ | |
| 771 | if( p->srvrFlags & SMTPSRV_STDERR ){ |
| 772 | fprintf(stderr, "S: %.*s\n", n-2, z); |
| 773 | } |
| 774 | fwrite(z, n, 1, stdout); |
| 775 | fflush(stdout); |
| 776 | blob_reset(&b); |
| 777 | } |
| 778 | |
| 779 | /* |
| 780 | ** Read a single line from the client. |
| 781 | */ |
| @@ -718,33 +815,147 @@ | |
| 815 | nLine, blob_size(&p->msg)); |
| 816 | } |
| 817 | } |
| 818 | |
| 819 | /* |
| 820 | ** Send an email to a single email addess that is registered with |
| 821 | ** this system, according to the instructions in emailroute. If |
| 822 | ** zAddr is not in the emailroute table, then this routine is a |
| 823 | ** no-op. Or if zAddr has already been processed, then this |
| 824 | ** routine is a no-op. |
| 825 | */ |
| 826 | static void smtp_server_send_one_user( |
| 827 | SmtpServer *p, /* The current inbound email */ |
| 828 | const char *zAddr, /* Who to forward this to */ |
| 829 | int okRemote /* True if ok to foward to another domain */ |
| 830 | ){ |
| 831 | char *zPolicy; |
| 832 | Blob policy, line, token, tail; |
| 833 | |
| 834 | zPolicy = db_text(0, |
| 835 | "SELECT epolicy FROM emailroute WHERE eaddr=%Q", zAddr); |
| 836 | if( zPolicy==0 ){ |
| 837 | if( okRemote ){ |
| 838 | int i; |
| 839 | for(i=0; zAddr[i] && zAddr[i]!='@'; i++){} |
| 840 | if( zAddr[i]=='@' && zAddr[i+1]!=0 ){ |
| 841 | db_multi_exec( |
| 842 | "INSERT INTO emailoutq(edomain,efrom,eto,emsgid,ectime," |
| 843 | "emtime,ensend)" |
| 844 | "VALUES(%Q,%Q,%Q,%lld,now(),0,0)", |
| 845 | zAddr+i+1, p->zFrom, zAddr, p->idMsg |
| 846 | ); |
| 847 | } |
| 848 | } |
| 849 | return; |
| 850 | } |
| 851 | blob_init(&policy, zPolicy, -1); |
| 852 | while( blob_line(&policy, &line) ){ |
| 853 | blob_trim(&line); |
| 854 | blob_token(&line, &token); |
| 855 | blob_tail(&line, &tail); |
| 856 | if( blob_size(&tail)==0 ) continue; |
| 857 | if( blob_eq_str(&token, "mbox", 4) ){ |
| 858 | db_multi_exec( |
| 859 | "INSERT INTO emailbox(euser,edate,efrom,emsgid,ets,estate)" |
| 860 | " VALUES(%Q,now(),%Q,%lld,%lld,0)", |
| 861 | blob_str(&tail), p->zFrom, p->idMsg, p->idTranscript |
| 862 | ); |
| 863 | } |
| 864 | if( blob_eq_str(&token, "forward", 7) ){ |
| 865 | smtp_append_to(p, fossil_strdup(blob_str(&tail)), 1); |
| 866 | } |
| 867 | blob_reset(&tail); |
| 868 | } |
| 869 | } |
| 870 | |
| 871 | /* |
| 872 | ** The SmtpServer object contains a complete incoming email. |
| 873 | ** Add this email to the database. |
| 874 | */ |
| 875 | static void smtp_server_route_incoming(SmtpServer *p, int bFinish){ |
| 876 | Stmt s; |
| 877 | int i, j; |
| 878 | if( p->zFrom && p->nTo && blob_size(&p->msg) ){ |
| 879 | db_begin_transaction(); |
| 880 | if( p->idTranscript==0 ) smtp_server_schema(0); |
| 881 | db_prepare(&s, |
| 882 | "INSERT INTO emailblob(ets,etime,etxt)" |
| 883 | " VALUES(:ets,now(),:etxt)" |
| 884 | ); |
| 885 | if( !bFinish && p->idTranscript==0 ){ |
| 886 | db_bind_null(&s, ":ets"); |
| 887 | db_bind_null(&s, ":etxt"); |
| 888 | db_step(&s); |
| 889 | db_reset(&s); |
| 890 | p->idTranscript = db_last_insert_rowid(); |
| 891 | }else if( bFinish ){ |
| 892 | if( p->idTranscript ){ |
| 893 | db_multi_exec("UPDATE emailblob SET etxt=%Q WHERE emailid=%lld", |
| 894 | blob_str(&p->transcript), p->idTranscript); |
| 895 | }else{ |
| 896 | db_bind_null(&s, ":ets"); |
| 897 | db_bind_str(&s, ":etxt", &p->transcript); |
| 898 | db_step(&s); |
| 899 | db_reset(&s); |
| 900 | p->idTranscript = db_last_insert_rowid(); |
| 901 | } |
| 902 | } |
| 903 | db_bind_int64(&s, ":ets", p->idTranscript); |
| 904 | db_bind_str(&s, ":etxt", &p->msg); |
| 905 | db_step(&s); |
| 906 | db_finalize(&s); |
| 907 | p->idMsg = db_last_insert_rowid(); |
| 908 | |
| 909 | /* make entries in emailbox and emailoutq */ |
| 910 | for(i=0; i<p->nTo; i++){ |
| 911 | int okRemote = p->aTo[i].okRemote; |
| 912 | p->aTo[i].okRemote = 1; |
| 913 | smtp_server_send_one_user(p, p->aTo[i].z, okRemote); |
| 914 | } |
| 915 | |
| 916 | /* Finish the transaction after all changes are implemented */ |
| 917 | db_end_transaction(0); |
| 918 | } |
| 919 | smtp_server_clear(p, SMTPSRV_CLEAR_MSG); |
| 920 | } |
| 921 | |
| 922 | /* |
| 923 | ** Make a copy of the input string up to but not including the |
| 924 | ** first ">" character. |
| 925 | */ |
| 926 | static char *extractEmail(const char *z){ |
| 927 | int i; |
| 928 | for(i=0; z[i] && z[i]!='>'; i++){} |
| 929 | return mprintf("%.*s", i, z); |
| 930 | } |
| 931 | |
| 932 | /* |
| 933 | ** COMMAND: smtpd |
| 934 | ** |
| 935 | ** Usage: %fossil smtpd [OPTIONS] REPOSITORY |
| 936 | ** |
| 937 | ** Begin a SMTP conversation with a client using stdin/stdout. The |
| 938 | ** received email is stored in REPOSITORY |
| 939 | ** |
| 940 | */ |
| 941 | void smtp_server(void){ |
| 942 | char *zDbName; |
| 943 | const char *zDomain; |
| 944 | SmtpServer x; |
| 945 | char z[5000]; |
| 946 | |
| 947 | smtp_server_init(&x); |
| 948 | zDomain = find_option("domain",0,1); |
| 949 | if( zDomain==0 ) zDomain = ""; |
| 950 | x.srvrFlags = SMTPSRV_LOG; |
| 951 | if( find_option("trace",0,0)!=0 ) x.srvrFlags |= SMTPSRV_STDERR; |
| 952 | verify_all_options(); |
| 953 | if( g.argc!=3 ) usage("DBNAME"); |
| 954 | zDbName = g.argv[2]; |
| 955 | zDbName = enter_chroot_jail(zDbName, 0); |
| 956 | db_open_repository(zDbName); |
| 957 | smtp_server_send(&x, "220 %s ESMTP https://fossil-scm.org/ %s\r\n", |
| 958 | zDomain, MANIFEST_VERSION); |
| 959 | while( smtp_server_gets(&x, z, sizeof(z)) ){ |
| 960 | if( strncmp(z, "EHLO ", 5)==0 ){ |
| 961 | smtp_server_send(&x, "250 ok\r\n"); |
| @@ -751,27 +962,35 @@ | |
| 962 | }else |
| 963 | if( strncmp(z, "HELO ", 5)==0 ){ |
| 964 | smtp_server_send(&x, "250 ok\r\n"); |
| 965 | }else |
| 966 | if( strncmp(z, "MAIL FROM:<", 11)==0 ){ |
| 967 | smtp_server_route_incoming(&x, 0); |
| 968 | smtp_server_clear(&x, SMTPSRV_CLEAR_MSG); |
| 969 | x.zFrom = extractEmail(z+11); |
| 970 | smtp_server_send(&x, "250 ok\r\n"); |
| 971 | }else |
| 972 | if( strncmp(z, "RCPT TO:<", 9)==0 ){ |
| 973 | char *zAddr = extractEmail(z+9); |
| 974 | smtp_append_to(&x, zAddr, 0); |
| 975 | if( x.nTo>=100 ){ |
| 976 | smtp_server_send(&x, "452 too many recipients\r\n"); |
| 977 | continue; |
| 978 | } |
| 979 | smtp_server_send(&x, "250 ok\r\n"); |
| 980 | }else |
| 981 | if( strncmp(z, "DATA", 4)==0 ){ |
| 982 | smtp_server_send(&x, "354 ready\r\n"); |
| 983 | smtp_server_capture_data(&x, z, sizeof(z)); |
| 984 | smtp_server_send(&x, "250 ok\r\n"); |
| 985 | }else |
| 986 | if( strncmp(z, "QUIT", 4)==0 ){ |
| 987 | smtp_server_send(&x, "221 closing connection\r\n"); |
| 988 | break; |
| 989 | }else |
| 990 | { |
| 991 | smtp_server_send(&x, "500 unknown command\r\n"); |
| 992 | } |
| 993 | } |
| 994 | smtp_server_route_incoming(&x, 1); |
| 995 | smtp_server_clear(&x, SMTPSRV_CLEAR_ALL); |
| 996 | } |
| 997 |