Fossil SCM

The "fossil smtpd" command stores incoming messages in the database and routes them according to the emailroute table.

drh 2018-06-29 19:54 UTC smtp
Commit e4144ced8dc73a310f00d4a4f392023dc30598c3b6cc280c53486b57fcc3fb6f
2 files changed +12 -12 +252 -33
+12 -12
--- src/email.c
+++ src/email.c
@@ -13,11 +13,11 @@
1313
** [email protected]
1414
** http://www.hwaci.com/drh/
1515
**
1616
*******************************************************************************
1717
**
18
-** Email notification features
18
+** Logic for email notification, also known as "alerts".
1919
*/
2020
#include "config.h"
2121
#include "email.h"
2222
#include <assert.h>
2323
@@ -393,11 +393,11 @@
393393
p->db = 0;
394394
p->zDb = 0;
395395
p->zDir = 0;
396396
p->zCmd = 0;
397397
p->zDest = "off";
398
- blob_zero(&p->out);
398
+ blob_reset(&p->out);
399399
}
400400
401401
/*
402402
** Put the EmailSender into an error state.
403403
*/
@@ -571,11 +571,11 @@
571571
blob_write_to_file(&all, zFile);
572572
fossil_free(zFile);
573573
}else if( strcmp(p->zDest, "stdout")==0 ){
574574
fossil_print("%s\n", blob_str(&all));
575575
}
576
- blob_zero(&all);
576
+ blob_reset(&all);
577577
}
578578
579579
/*
580580
** Analyze and act on a received email.
581581
**
@@ -585,11 +585,11 @@
585585
** This routine acts on all email messages received from the
586586
** "fossil email inbound" command.
587587
*/
588588
void email_receive(Blob *pMsg){
589589
/* To Do: Look for bounce messages and possibly disable subscriptions */
590
- blob_zero(pMsg);
590
+ blob_reset(pMsg);
591591
}
592592
593593
/*
594594
** SETTING: email-send-method width=5 default=off
595595
** Determine the method used to send email. Allowed values are
@@ -715,11 +715,11 @@
715715
"This will erase all content in the repository tables, thus\n"
716716
"deleting all subscriber information. The information will be\n"
717717
"unrecoverable.\n");
718718
prompt_user("Continue? (y/N) ", &yn);
719719
c = blob_str(&yn)[0];
720
- blob_zero(&yn);
720
+ blob_reset(&yn);
721721
}
722722
if( c=='y' ){
723723
email_triggers_disable();
724724
db_multi_exec(
725725
"DROP TABLE IF EXISTS subscriber;\n"
@@ -756,13 +756,13 @@
756756
}
757757
blob_add_final_newline(&body);
758758
pSender = email_sender_new(zDest, 1);
759759
email_send(pSender, &hdr, &body);
760760
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);
764764
}else
765765
if( strncmp(zCmd, "settings", nCmd)==0 ){
766766
int isGlobal = find_option("global",0,0)!=0;
767767
int nSetting;
768768
const Setting *pSetting = setting_info(&nSetting);
@@ -1567,11 +1567,11 @@
15671567
** Free a linked list of EmailEvent objects
15681568
*/
15691569
void email_free_eventlist(EmailEvent *p){
15701570
while( p ){
15711571
EmailEvent *pNext = p->pNext;
1572
- blob_zero(&p->txt);
1572
+ blob_reset(&p->txt);
15731573
fossil_free(p);
15741574
p = pNext;
15751575
}
15761576
}
15771577
@@ -1700,11 +1700,11 @@
17001700
blob_append(&out, blob_buffer(&p->txt), blob_size(&p->txt));
17011701
}
17021702
email_free_eventlist(pEvent);
17031703
email_footer(&out);
17041704
fossil_print("%s", blob_str(&out));
1705
- blob_zero(&out);
1705
+ blob_reset(&out);
17061706
db_end_transaction(0);
17071707
}
17081708
17091709
/*
17101710
** COMMAND: test-add-alerts
@@ -1822,12 +1822,12 @@
18221822
zUrl, zCode);
18231823
email_send(pSender,&hdr,&body);
18241824
blob_truncate(&hdr, 0);
18251825
blob_truncate(&body, 0);
18261826
}
1827
- blob_zero(&hdr);
1828
- blob_zero(&body);
1827
+ blob_reset(&hdr);
1828
+ blob_reset(&body);
18291829
db_finalize(&q);
18301830
email_free_eventlist(pEvents);
18311831
if( (flags & SENDALERT_PRESERVE)==0 ){
18321832
if( flags & SENDALERT_DIGEST ){
18331833
db_multi_exec("UPDATE pending_alert SET sentDigest=true");
18341834
--- 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 @@
135135
/*
136136
** Shutdown an SmtpSession
137137
*/
138138
void smtp_session_free(SmtpSession *pSession){
139139
socket_close();
140
- blob_zero(&pSession->inbuf);
140
+ blob_reset(&pSession->inbuf);
141141
fossil_free(pSession->zHostname);
142142
fossil_free(pSession->zErr);
143143
fossil_free(pSession);
144144
}
145145
@@ -228,11 +228,11 @@
228228
}
229229
if( p->smtpFlags & SMTP_TRACE_BLOB ){
230230
blob_appendf(p->pTranscript, "C: %.*s\n", n-2, z);
231231
}
232232
socket_send(0, z, n);
233
- blob_zero(&b);
233
+ blob_reset(&b);
234234
}
235235
236236
/*
237237
** Read a line of input received from the SMTP server. Make in point
238238
** to the next input line.
@@ -439,12 +439,12 @@
439439
blob_append(&out, "\r\n", 2);
440440
}
441441
}
442442
blob_append(&out, ".\r\n", 3);
443443
xSend(pArg, blob_buffer(&out), blob_size(&out));
444
- blob_zero(&out);
445
- blob_zero(&line);
444
+ blob_reset(&out);
445
+ blob_reset(&line);
446446
}
447447
448448
/* A sender function appropriate for use by smtp_send_email_body() to
449449
** send all content to the console, for testing.
450450
*/
@@ -464,11 +464,11 @@
464464
void test_smtp_senddata(void){
465465
Blob f;
466466
if( g.argc!=3 ) usage("FILE");
467467
blob_read_from_file(&f, g.argv[2], ExtFILE);
468468
smtp_send_email_body(blob_str(&f), smtp_test_sender, 0);
469
- blob_zero(&f);
469
+ blob_reset(&f);
470470
}
471471
472472
/*
473473
** Send a single email message to the SMTP server.
474474
**
@@ -588,30 +588,104 @@
588588
smtp_client_quit(p);
589589
if( p->zErr ){
590590
fossil_fatal("ERROR: %s\n", p->zErr);
591591
}
592592
smtp_session_free(p);
593
- blob_zero(&body);
593
+ blob_reset(&body);
594594
}
595595
596596
/*****************************************************************************
597597
** Server implementation
598598
*****************************************************************************/
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
+}
599669
600670
#if LOCAL_INTERFACE
601671
/*
602672
** State information for the server
603673
*/
604674
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 */
613687
};
614688
615689
#define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */
616690
#define SMTPSRV_CLEAR_ALL 2 /* smtp_server_clear() everything */
617691
#define SMTPSRV_LOG 0x001 /* Record a transcript of the interaction */
@@ -626,20 +700,20 @@
626700
static void smtp_server_clear(SmtpServer *p, int eHowMuch){
627701
int i;
628702
if( eHowMuch>=SMTPSRV_CLEAR_MSG ){
629703
fossil_free(p->zFrom);
630704
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;
634708
p->nTo = 0;
635
- blob_zero(&p->msg);
709
+ blob_reset(&p->msg);
710
+ p->idMsg = 0;
636711
}
637712
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;
641715
fossil_free(p->zEhlo);
642716
p->zEhlo = 0;
643717
}
644718
}
645719
@@ -649,10 +723,33 @@
649723
static void smtp_server_init(SmtpServer *p){
650724
memset(p, 0, sizeof(*p));
651725
blob_init(&p->msg, 0, 0);
652726
blob_init(&p->transcript, 0, 0);
653727
}
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
+}
654751
655752
/*
656753
** Send a single line of output from the server to the client.
657754
*/
658755
static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){
@@ -674,11 +771,11 @@
674771
if( p->srvrFlags & SMTPSRV_STDERR ){
675772
fprintf(stderr, "S: %.*s\n", n-2, z);
676773
}
677774
fwrite(z, n, 1, stdout);
678775
fflush(stdout);
679
- blob_zero(&b);
776
+ blob_reset(&b);
680777
}
681778
682779
/*
683780
** Read a single line from the client.
684781
*/
@@ -718,33 +815,147 @@
718815
nLine, blob_size(&p->msg));
719816
}
720817
}
721818
722819
/*
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
+**
731940
*/
732941
void smtp_server(void){
733942
char *zDbName;
734943
const char *zDomain;
735944
SmtpServer x;
736945
char z[5000];
737946
738947
smtp_server_init(&x);
739948
zDomain = find_option("domain",0,1);
740
- if( zDomain==0 ) zDomain = "unspecified.domain";
949
+ if( zDomain==0 ) zDomain = "";
950
+ x.srvrFlags = SMTPSRV_LOG;
741951
if( find_option("trace",0,0)!=0 ) x.srvrFlags |= SMTPSRV_STDERR;
742952
verify_all_options();
743953
if( g.argc!=3 ) usage("DBNAME");
744954
zDbName = g.argv[2];
745955
zDbName = enter_chroot_jail(zDbName, 0);
956
+ db_open_repository(zDbName);
746957
smtp_server_send(&x, "220 %s ESMTP https://fossil-scm.org/ %s\r\n",
747958
zDomain, MANIFEST_VERSION);
748959
while( smtp_server_gets(&x, z, sizeof(z)) ){
749960
if( strncmp(z, "EHLO ", 5)==0 ){
750961
smtp_server_send(&x, "250 ok\r\n");
@@ -751,27 +962,35 @@
751962
}else
752963
if( strncmp(z, "HELO ", 5)==0 ){
753964
smtp_server_send(&x, "250 ok\r\n");
754965
}else
755966
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);
756970
smtp_server_send(&x, "250 ok\r\n");
757971
}else
758972
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
+ }
759979
smtp_server_send(&x, "250 ok\r\n");
760980
}else
761981
if( strncmp(z, "DATA", 4)==0 ){
762982
smtp_server_send(&x, "354 ready\r\n");
763983
smtp_server_capture_data(&x, z, sizeof(z));
764984
smtp_server_send(&x, "250 ok\r\n");
765
- smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
766985
}else
767986
if( strncmp(z, "QUIT", 4)==0 ){
768987
smtp_server_send(&x, "221 closing connection\r\n");
769988
break;
770989
}else
771990
{
772991
smtp_server_send(&x, "500 unknown command\r\n");
773992
}
774993
}
775
- fclose(stdin);
994
+ smtp_server_route_incoming(&x, 1);
776995
smtp_server_clear(&x, SMTPSRV_CLEAR_ALL);
777996
}
778997
--- 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

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button