Fossil SCM

Remove smtpd server functionality first pass

danshearer 2021-03-16 19:59 trunk
Commit db5c2d399c143a04123d7663e30754de835061e579a7dc35267b40c6db99443e
--- src/backoffice.c
+++ src/backoffice.c
@@ -630,12 +630,10 @@
630630
}
631631
632632
/* Here is where the actual work of the backoffice happens */
633633
nThis = alert_backoffice(0);
634634
if( nThis ){ backoffice_log("%d alerts", nThis); nTotal += nThis; }
635
- nThis = smtp_cleanup();
636
- if( nThis ){ backoffice_log("%d SMTPs", nThis); nTotal += nThis; }
637635
nThis = hook_backoffice();
638636
if( nThis ){ backoffice_log("%d hooks", nThis); nTotal += nThis; }
639637
640638
/* Close the log */
641639
if( backofficeFILE ){
642640
--- src/backoffice.c
+++ src/backoffice.c
@@ -630,12 +630,10 @@
630 }
631
632 /* Here is where the actual work of the backoffice happens */
633 nThis = alert_backoffice(0);
634 if( nThis ){ backoffice_log("%d alerts", nThis); nTotal += nThis; }
635 nThis = smtp_cleanup();
636 if( nThis ){ backoffice_log("%d SMTPs", nThis); nTotal += nThis; }
637 nThis = hook_backoffice();
638 if( nThis ){ backoffice_log("%d hooks", nThis); nTotal += nThis; }
639
640 /* Close the log */
641 if( backofficeFILE ){
642
--- src/backoffice.c
+++ src/backoffice.c
@@ -630,12 +630,10 @@
630 }
631
632 /* Here is where the actual work of the backoffice happens */
633 nThis = alert_backoffice(0);
634 if( nThis ){ backoffice_log("%d alerts", nThis); nTotal += nThis; }
 
 
635 nThis = hook_backoffice();
636 if( nThis ){ backoffice_log("%d hooks", nThis); nTotal += nThis; }
637
638 /* Close the log */
639 if( backofficeFILE ){
640
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -164,11 +164,10 @@
164164
user
165165
utf8
166166
util
167167
verify
168168
vfile
169
- webmail
170169
wiki
171170
wikiformat
172171
winfile
173172
winhttp
174173
xfer
175174
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -164,11 +164,10 @@
164 user
165 utf8
166 util
167 verify
168 vfile
169 webmail
170 wiki
171 wikiformat
172 winfile
173 winhttp
174 xfer
175
--- src/makemake.tcl
+++ src/makemake.tcl
@@ -164,11 +164,10 @@
164 user
165 utf8
166 util
167 verify
168 vfile
 
169 wiki
170 wikiformat
171 winfile
172 winhttp
173 xfer
174
-1038
--- src/smtp.c
+++ src/smtp.c
@@ -646,1043 +646,5 @@
646646
fossil_fatal("ERROR: %s\n", p->zErr);
647647
}
648648
smtp_session_free(p);
649649
blob_reset(&body);
650650
}
651
-
652
-/*****************************************************************************
653
-** Server implementation
654
-*****************************************************************************/
655
-
656
-/*
657
-** Schema used by the email processing system.
658
-*/
659
-static const char zEmailSchema[] =
660
-@ -- bulk storage is in this table. This table can store either
661
-@ -- the body of email messages or transcripts of an smtp session.
662
-@ CREATE TABLE IF NOT EXISTS repository.emailblob(
663
-@ emailid INTEGER PRIMARY KEY AUTOINCREMENT, -- numeric idea for the entry
664
-@ enref INT, -- Number of references to this blob
665
-@ ets INT, -- Corresponding transcript, or NULL
666
-@ etime INT, -- insertion time, secs since 1970
667
-@ esz INT, -- uncompressed content size
668
-@ etxt TEXT -- content of this entry
669
-@ );
670
-@
671
-@ -- One row for each mailbox entry. All users emails are stored in
672
-@ -- this same table.
673
-@ CREATE TABLE IF NOT EXISTS repository.emailbox(
674
-@ ebid INTEGER PRIMARY KEY, -- Unique id for each mailbox entry
675
-@ euser TEXT, -- User who received this email
676
-@ edate INT, -- Date received. Seconds since 1970
677
-@ efrom TEXT, -- Who is the email from
678
-@ emsgid INT, -- Raw email text
679
-@ estate INT, -- 0: Unread, 1: read, 2: trash 3: sent
680
-@ esubject TEXT, -- Subject line for display
681
-@ etags TEXT -- zero or more tags
682
-@ );
683
-@
684
-@ -- Information on how to deliver incoming email.
685
-@ CREATE TABLE IF NOT EXISTS repository.emailroute(
686
-@ eaddr TEXT PRIMARY KEY, -- Email address
687
-@ epolicy TEXT -- How to handle email sent to this address
688
-@ ) WITHOUT ROWID;
689
-@
690
-@ -- Outgoing email queue
691
-@ CREATE TABLE IF NOT EXISTS repository.emailoutq(
692
-@ edomain TEXT, -- Destination domain. (ex: "fossil-scm.org")
693
-@ efrom TEXT, -- Sender email address (envelope "from")
694
-@ eto TEXT, -- Recipient email address (envelope "to")
695
-@ emsgid INT, -- Message body in the emailblob table
696
-@ ectime INT, -- Time enqueued. Seconds since 1970
697
-@ emtime INT, -- Time of last send attempt. Sec since 1970
698
-@ ensend INT, -- Number of send attempts
699
-@ ets INT -- Transcript of last failed attempt
700
-@ );
701
-@
702
-@ -- Triggers to automatically keep the emailblob.enref field up to date
703
-@ -- as entries in the emailblob, emailbox, and emailoutq tables are
704
-@ -- deleted.
705
-@ CREATE TRIGGER IF NOT EXISTS repository.emailblob_d1
706
-@ AFTER DELETE ON emailblob BEGIN
707
-@ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.ets;
708
-@ END;
709
-@ CREATE TRIGGER IF NOT EXISTS repository.emailbox_d1
710
-@ AFTER DELETE ON emailbox BEGIN
711
-@ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.emsgid;
712
-@ END;
713
-@ CREATE TRIGGER IF NOT EXISTS repository.emailoutq_d1
714
-@ AFTER DELETE ON emailoutq BEGIN
715
-@ UPDATE emailblob SET enref=enref-1 WHERE emailid IN (old.ets,old.emsgid);
716
-@ END;
717
-@
718
-@ -- An index on the emailblob entries which are unreferenced.
719
-@ CREATE INDEX IF NOT EXISTS repository.emailblob_nref ON emailblob(enref)
720
-@ WHERE enref<=0;
721
-;
722
-
723
-/*
724
-** Code used to delete the email tables.
725
-*/
726
-static const char zEmailDrop[] =
727
-@ DROP TABLE IF EXISTS emailblob;
728
-@ DROP TABLE IF EXISTS emailbox;
729
-@ DROP TABLE IF EXISTS emailroute;
730
-@ DROP TABLE IF EXISTS emailqueue;
731
-;
732
-
733
-#if INTERFACE
734
-/*
735
-** Mailbox message states
736
-*/
737
-#define MSG_UNREAD 0
738
-#define MSG_READ 1
739
-#define MSG_TRASH 2
740
-#endif /* INTERFACE */
741
-
742
-
743
-/*
744
-** Populate the schema of a database.
745
-**
746
-** eForce==0 Fast
747
-** eForce==1 Run CREATE TABLE statements every time
748
-** eForce==2 DROP then rerun CREATE TABLE
749
-*/
750
-void smtp_server_schema(int eForce){
751
- if( eForce==2 ){
752
- db_multi_exec(zEmailDrop/*works-like:""*/);
753
- }
754
- if( eForce==1 || !db_table_exists("repository","emailblob") ){
755
- db_multi_exec(zEmailSchema/*works-like:""*/);
756
- }
757
-}
758
-
759
-/*
760
-** WEBPAGE: setup_smtp
761
-**
762
-** Administrative page for configuring and controlling inbound email and
763
-** output email queuing. This page is available to administrators
764
-** only via the /Admin/EmailServer menu.
765
-*/
766
-void setup_smtp(void){
767
- Stmt q;
768
- login_check_credentials();
769
- if( !g.perm.Setup ){
770
- login_needed(0);
771
- return;
772
- }
773
- db_begin_transaction();
774
- style_set_current_feature("smtp");
775
- style_header("Email Server Setup");
776
- if( db_table_exists("repository","emailroute") ){
777
- style_submenu_element("emailblob table", "%R/emailblob");
778
- style_submenu_element("emailoutq table", "%R/emailoutq");
779
- db_prepare(&q, "SELECT eaddr, epolicy FROM emailroute ORDER BY 1");
780
- }else{
781
- db_prepare(&q, "SELECT null, null WHERE false");
782
- }
783
- @ <h1>Email Routing Table</h1>
784
- @ <table class="emailroutetab" cellpadding="5" border="1" cellspacing="0">
785
- @ <thead>
786
- @ <tr>
787
- @ <th>Email Address
788
- @ <th>Routing
789
- @ <th>
790
- @ </tr>
791
- @ </thead><tbody>
792
- while( db_step(&q)==SQLITE_ROW ){
793
- const char *zEAddr = db_column_text(&q, 0);
794
- const char *zEPolicy = db_column_text(&q, 1);
795
- @ <tr>
796
- @ <td valign="top">%h(zEAddr)</td>
797
- @ <td valign="top"><span style="white-space:pre;">%h(zEPolicy)</span></td>
798
- @ <td valign="top"><form method="POST" action="%R/setup_smtp_route">
799
- @ <input type="hidden" name="oaddr" value="%h(zEAddr)">
800
- @ <input type="submit" value="Edit">
801
- @ </form>
802
- }
803
- db_finalize(&q);
804
- @ <tr>
805
- @ <td colspan="3">
806
- @ <form method="POST" action="%R/setup_smtp_route">
807
- @ <input type="submit" value="New">
808
- @ &larr; Add a new email address
809
- @ </form>
810
- @ </table>
811
- style_finish_page();
812
- db_end_transaction(0);
813
-}
814
-
815
-/*
816
-** WEBPAGE: setup_smtp_route
817
-**
818
-** Edit a single entry in the emailroute table.
819
-** Query parameters:
820
-**
821
-** eaddr=ADDR ADDR is the email address as edited.
822
-**
823
-** oaddr=ADDR The original email address prior to editing.
824
-** Omit to add a new address.
825
-**
826
-** epolicy=TXT The routing policy.
827
-*/
828
-void setup_smtp_route(void){
829
- char *zEAddr = PT("eaddr"); /* new email address */
830
- char *zEPolicy = PT("epolicy"); /* new routing policy */
831
- char *zOAddr = PT("oaddr"); /* original email address */
832
- char *zErr = 0;
833
- int iErr = 0;
834
- login_check_credentials();
835
- if( !g.perm.Setup ){
836
- login_needed(0);
837
- return;
838
- }
839
- style_set_current_feature("smtp");
840
- style_header("Email Route Editor");
841
-
842
- if( P("edit") && cgi_csrf_safe(1) && zEAddr!=0 && zEPolicy!=0 ){
843
- smtp_server_schema(0);
844
- if( (zOAddr==0 || fossil_strcmp(zEAddr,zOAddr)!=0) ){
845
- /* New or changed email address */
846
- if( db_exists("SELECT 1 FROM emailroute WHERE eaddr=%Q",zEAddr) ){
847
- iErr = 1;
848
- zErr = mprintf("email address \"%h(zEAddr)\" already exists",zEAddr);
849
- goto smtp_route_edit;
850
- }
851
- if( zEPolicy[0]==0 ){
852
- iErr = 2;
853
- zErr = mprintf("empty route");
854
- goto smtp_route_edit;
855
- }
856
- }
857
- /* If the email address has changed, or if the new policy is blank,
858
- ** delete the old address and route information
859
- */
860
- db_begin_transaction();
861
- if( (zOAddr && fossil_strcmp(zEAddr,zOAddr)!=0) || zEPolicy[0]==0 ){
862
- db_multi_exec("DELETE FROM emailroute WHERE eaddr=%Q", zOAddr);
863
- }
864
- if( zEPolicy[0] ){
865
- /* Insert the new address and route */
866
- db_multi_exec(
867
- "REPLACE INTO emailroute(eaddr,epolicy) VALUES(%Q,%Q)",
868
- zEAddr, zEPolicy
869
- );
870
- }
871
- db_end_transaction(0);
872
- cgi_redirectf("%R/setup_smtp");
873
- }
874
- if( P("cancel")!=0 ){
875
- cgi_redirectf("%R/setup_smtp");
876
- }
877
-
878
-smtp_route_edit:
879
- if( zEAddr==0 ) zEAddr = zOAddr;
880
- if( zEPolicy==0 && db_table_exists("repository","emailroute") ){
881
- zEPolicy = db_text(0, "SELECT epolicy FROM emailroute WHERE eaddr=%Q",
882
- zEAddr);
883
- }
884
- if( zEPolicy==0 ) zEPolicy = "";
885
- @ <form method="POST" action="%R/setup_smtp_route">
886
- if( zOAddr ){
887
- @ <input type="hidden" name="oaddr" value="%h(zOAddr)">
888
- }
889
- @ <table class="label-value">
890
- @ <tr>
891
- @ <th>Email Address:</th>
892
- @ <td><input type="text" size=30 name="eaddr" value="%h(zEAddr)">
893
- if( iErr==1 ){
894
- @ <td><span class="generalError">&larr; %z(zErr)</span>
895
- }
896
- @ </tr>
897
- if( zOAddr && fossil_strcmp(zOAddr,zEAddr)!=0 ){
898
- @ <tr>
899
- @ <th>Original Address:</th>
900
- @ <td>%h(zOAddr)
901
- @ </tr>
902
- }
903
- @ <tr>
904
- @ <th>Routing:</th>
905
- @ <td><textarea name="epolicy" rows="3" cols="40">%h(zEPolicy)</textarea>
906
- if( iErr==2 ){
907
- @ <td valign="top"><span class="generalError">&larr; %z(zErr)</span>
908
- }
909
- @ </tr>
910
- @ <tr>
911
- @ <td>&nbsp;
912
- @ <td><input type="submit" name="edit" value="Apply">
913
- @ <input type="submit" name="cancel" value="Cancel">
914
- @ </tr>
915
- @ </table>
916
- @ <hr>
917
- @ <h1>Instructions</h1>
918
- @
919
- @ <p>The "Routing" field consists of zero or more lines where each
920
- @ line is an "action" followed by an "argument". Available actions:
921
- @ <ul>
922
- @ <li><p><b>forward</b> <i>email-address</i>
923
- @ <p>Forward the message to <i>email-address</i>.
924
- @ <li><p><b>mbox</b> <i>login-name</i>
925
- @ <p>Store the message in the local mailbox for the user
926
- @ with USER.LOGIN=<i>login-name</i>.
927
- @ </ul>
928
- @
929
- @ <p>To delete a route &rarr; erase all text from the "Routing" field then
930
- @ press the "Apply" button.
931
- style_finish_page();
932
-}
933
-
934
-#if LOCAL_INTERFACE
935
-/*
936
-** State information for the server
937
-*/
938
-struct SmtpServer {
939
- sqlite3_int64 idTranscript; /* Transcript ID number */
940
- sqlite3_int64 idMsg; /* Message ID number */
941
- const char *zIpAddr; /* Remote IP address */
942
- char *zEhlo; /* Client domain on the EHLO line */
943
- char *zFrom; /* MAIL FROM: argument */
944
- int nTo; /* Number of RCPT TO: lines seen */
945
- struct SmtpTo {
946
- char *z; /* Address in each RCPT TO line */
947
- int okRemote; /* zTo can be in another domain */
948
- } *aTo;
949
- u32 srvrFlags; /* Control flags */
950
- int nEts; /* Number of references to the transcript */
951
- int nRef; /* Number of references to idMsg */
952
- Blob msg; /* Content following DATA */
953
- Blob transcript; /* Session transcript */
954
-};
955
-
956
-#define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */
957
-#define SMTPSRV_CLEAR_ALL 2 /* smtp_server_clear() everything */
958
-#define SMTPSRV_LOG 0x001 /* Record a transcript of the interaction */
959
-#define SMTPSRV_STDERR 0x002 /* Transcription written to stderr */
960
-#define SMTPSRV_DRYRUN 0x004 /* Do not record anything in database */
961
-
962
-#endif /* LOCAL_INTERFACE */
963
-
964
-/*
965
-** Clear the SmtpServer object. Deallocate resources.
966
-** How much to clear depends on eHowMuch
967
-*/
968
-static void smtp_server_clear(SmtpServer *p, int eHowMuch){
969
- int i;
970
- if( eHowMuch>=SMTPSRV_CLEAR_MSG ){
971
- fossil_free(p->zFrom);
972
- p->zFrom = 0;
973
- for(i=0; i<p->nTo; i++) fossil_free(p->aTo[i].z);
974
- fossil_free(p->aTo);
975
- p->aTo = 0;
976
- p->nTo = 0;
977
- blob_reset(&p->msg);
978
- p->idMsg = 0;
979
- }
980
- if( eHowMuch>=SMTPSRV_CLEAR_ALL ){
981
- blob_reset(&p->transcript);
982
- p->idTranscript = 0;
983
- fossil_free(p->zEhlo);
984
- p->zEhlo = 0;
985
- }
986
-}
987
-
988
-/*
989
-** Turn raw memory into an SmtpServer object.
990
-*/
991
-static void smtp_server_init(SmtpServer *p){
992
- memset(p, 0, sizeof(*p));
993
- blob_init(&p->msg, 0, 0);
994
- blob_init(&p->transcript, 0, 0);
995
-}
996
-
997
-/*
998
-** Append a new TO entry to the SmtpServer object. Do not do the
999
-** append if the same entry is already on the list.
1000
-**
1001
-** The zAddr argument is obtained from fossil_malloc(). This
1002
-** routine assumes ownership of the allocation.
1003
-*/
1004
-static void smtp_append_to(SmtpServer *p, char *zAddr, int okRemote){
1005
- int i;
1006
- for(i=0; zAddr[i]; i++){ zAddr[i] = fossil_tolower(zAddr[i]); }
1007
- for(i=0; i<p->nTo; i++){
1008
- if( strcmp(zAddr, p->aTo[i].z)==0 ){
1009
- fossil_free(zAddr);
1010
- if( p->aTo[i].okRemote==0 ) p->aTo[i].okRemote = okRemote;
1011
- return;
1012
- }
1013
- }
1014
- p->aTo = fossil_realloc(p->aTo, (p->nTo+1)*sizeof(p->aTo[0]));
1015
- p->aTo[p->nTo].z = zAddr;
1016
- p->aTo[p->nTo].okRemote = okRemote;
1017
- p->nTo++;
1018
-}
1019
-
1020
-/*
1021
-** Send a single line of output from the server to the client.
1022
-*/
1023
-static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){
1024
- Blob b = empty_blob;
1025
- va_list ap;
1026
- char *z;
1027
- int n;
1028
- va_start(ap, zFormat);
1029
- blob_vappendf(&b, zFormat, ap);
1030
- va_end(ap);
1031
- z = blob_buffer(&b);
1032
- n = blob_size(&b);
1033
- assert( n>=2 );
1034
- assert( z[n-1]=='\n' );
1035
- assert( z[n-2]=='\r' );
1036
- if( p->srvrFlags & SMTPSRV_LOG ){
1037
- blob_appendf(&p->transcript, "S: %.*s\n", n-2, z);
1038
- }
1039
- if( p->srvrFlags & SMTPSRV_STDERR ){
1040
- fprintf(stderr, "S: %.*s\n", n-2, z);
1041
- }
1042
- fwrite(z, n, 1, stdout);
1043
- fflush(stdout);
1044
- blob_reset(&b);
1045
-}
1046
-
1047
-/*
1048
-** Read a single line from the client.
1049
-*/
1050
-static int smtp_server_gets(SmtpServer *p, char *aBuf, int nBuf){
1051
- int rc = fgets(aBuf, nBuf, stdin)!=0;
1052
- if( rc ){
1053
- if( (p->srvrFlags & SMTPSRV_LOG)!=0 ){
1054
- blob_appendf(&p->transcript, "C: %s", aBuf);
1055
- }
1056
- if( (p->srvrFlags & SMTPSRV_STDERR)!=0 ){
1057
- fprintf(stderr, "C: %s", aBuf);
1058
- }
1059
- }
1060
- return rc;
1061
-}
1062
-
1063
-/*
1064
-** RFC-5321 requires certain content be prepended to an email header
1065
-** as that email is received.
1066
-*/
1067
-static void smtp_server_prepend_header_lines(SmtpServer *p){
1068
- blob_appendf(&p->msg, "Received: from %s by Fossil-smtp\r\n", p->zIpAddr);
1069
-}
1070
-
1071
-/*
1072
-** Capture the incoming email data into the p->msg blob. Dequote
1073
-** lines of "..\r\n" into just ".\r\n".
1074
-*/
1075
-static void smtp_server_capture_data(SmtpServer *p, char *z, int n){
1076
- int nLine = 0;
1077
- while( fgets(z, n, stdin) ){
1078
- if( strncmp(z, ".\r\n", 3)==0 || strncmp(z, ".\n",2)==0 ) break;
1079
- nLine++;
1080
- if( strncmp(z, "..\r\n", 4)==0 || strncmp(z, "..\n",3)==0 ){
1081
- memmove(z, z+1, 4);
1082
- }
1083
- blob_append(&p->msg, z, -1);
1084
- }
1085
- if( p->srvrFlags & SMTPSRV_LOG ){
1086
- blob_appendf(&p->transcript, "C: # %d lines, %d bytes of content\n",
1087
- nLine, blob_size(&p->msg));
1088
- }
1089
- if( p->srvrFlags & SMTPSRV_STDERR ){
1090
- fprintf(stderr, "C: # %d lines, %d bytes of content\n",
1091
- nLine, blob_size(&p->msg));
1092
- }
1093
-}
1094
-
1095
-/*
1096
-** Send an email to a single email addess that is registered with
1097
-** this system, according to the instructions in emailroute. If
1098
-** zAddr is not in the emailroute table, then this routine is a
1099
-** no-op. Or if zAddr has already been processed, then this
1100
-** routine is a no-op.
1101
-*/
1102
-static void smtp_server_send_one_user(
1103
- SmtpServer *p, /* The current inbound email */
1104
- const char *zAddr, /* Who to forward this to */
1105
- int okRemote /* True if ok to foward to another domain */
1106
-){
1107
- char *zPolicy;
1108
- Blob policy, line, token, tail;
1109
-
1110
- zPolicy = db_text(0,
1111
- "SELECT epolicy FROM emailroute WHERE eaddr=%Q", zAddr);
1112
- if( zPolicy==0 ){
1113
- if( okRemote ){
1114
- int i;
1115
- for(i=0; zAddr[i] && zAddr[i]!='@'; i++){}
1116
- if( zAddr[i]=='@' && zAddr[i+1]!=0 ){
1117
- db_multi_exec(
1118
- "INSERT INTO emailoutq(edomain,efrom,eto,emsgid,ectime,"
1119
- "emtime,ensend)"
1120
- "VALUES(%Q,%Q,%Q,%lld,now(),0,0)",
1121
- zAddr+i+1, p->zFrom, zAddr, p->idMsg
1122
- );
1123
- p->nRef++;
1124
- }
1125
- }
1126
- return;
1127
- }
1128
- blob_init(&policy, zPolicy, -1);
1129
- while( blob_line(&policy, &line) ){
1130
- blob_trim(&line);
1131
- blob_token(&line, &token);
1132
- blob_tail(&line, &tail);
1133
- if( blob_size(&tail)==0 ) continue;
1134
- if( blob_eq_str(&token, "mbox", 4) ){
1135
- Blob subj;
1136
- email_header_value(&p->msg, "subject", &subj);
1137
- db_multi_exec(
1138
- "INSERT INTO emailbox(euser,edate,efrom,emsgid,estate,esubject)"
1139
- " VALUES(%Q,now(),%Q,%lld,0,%Q)",
1140
- blob_str(&tail), p->zFrom, p->idMsg,
1141
- blob_str(&subj)
1142
- );
1143
- blob_reset(&subj);
1144
- p->nRef++;
1145
- }
1146
- if( blob_eq_str(&token, "forward", 7) ){
1147
- smtp_append_to(p, fossil_strdup(blob_str(&tail)), 1);
1148
- }
1149
- blob_reset(&tail);
1150
- }
1151
-}
1152
-
1153
-/*
1154
-** The SmtpServer object contains a complete incoming email.
1155
-** Add this email to the database.
1156
-*/
1157
-static void smtp_server_route_incoming(SmtpServer *p, int bFinish){
1158
- Stmt s;
1159
- int i;
1160
- int nEtsStart = p->nEts;
1161
- if( p->zFrom
1162
- && p->nTo
1163
- && blob_size(&p->msg)
1164
- && (p->srvrFlags & SMTPSRV_DRYRUN)==0
1165
- ){
1166
- db_begin_write();
1167
- if( p->idTranscript==0 ) smtp_server_schema(0);
1168
- p->nRef = 0;
1169
- db_prepare(&s,
1170
- "INSERT INTO emailblob(ets,etime,etxt,enref,esz)"
1171
- " VALUES(:ets,now(),compress(:etxt),0,:esz)"
1172
- );
1173
- p->nEts++;
1174
- if( !bFinish && p->idTranscript==0 ){
1175
- db_bind_null(&s, ":ets");
1176
- db_bind_null(&s, ":etxt");
1177
- db_bind_null(&s, ":esz");
1178
- db_step(&s);
1179
- db_reset(&s);
1180
- p->idTranscript = db_last_insert_rowid();
1181
- }else if( bFinish ){
1182
- if( p->idTranscript ){
1183
- db_multi_exec(
1184
- "UPDATE emailblob SET etxt=compress(%Q), enref=%d, esz=%d"
1185
- " WHERE emailid=%lld",
1186
- blob_str(&p->transcript), p->nEts, blob_size(&p->transcript),
1187
- p->idTranscript);
1188
- }else{
1189
- db_bind_null(&s, ":ets");
1190
- db_bind_str(&s, ":etxt", &p->transcript);
1191
- db_bind_int(&s, ":esz", blob_size(&p->transcript));
1192
- db_step(&s);
1193
- db_reset(&s);
1194
- p->idTranscript = db_last_insert_rowid();
1195
- db_multi_exec(
1196
- "UPDATE emailblob SET enref=%d WHERE emailid=%lld",
1197
- p->nEts, p->idTranscript);
1198
- }
1199
- /* smtp_server_send(p, "221-Transcript id %lld nref %d\r\n",
1200
- ** p->idTranscript, p->nEts); */
1201
- }
1202
- db_bind_int64(&s, ":ets", p->idTranscript);
1203
- db_bind_str(&s, ":etxt", &p->msg);
1204
- db_bind_int(&s, ":esz", blob_size(&p->msg));
1205
- db_step(&s);
1206
- db_finalize(&s);
1207
- p->idMsg = db_last_insert_rowid();
1208
-
1209
- /* make entries in emailbox and emailoutq */
1210
- for(i=0; i<p->nTo; i++){
1211
- int okRemote = p->aTo[i].okRemote;
1212
- p->aTo[i].okRemote = 1;
1213
- smtp_server_send_one_user(p, p->aTo[i].z, okRemote);
1214
- }
1215
-
1216
- /* Fix up the emailblob.enref field of the email message body */
1217
- if( p->nRef ){
1218
- db_multi_exec(
1219
- "UPDATE emailblob SET enref=%d WHERE emailid=%lld",
1220
- p->nRef, p->idMsg
1221
- );
1222
- }else{
1223
- db_multi_exec(
1224
- "DELETE FROM emailblob WHERE emailid=%lld", p->idMsg
1225
- );
1226
- p->nEts = nEtsStart;
1227
- }
1228
-
1229
- /* Clean out legacy entries */
1230
- if( bFinish ){
1231
- db_multi_exec("DELETE FROM emailblob WHERE enref<=0");
1232
- }
1233
-
1234
- /* Finish the transaction after all changes are implemented */
1235
- db_commit_transaction();
1236
- }
1237
- smtp_server_clear(p, SMTPSRV_CLEAR_MSG);
1238
-}
1239
-
1240
-/*
1241
-** Remove stale content from the emailblob table.
1242
-*/
1243
-int smtp_cleanup(void){
1244
- int nAction = 0;
1245
- if( db_table_exists("repository","emailblob") ){
1246
- db_begin_transaction();
1247
- db_multi_exec(
1248
- "UPDATE emailblob SET ets=NULL WHERE enref<=0;"
1249
- "DELETE FROM emailblob WHERE enref<=0;"
1250
- );
1251
- nAction = db_changes();
1252
- db_end_transaction(0);
1253
- }
1254
- return nAction;
1255
-}
1256
-
1257
-/*
1258
-** COMMAND: test-emailblob-refcheck
1259
-**
1260
-** Usage: %fossil test-emailblob-refcheck [--repair] [--full] [--clean]
1261
-**
1262
-** Verify that the emailblob.enref field is correct. Report any errors.
1263
-** Use the --repair command to fix up the enref field. The --full option
1264
-** gives a full report showing the enref value on all entries in the
1265
-** emailblob table. If the --clean flags is used together with --repair,
1266
-** then emailblob table entires with enref==0 are removed.
1267
-*/
1268
-void test_refcheck_emailblob(void){
1269
- int doRepair;
1270
- int fullReport;
1271
- int doClean;
1272
- Blob sql;
1273
- Stmt q;
1274
- int nErr = 0;
1275
- db_find_and_open_repository(0, 0);
1276
- fullReport = find_option("full",0,0)!=0;
1277
- doRepair = find_option("repair",0,0)!=0;
1278
- doClean = find_option("clean",0,0)!=0;
1279
- verify_all_options();
1280
- if( !db_table_exists("repository","emailblob") ){
1281
- fossil_print("emailblob table is not configured - nothing to check\n");
1282
- return;
1283
- }
1284
- db_multi_exec(
1285
- "CREATE TEMP TABLE refcnt(id INTEGER PRIMARY KEY, n);"
1286
- "INSERT INTO refcnt SELECT ets, count(*) FROM ("
1287
- " SELECT ets FROM emailblob"
1288
- " UNION ALL"
1289
- " SELECT emsgid FROM emailbox"
1290
- " UNION ALL"
1291
- " SELECT emsgid FROM emailoutq"
1292
- ") WHERE ets IS NOT NULL GROUP BY 1;"
1293
- "INSERT OR IGNORE INTO refcnt(id,n) SELECT emailid, 0 FROM emailblob;"
1294
- );
1295
- if( doRepair ){
1296
- db_multi_exec(
1297
- "UPDATE emailblob SET enref=(SELECT n FROM refcnt WHERE id=emailid)"
1298
- );
1299
- if( doClean ){
1300
- smtp_cleanup();
1301
- }
1302
- }
1303
- blob_init(&sql, 0, 0);
1304
- blob_append_sql(&sql,
1305
- "SELECT a.emailid, a.enref, b.n"
1306
- " FROM emailblob AS a JOIN refcnt AS b ON a.emailid=b.id"
1307
- );
1308
- if( !fullReport ){
1309
- blob_append_sql(&sql, " WHERE a.enref!=b.n");
1310
- }
1311
- db_prepare_blob(&q, &sql);
1312
- blob_reset(&sql);
1313
- while( db_step(&q)==SQLITE_ROW ){
1314
- sqlite3_int64 id = db_column_int64(&q,0);
1315
- int n1 = db_column_int(&q, 1);
1316
- int n2 = db_column_int(&q, 2);
1317
- if( n1!=n2 ) nErr++;
1318
- fossil_print("%12lld %4d %4d%s\n", id, n1, n2, n1!=n2 ? " ERROR" : "");
1319
- }
1320
- db_finalize(&q);
1321
- if( nErr ){
1322
- fossil_print("Number of incorrect emailblob.enref values: %d\n",nErr);
1323
- }
1324
-}
1325
-
1326
-
1327
-/*
1328
-** COMMAND: smtpd*
1329
-**
1330
-** Usage: %fossil smtpd [OPTIONS] REPOSITORY
1331
-**
1332
-** Begin a SMTP conversation with a client using stdin/stdout. The
1333
-** received email is stored in REPOSITORY.
1334
-**
1335
-** Options:
1336
-**
1337
-** --dryrun Do not record any emails in the database
1338
-**
1339
-** --trace Print a transcript of the conversation on stderr
1340
-** for debugging and analysis
1341
-**
1342
-** --ipaddr ADDR The SMTP connection originates at ADDR. Or if ADDR
1343
-** is the name of an environment variable, the address
1344
-** is taken from that environment variable.
1345
-*/
1346
-void smtp_server(void){
1347
- char *zDbName;
1348
- const char *zDomain;
1349
- SmtpServer x;
1350
- char z[5000];
1351
-
1352
- smtp_server_init(&x);
1353
- zDomain = find_option("domain",0,1);
1354
- if( zDomain==0 ) zDomain = "";
1355
- x.srvrFlags = SMTPSRV_LOG;
1356
- if( find_option("trace",0,0)!=0 ) x.srvrFlags |= SMTPSRV_STDERR;
1357
- if( find_option("dryrun",0,0)!=0 ) x.srvrFlags |= SMTPSRV_DRYRUN;
1358
- x.zIpAddr = find_option("ipaddr",0,1);
1359
- if( x.zIpAddr ){
1360
- const char *zNew = fossil_getenv(x.zIpAddr);
1361
- if( zNew && zNew[0] ) x.zIpAddr = zNew;
1362
- }
1363
- if( x.zIpAddr==0 ){
1364
- x.zIpAddr = cgi_remote_ip(0);
1365
- if( x.zIpAddr==0 ) x.zIpAddr = "?.?.?.?";
1366
- }
1367
- verify_all_options();
1368
- if( g.argc!=3 ) usage("DBNAME");
1369
- zDbName = g.argv[2];
1370
- zDbName = enter_chroot_jail(zDbName, 0);
1371
- db_open_repository(zDbName);
1372
- add_content_sql_commands(g.db);
1373
- smtp_server_send(&x, "220 %s ESMTP https://fossil-scm.org/ %s\r\n",
1374
- zDomain, MANIFEST_VERSION);
1375
- while( smtp_server_gets(&x, z, sizeof(z)) ){
1376
- if( strncmp(z, "EHLO", 4)==0 && fossil_isspace(z[4]) ){
1377
- smtp_server_send(&x, "250 ok\r\n");
1378
- }else
1379
- if( strncmp(z, "HELO", 4)==0 && fossil_isspace(z[4]) ){
1380
- smtp_server_send(&x, "250 ok\r\n");
1381
- }else
1382
- if( strncmp(z, "MAIL FROM:<", 11)==0 ){
1383
- smtp_server_route_incoming(&x, 0);
1384
- smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
1385
- x.zFrom = email_copy_addr(z+11,'>');
1386
- if( x.zFrom==0 ){
1387
- smtp_server_send(&x, "500 unacceptable email address\r\n");
1388
- }else{
1389
- smtp_server_send(&x, "250 ok\r\n");
1390
- }
1391
- }else
1392
- if( strncmp(z, "RCPT TO:<", 9)==0 ){
1393
- char *zAddr;
1394
- if( x.zFrom==0 ){
1395
- smtp_server_send(&x, "500 missing MAIL FROM\r\n");
1396
- continue;
1397
- }
1398
- zAddr = email_copy_addr(z+9, '>');
1399
- if( zAddr==0 ){
1400
- smtp_server_send(&x, "505 no such user\r\n");
1401
- continue;
1402
- }
1403
- smtp_append_to(&x, zAddr, 0);
1404
- if( x.nTo>=100 ){
1405
- smtp_server_send(&x, "452 too many recipients\r\n");
1406
- continue;
1407
- }
1408
- smtp_server_send(&x, "250 ok\r\n");
1409
- }else
1410
- if( strncmp(z, "DATA", 4)==0 && fossil_isspace(z[4]) ){
1411
- if( x.zFrom==0 || x.nTo==0 ){
1412
- smtp_server_send(&x, "500 missing RCPT TO\r\n");
1413
- continue;
1414
- }
1415
- smtp_server_send(&x, "354 ready\r\n");
1416
- smtp_server_prepend_header_lines(&x);
1417
- smtp_server_capture_data(&x, z, sizeof(z));
1418
- smtp_server_send(&x, "250 ok\r\n");
1419
- }else
1420
- if( strncmp(z, "QUIT", 4)==0 && fossil_isspace(z[4]) ){
1421
- smtp_server_route_incoming(&x, 1);
1422
- smtp_server_send(&x, "221 closing connection\r\n");
1423
- break;
1424
- }else
1425
- {
1426
- smtp_server_send(&x, "500 unknown command\r\n");
1427
- }
1428
- }
1429
- smtp_server_clear(&x, SMTPSRV_CLEAR_ALL);
1430
-}
1431
-
1432
-/*
1433
-** Zero-terminate the argument. Return a pointer the start of the
1434
-** next argument, or to NULL if there are no more arguments.
1435
-*/
1436
-static char *pop3d_arg(char *z){
1437
- if( z[0]==0 || fossil_isspace(z[0]) ){
1438
- return 0;
1439
- }
1440
- z++;
1441
- while( z[0] && !fossil_isspace(z[0]) ){ z++; }
1442
- if( z[0]==0 ) return 0;
1443
- z[0] = 0;
1444
- z++;
1445
- if( z[0]==0 || fossil_isspace(z[0]) ) return 0;
1446
- return z;
1447
-}
1448
-
1449
-/*
1450
-** Write formatted output back to the pop3 client, and also to the
1451
-** log file, if there is a log file.
1452
-*/
1453
-static void pop3_print(FILE *pLog, const char *zFormat, ...){
1454
- va_list ap;
1455
- char zLine[500];
1456
- va_start(ap, zFormat);
1457
- sqlite3_vsnprintf(sizeof(zLine),zLine,zFormat,ap);
1458
- va_end(ap);
1459
- printf("%s\r\n", zLine);
1460
- fflush(stdout);
1461
- if( pLog ) fprintf(pLog, "S: %s\n", zLine);
1462
-}
1463
-
1464
-/*
1465
-** Try to log in for zUser and zPass.
1466
-**
1467
-** zUser can either point to a Fossil user name or to an email address
1468
-** found in the user table's info field, in angle brackets.
1469
-*/
1470
-static int pop3_login(const char *zUser, char *zPass){
1471
- return login_search_uid(&zUser, zPass) != 0;
1472
-}
1473
-
1474
-/*
1475
-** COMMAND: pop3d*
1476
-**
1477
-** Usage: %fossil pop3d [OPTIONS] REPOSITORY
1478
-**
1479
-** Begin a POP3 conversation with a client using stdin/stdout using
1480
-** the mailboxes stored in REPOSITORY.
1481
-**
1482
-** If launched as root, the process first enters a chroot jail using
1483
-** the directory of REPOSITORY as root, then drops all privileges and
1484
-** assumes the user and group of REPOSITORY before reading any content
1485
-** off of the wire.
1486
-**
1487
-** --logdir DIR Each pop3d session creates a new logfile
1488
-** in the directory DIR and records a transcript
1489
-** of the session there. The logfile is opened
1490
-** before entering the chroot jail.
1491
-*/
1492
-void pop3d_command(void){
1493
- char *zDbName;
1494
- char *zA1, *zA2, *zCmd, *z;
1495
- int inAuth = 1;
1496
- int i;
1497
- FILE *pLog = 0;
1498
- const char *zDir;
1499
- Stmt q;
1500
- char zIn[1000];
1501
- char zUser[100];
1502
- zDir = find_option("logdir",0,1);
1503
- if( zDir ){
1504
- char *zFile = file_time_tempname(zDir, ".txt");
1505
- pLog = fossil_fopen(zFile, "w");
1506
- fossil_free(zFile);
1507
- }
1508
- verify_all_options();
1509
- if( g.argc!=3 ) usage("DBNAME");
1510
- zDbName = g.argv[2];
1511
- zDbName = enter_chroot_jail(zDbName, 0);
1512
- db_open_repository(zDbName);
1513
- add_content_sql_commands(g.db);
1514
- pop3_print(pLog, "+OK POP3 server ready");
1515
- while( fgets(zIn, sizeof(zIn), stdin) ){
1516
- if( pLog ) fprintf(pLog, "C: %s", zIn);
1517
- zCmd = zIn;
1518
- zA1 = pop3d_arg(zCmd);
1519
- zA2 = zA1 ? pop3d_arg(zA1) : 0;
1520
- for(i=0; zCmd[i]; i++){ zCmd[i] = fossil_tolower(zCmd[i]); }
1521
- if( strcmp(zCmd,"quit")==0 ){
1522
- if( !inAuth ){
1523
- db_multi_exec(
1524
- "UPDATE emailbox SET estate=2"
1525
- " WHERE estate<2 AND ebid IN (SELECT ebid FROM pop3 WHERE isDel);"
1526
- );
1527
- }
1528
- pop3_print(pLog, "+OK");
1529
- break;
1530
- }
1531
- if( strcmp(zCmd,"capa")==0 ){
1532
- static const char *const azCap[] = {
1533
- "TOP", "USER", "UIDL",
1534
- };
1535
- int i;
1536
- pop3_print(pLog, "+OK");
1537
- for(i=0; i<sizeof(azCap)/sizeof(azCap[0]); i++){
1538
- pop3_print(pLog, "%s", azCap[i]);
1539
- }
1540
- pop3_print(pLog, ".");
1541
- continue;
1542
- }
1543
- if( inAuth ){
1544
- if( strcmp(zCmd,"user")==0 ){
1545
- if( zA1==0 || zA2!=0 ) goto cmd_error;
1546
- sqlite3_snprintf(sizeof(zUser),zUser,"%s",zA1);
1547
- goto cmd_ok;
1548
- }
1549
- if( strcmp(zCmd,"pass")==0 ){
1550
- if( zA1==0 || zA2!=0 ) goto cmd_error;
1551
- if( pop3_login(zUser,zA1)==0 ){
1552
- goto cmd_error;
1553
- }else{
1554
- inAuth = 0;
1555
- db_multi_exec(
1556
- "CREATE TEMP TABLE pop3("
1557
- " id INTEGER PRIMARY KEY,"
1558
- " emailid INT,"
1559
- " ebid INT,"
1560
- " isDel INT,"
1561
- " esz INT"
1562
- ");"
1563
- "INSERT INTO pop3(id,emailid,ebid,isDel,esz)"
1564
- " SELECT NULL, emailid, ebid, 0, esz FROM emailblob, emailbox"
1565
- " WHERE emailid=emsgid AND euser=%Q AND estate<=1"
1566
- " ORDER BY edate;",
1567
- zUser
1568
- );
1569
- goto cmd_ok;
1570
- }
1571
- }
1572
- /* Fossil cannot process APOP since the users clear-text password is
1573
- ** unknown. */
1574
- goto cmd_error;
1575
- }else{
1576
- if( strcmp(zCmd,"stat")==0 ){
1577
- db_prepare(&q, "SELECT count(*), sum(esz) FROM pop3 WHERE NOT isDel");
1578
- if( db_step(&q)==SQLITE_ROW ){
1579
- pop3_print(pLog, "+OK %d %d",
1580
- db_column_int(&q,0), db_column_int(&q,1));
1581
- }else{
1582
- pop3_print(pLog,"-ERR");
1583
- }
1584
- db_finalize(&q);
1585
- continue;
1586
- }
1587
- if( strcmp(zCmd,"list")==0 ){
1588
- if( zA1 ){
1589
- db_prepare(&q, "SELECT id, esz FROM pop3"
1590
- " WHERE id=%d AND NOT isDel", atoi(zA1));
1591
- if( db_step(&q)==SQLITE_ROW ){
1592
- pop3_print(pLog, "+OK %d %d",
1593
- db_column_int(&q,0), db_column_int(&q,1));
1594
- }else{
1595
- pop3_print(pLog, "-ERR");
1596
- }
1597
- }else{
1598
- pop3_print(pLog, "+OK");
1599
- db_prepare(&q, "SELECT id, esz FROM pop3 WHERE NOT isDel");
1600
- while( db_step(&q)==SQLITE_ROW ){
1601
- pop3_print(pLog, "%d %d",
1602
- db_column_int(&q,0), db_column_int(&q,1));
1603
- }
1604
- pop3_print(pLog, ".");
1605
- }
1606
- db_finalize(&q);
1607
- continue;
1608
- }
1609
- if( strcmp(zCmd,"retr")==0 || strcmp(zCmd,"top")==0 ){
1610
- Blob all, line;
1611
- int nLine = 0;
1612
- int iLimit;
1613
- int hdrPending = 1;
1614
- if( zA1==0 ) goto cmd_error;
1615
- iLimit = zA2 ? atoi(zA2) : 2147483647;
1616
- if( iLimit<0 ) goto cmd_error;
1617
- z = db_text(0, "SELECT decompress(emailblob.etxt) "
1618
- " FROM emailblob, pop3"
1619
- " WHERE emailblob.emailid=pop3.emailid"
1620
- " AND pop3.id=%d AND NOT pop3.isDel",
1621
- atoi(zA1));
1622
- if( z==0 ) goto cmd_error;
1623
- pop3_print(pLog, "+OK");
1624
- blob_init(&all, z, -1);
1625
- while( (hdrPending || iLimit>0) && blob_line(&all, &line) ){
1626
- char c = blob_buffer(&line)[0];
1627
- if( c=='.' ){
1628
- fputc('.', stdout);
1629
- }else if( c=='\r' || c=='\n' ){
1630
- hdrPending = 0;
1631
- }
1632
- fwrite(blob_buffer(&line), 1, blob_size(&line), stdout);
1633
- nLine++;
1634
- if( !hdrPending ) iLimit--;
1635
- }
1636
- if( pLog ) fprintf(pLog, "S: # %d lines of content\n", nLine);
1637
- pop3_print(pLog, ".");
1638
- fossil_free(z);
1639
- blob_reset(&all);
1640
- blob_reset(&line);
1641
- fflush(stdout);
1642
- continue;
1643
- }
1644
- if( strcmp(zCmd,"dele")==0 ){
1645
- if( zA1==0 ) goto cmd_error;
1646
- db_multi_exec("UPDATE pop3 SET isDel=1 WHERE id=%d",atoi(zA1));
1647
- goto cmd_ok;
1648
- }
1649
- if( strcmp(zCmd,"rset")==0 ){
1650
- db_multi_exec("UPDATE pop3 SET isDel=0");
1651
- goto cmd_ok;
1652
- }
1653
- if( strcmp(zCmd,"uidl")==0 ){
1654
- if( zA1 ){
1655
- db_prepare(&q, "SELECT id, emailid FROM pop3"
1656
- " WHERE id=%d AND NOT isDel", atoi(zA1));
1657
- if( db_step(&q)==SQLITE_ROW ){
1658
- pop3_print(pLog, "+OK %d %d",
1659
- db_column_int(&q,0), db_column_int(&q,1));
1660
- }else{
1661
- pop3_print(pLog,"-ERR");
1662
- }
1663
- }else{
1664
- pop3_print(pLog, "+OK");
1665
- db_prepare(&q, "SELECT id, emailid FROM pop3 WHERE NOT isDel");
1666
- while( db_step(&q)==SQLITE_ROW ){
1667
- pop3_print(pLog, "%d %d",
1668
- db_column_int(&q,0), db_column_int(&q,1));
1669
- }
1670
- pop3_print(pLog, ".");
1671
- }
1672
- db_finalize(&q);
1673
- continue;
1674
- }
1675
- if( strcmp(zCmd,"noop")==0 ){
1676
- goto cmd_ok;
1677
- }
1678
- /* Else, fall through into cmd_error */
1679
- }
1680
- cmd_error:
1681
- pop3_print(pLog, "-ERR");
1682
- continue;
1683
- cmd_ok:
1684
- pop3_print(pLog, "+OK");
1685
- continue;
1686
- }
1687
- if( pLog ) fclose(pLog);
1688
-}
1689651
--- src/smtp.c
+++ src/smtp.c
@@ -646,1043 +646,5 @@
646 fossil_fatal("ERROR: %s\n", p->zErr);
647 }
648 smtp_session_free(p);
649 blob_reset(&body);
650 }
651
652 /*****************************************************************************
653 ** Server implementation
654 *****************************************************************************/
655
656 /*
657 ** Schema used by the email processing system.
658 */
659 static const char zEmailSchema[] =
660 @ -- bulk storage is in this table. This table can store either
661 @ -- the body of email messages or transcripts of an smtp session.
662 @ CREATE TABLE IF NOT EXISTS repository.emailblob(
663 @ emailid INTEGER PRIMARY KEY AUTOINCREMENT, -- numeric idea for the entry
664 @ enref INT, -- Number of references to this blob
665 @ ets INT, -- Corresponding transcript, or NULL
666 @ etime INT, -- insertion time, secs since 1970
667 @ esz INT, -- uncompressed content size
668 @ etxt TEXT -- content of this entry
669 @ );
670 @
671 @ -- One row for each mailbox entry. All users emails are stored in
672 @ -- this same table.
673 @ CREATE TABLE IF NOT EXISTS repository.emailbox(
674 @ ebid INTEGER PRIMARY KEY, -- Unique id for each mailbox entry
675 @ euser TEXT, -- User who received this email
676 @ edate INT, -- Date received. Seconds since 1970
677 @ efrom TEXT, -- Who is the email from
678 @ emsgid INT, -- Raw email text
679 @ estate INT, -- 0: Unread, 1: read, 2: trash 3: sent
680 @ esubject TEXT, -- Subject line for display
681 @ etags TEXT -- zero or more tags
682 @ );
683 @
684 @ -- Information on how to deliver incoming email.
685 @ CREATE TABLE IF NOT EXISTS repository.emailroute(
686 @ eaddr TEXT PRIMARY KEY, -- Email address
687 @ epolicy TEXT -- How to handle email sent to this address
688 @ ) WITHOUT ROWID;
689 @
690 @ -- Outgoing email queue
691 @ CREATE TABLE IF NOT EXISTS repository.emailoutq(
692 @ edomain TEXT, -- Destination domain. (ex: "fossil-scm.org")
693 @ efrom TEXT, -- Sender email address (envelope "from")
694 @ eto TEXT, -- Recipient email address (envelope "to")
695 @ emsgid INT, -- Message body in the emailblob table
696 @ ectime INT, -- Time enqueued. Seconds since 1970
697 @ emtime INT, -- Time of last send attempt. Sec since 1970
698 @ ensend INT, -- Number of send attempts
699 @ ets INT -- Transcript of last failed attempt
700 @ );
701 @
702 @ -- Triggers to automatically keep the emailblob.enref field up to date
703 @ -- as entries in the emailblob, emailbox, and emailoutq tables are
704 @ -- deleted.
705 @ CREATE TRIGGER IF NOT EXISTS repository.emailblob_d1
706 @ AFTER DELETE ON emailblob BEGIN
707 @ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.ets;
708 @ END;
709 @ CREATE TRIGGER IF NOT EXISTS repository.emailbox_d1
710 @ AFTER DELETE ON emailbox BEGIN
711 @ UPDATE emailblob SET enref=enref-1 WHERE emailid=old.emsgid;
712 @ END;
713 @ CREATE TRIGGER IF NOT EXISTS repository.emailoutq_d1
714 @ AFTER DELETE ON emailoutq BEGIN
715 @ UPDATE emailblob SET enref=enref-1 WHERE emailid IN (old.ets,old.emsgid);
716 @ END;
717 @
718 @ -- An index on the emailblob entries which are unreferenced.
719 @ CREATE INDEX IF NOT EXISTS repository.emailblob_nref ON emailblob(enref)
720 @ WHERE enref<=0;
721 ;
722
723 /*
724 ** Code used to delete the email tables.
725 */
726 static const char zEmailDrop[] =
727 @ DROP TABLE IF EXISTS emailblob;
728 @ DROP TABLE IF EXISTS emailbox;
729 @ DROP TABLE IF EXISTS emailroute;
730 @ DROP TABLE IF EXISTS emailqueue;
731 ;
732
733 #if INTERFACE
734 /*
735 ** Mailbox message states
736 */
737 #define MSG_UNREAD 0
738 #define MSG_READ 1
739 #define MSG_TRASH 2
740 #endif /* INTERFACE */
741
742
743 /*
744 ** Populate the schema of a database.
745 **
746 ** eForce==0 Fast
747 ** eForce==1 Run CREATE TABLE statements every time
748 ** eForce==2 DROP then rerun CREATE TABLE
749 */
750 void smtp_server_schema(int eForce){
751 if( eForce==2 ){
752 db_multi_exec(zEmailDrop/*works-like:""*/);
753 }
754 if( eForce==1 || !db_table_exists("repository","emailblob") ){
755 db_multi_exec(zEmailSchema/*works-like:""*/);
756 }
757 }
758
759 /*
760 ** WEBPAGE: setup_smtp
761 **
762 ** Administrative page for configuring and controlling inbound email and
763 ** output email queuing. This page is available to administrators
764 ** only via the /Admin/EmailServer menu.
765 */
766 void setup_smtp(void){
767 Stmt q;
768 login_check_credentials();
769 if( !g.perm.Setup ){
770 login_needed(0);
771 return;
772 }
773 db_begin_transaction();
774 style_set_current_feature("smtp");
775 style_header("Email Server Setup");
776 if( db_table_exists("repository","emailroute") ){
777 style_submenu_element("emailblob table", "%R/emailblob");
778 style_submenu_element("emailoutq table", "%R/emailoutq");
779 db_prepare(&q, "SELECT eaddr, epolicy FROM emailroute ORDER BY 1");
780 }else{
781 db_prepare(&q, "SELECT null, null WHERE false");
782 }
783 @ <h1>Email Routing Table</h1>
784 @ <table class="emailroutetab" cellpadding="5" border="1" cellspacing="0">
785 @ <thead>
786 @ <tr>
787 @ <th>Email Address
788 @ <th>Routing
789 @ <th>
790 @ </tr>
791 @ </thead><tbody>
792 while( db_step(&q)==SQLITE_ROW ){
793 const char *zEAddr = db_column_text(&q, 0);
794 const char *zEPolicy = db_column_text(&q, 1);
795 @ <tr>
796 @ <td valign="top">%h(zEAddr)</td>
797 @ <td valign="top"><span style="white-space:pre;">%h(zEPolicy)</span></td>
798 @ <td valign="top"><form method="POST" action="%R/setup_smtp_route">
799 @ <input type="hidden" name="oaddr" value="%h(zEAddr)">
800 @ <input type="submit" value="Edit">
801 @ </form>
802 }
803 db_finalize(&q);
804 @ <tr>
805 @ <td colspan="3">
806 @ <form method="POST" action="%R/setup_smtp_route">
807 @ <input type="submit" value="New">
808 @ &larr; Add a new email address
809 @ </form>
810 @ </table>
811 style_finish_page();
812 db_end_transaction(0);
813 }
814
815 /*
816 ** WEBPAGE: setup_smtp_route
817 **
818 ** Edit a single entry in the emailroute table.
819 ** Query parameters:
820 **
821 ** eaddr=ADDR ADDR is the email address as edited.
822 **
823 ** oaddr=ADDR The original email address prior to editing.
824 ** Omit to add a new address.
825 **
826 ** epolicy=TXT The routing policy.
827 */
828 void setup_smtp_route(void){
829 char *zEAddr = PT("eaddr"); /* new email address */
830 char *zEPolicy = PT("epolicy"); /* new routing policy */
831 char *zOAddr = PT("oaddr"); /* original email address */
832 char *zErr = 0;
833 int iErr = 0;
834 login_check_credentials();
835 if( !g.perm.Setup ){
836 login_needed(0);
837 return;
838 }
839 style_set_current_feature("smtp");
840 style_header("Email Route Editor");
841
842 if( P("edit") && cgi_csrf_safe(1) && zEAddr!=0 && zEPolicy!=0 ){
843 smtp_server_schema(0);
844 if( (zOAddr==0 || fossil_strcmp(zEAddr,zOAddr)!=0) ){
845 /* New or changed email address */
846 if( db_exists("SELECT 1 FROM emailroute WHERE eaddr=%Q",zEAddr) ){
847 iErr = 1;
848 zErr = mprintf("email address \"%h(zEAddr)\" already exists",zEAddr);
849 goto smtp_route_edit;
850 }
851 if( zEPolicy[0]==0 ){
852 iErr = 2;
853 zErr = mprintf("empty route");
854 goto smtp_route_edit;
855 }
856 }
857 /* If the email address has changed, or if the new policy is blank,
858 ** delete the old address and route information
859 */
860 db_begin_transaction();
861 if( (zOAddr && fossil_strcmp(zEAddr,zOAddr)!=0) || zEPolicy[0]==0 ){
862 db_multi_exec("DELETE FROM emailroute WHERE eaddr=%Q", zOAddr);
863 }
864 if( zEPolicy[0] ){
865 /* Insert the new address and route */
866 db_multi_exec(
867 "REPLACE INTO emailroute(eaddr,epolicy) VALUES(%Q,%Q)",
868 zEAddr, zEPolicy
869 );
870 }
871 db_end_transaction(0);
872 cgi_redirectf("%R/setup_smtp");
873 }
874 if( P("cancel")!=0 ){
875 cgi_redirectf("%R/setup_smtp");
876 }
877
878 smtp_route_edit:
879 if( zEAddr==0 ) zEAddr = zOAddr;
880 if( zEPolicy==0 && db_table_exists("repository","emailroute") ){
881 zEPolicy = db_text(0, "SELECT epolicy FROM emailroute WHERE eaddr=%Q",
882 zEAddr);
883 }
884 if( zEPolicy==0 ) zEPolicy = "";
885 @ <form method="POST" action="%R/setup_smtp_route">
886 if( zOAddr ){
887 @ <input type="hidden" name="oaddr" value="%h(zOAddr)">
888 }
889 @ <table class="label-value">
890 @ <tr>
891 @ <th>Email Address:</th>
892 @ <td><input type="text" size=30 name="eaddr" value="%h(zEAddr)">
893 if( iErr==1 ){
894 @ <td><span class="generalError">&larr; %z(zErr)</span>
895 }
896 @ </tr>
897 if( zOAddr && fossil_strcmp(zOAddr,zEAddr)!=0 ){
898 @ <tr>
899 @ <th>Original Address:</th>
900 @ <td>%h(zOAddr)
901 @ </tr>
902 }
903 @ <tr>
904 @ <th>Routing:</th>
905 @ <td><textarea name="epolicy" rows="3" cols="40">%h(zEPolicy)</textarea>
906 if( iErr==2 ){
907 @ <td valign="top"><span class="generalError">&larr; %z(zErr)</span>
908 }
909 @ </tr>
910 @ <tr>
911 @ <td>&nbsp;
912 @ <td><input type="submit" name="edit" value="Apply">
913 @ <input type="submit" name="cancel" value="Cancel">
914 @ </tr>
915 @ </table>
916 @ <hr>
917 @ <h1>Instructions</h1>
918 @
919 @ <p>The "Routing" field consists of zero or more lines where each
920 @ line is an "action" followed by an "argument". Available actions:
921 @ <ul>
922 @ <li><p><b>forward</b> <i>email-address</i>
923 @ <p>Forward the message to <i>email-address</i>.
924 @ <li><p><b>mbox</b> <i>login-name</i>
925 @ <p>Store the message in the local mailbox for the user
926 @ with USER.LOGIN=<i>login-name</i>.
927 @ </ul>
928 @
929 @ <p>To delete a route &rarr; erase all text from the "Routing" field then
930 @ press the "Apply" button.
931 style_finish_page();
932 }
933
934 #if LOCAL_INTERFACE
935 /*
936 ** State information for the server
937 */
938 struct SmtpServer {
939 sqlite3_int64 idTranscript; /* Transcript ID number */
940 sqlite3_int64 idMsg; /* Message ID number */
941 const char *zIpAddr; /* Remote IP address */
942 char *zEhlo; /* Client domain on the EHLO line */
943 char *zFrom; /* MAIL FROM: argument */
944 int nTo; /* Number of RCPT TO: lines seen */
945 struct SmtpTo {
946 char *z; /* Address in each RCPT TO line */
947 int okRemote; /* zTo can be in another domain */
948 } *aTo;
949 u32 srvrFlags; /* Control flags */
950 int nEts; /* Number of references to the transcript */
951 int nRef; /* Number of references to idMsg */
952 Blob msg; /* Content following DATA */
953 Blob transcript; /* Session transcript */
954 };
955
956 #define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */
957 #define SMTPSRV_CLEAR_ALL 2 /* smtp_server_clear() everything */
958 #define SMTPSRV_LOG 0x001 /* Record a transcript of the interaction */
959 #define SMTPSRV_STDERR 0x002 /* Transcription written to stderr */
960 #define SMTPSRV_DRYRUN 0x004 /* Do not record anything in database */
961
962 #endif /* LOCAL_INTERFACE */
963
964 /*
965 ** Clear the SmtpServer object. Deallocate resources.
966 ** How much to clear depends on eHowMuch
967 */
968 static void smtp_server_clear(SmtpServer *p, int eHowMuch){
969 int i;
970 if( eHowMuch>=SMTPSRV_CLEAR_MSG ){
971 fossil_free(p->zFrom);
972 p->zFrom = 0;
973 for(i=0; i<p->nTo; i++) fossil_free(p->aTo[i].z);
974 fossil_free(p->aTo);
975 p->aTo = 0;
976 p->nTo = 0;
977 blob_reset(&p->msg);
978 p->idMsg = 0;
979 }
980 if( eHowMuch>=SMTPSRV_CLEAR_ALL ){
981 blob_reset(&p->transcript);
982 p->idTranscript = 0;
983 fossil_free(p->zEhlo);
984 p->zEhlo = 0;
985 }
986 }
987
988 /*
989 ** Turn raw memory into an SmtpServer object.
990 */
991 static void smtp_server_init(SmtpServer *p){
992 memset(p, 0, sizeof(*p));
993 blob_init(&p->msg, 0, 0);
994 blob_init(&p->transcript, 0, 0);
995 }
996
997 /*
998 ** Append a new TO entry to the SmtpServer object. Do not do the
999 ** append if the same entry is already on the list.
1000 **
1001 ** The zAddr argument is obtained from fossil_malloc(). This
1002 ** routine assumes ownership of the allocation.
1003 */
1004 static void smtp_append_to(SmtpServer *p, char *zAddr, int okRemote){
1005 int i;
1006 for(i=0; zAddr[i]; i++){ zAddr[i] = fossil_tolower(zAddr[i]); }
1007 for(i=0; i<p->nTo; i++){
1008 if( strcmp(zAddr, p->aTo[i].z)==0 ){
1009 fossil_free(zAddr);
1010 if( p->aTo[i].okRemote==0 ) p->aTo[i].okRemote = okRemote;
1011 return;
1012 }
1013 }
1014 p->aTo = fossil_realloc(p->aTo, (p->nTo+1)*sizeof(p->aTo[0]));
1015 p->aTo[p->nTo].z = zAddr;
1016 p->aTo[p->nTo].okRemote = okRemote;
1017 p->nTo++;
1018 }
1019
1020 /*
1021 ** Send a single line of output from the server to the client.
1022 */
1023 static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){
1024 Blob b = empty_blob;
1025 va_list ap;
1026 char *z;
1027 int n;
1028 va_start(ap, zFormat);
1029 blob_vappendf(&b, zFormat, ap);
1030 va_end(ap);
1031 z = blob_buffer(&b);
1032 n = blob_size(&b);
1033 assert( n>=2 );
1034 assert( z[n-1]=='\n' );
1035 assert( z[n-2]=='\r' );
1036 if( p->srvrFlags & SMTPSRV_LOG ){
1037 blob_appendf(&p->transcript, "S: %.*s\n", n-2, z);
1038 }
1039 if( p->srvrFlags & SMTPSRV_STDERR ){
1040 fprintf(stderr, "S: %.*s\n", n-2, z);
1041 }
1042 fwrite(z, n, 1, stdout);
1043 fflush(stdout);
1044 blob_reset(&b);
1045 }
1046
1047 /*
1048 ** Read a single line from the client.
1049 */
1050 static int smtp_server_gets(SmtpServer *p, char *aBuf, int nBuf){
1051 int rc = fgets(aBuf, nBuf, stdin)!=0;
1052 if( rc ){
1053 if( (p->srvrFlags & SMTPSRV_LOG)!=0 ){
1054 blob_appendf(&p->transcript, "C: %s", aBuf);
1055 }
1056 if( (p->srvrFlags & SMTPSRV_STDERR)!=0 ){
1057 fprintf(stderr, "C: %s", aBuf);
1058 }
1059 }
1060 return rc;
1061 }
1062
1063 /*
1064 ** RFC-5321 requires certain content be prepended to an email header
1065 ** as that email is received.
1066 */
1067 static void smtp_server_prepend_header_lines(SmtpServer *p){
1068 blob_appendf(&p->msg, "Received: from %s by Fossil-smtp\r\n", p->zIpAddr);
1069 }
1070
1071 /*
1072 ** Capture the incoming email data into the p->msg blob. Dequote
1073 ** lines of "..\r\n" into just ".\r\n".
1074 */
1075 static void smtp_server_capture_data(SmtpServer *p, char *z, int n){
1076 int nLine = 0;
1077 while( fgets(z, n, stdin) ){
1078 if( strncmp(z, ".\r\n", 3)==0 || strncmp(z, ".\n",2)==0 ) break;
1079 nLine++;
1080 if( strncmp(z, "..\r\n", 4)==0 || strncmp(z, "..\n",3)==0 ){
1081 memmove(z, z+1, 4);
1082 }
1083 blob_append(&p->msg, z, -1);
1084 }
1085 if( p->srvrFlags & SMTPSRV_LOG ){
1086 blob_appendf(&p->transcript, "C: # %d lines, %d bytes of content\n",
1087 nLine, blob_size(&p->msg));
1088 }
1089 if( p->srvrFlags & SMTPSRV_STDERR ){
1090 fprintf(stderr, "C: # %d lines, %d bytes of content\n",
1091 nLine, blob_size(&p->msg));
1092 }
1093 }
1094
1095 /*
1096 ** Send an email to a single email addess that is registered with
1097 ** this system, according to the instructions in emailroute. If
1098 ** zAddr is not in the emailroute table, then this routine is a
1099 ** no-op. Or if zAddr has already been processed, then this
1100 ** routine is a no-op.
1101 */
1102 static void smtp_server_send_one_user(
1103 SmtpServer *p, /* The current inbound email */
1104 const char *zAddr, /* Who to forward this to */
1105 int okRemote /* True if ok to foward to another domain */
1106 ){
1107 char *zPolicy;
1108 Blob policy, line, token, tail;
1109
1110 zPolicy = db_text(0,
1111 "SELECT epolicy FROM emailroute WHERE eaddr=%Q", zAddr);
1112 if( zPolicy==0 ){
1113 if( okRemote ){
1114 int i;
1115 for(i=0; zAddr[i] && zAddr[i]!='@'; i++){}
1116 if( zAddr[i]=='@' && zAddr[i+1]!=0 ){
1117 db_multi_exec(
1118 "INSERT INTO emailoutq(edomain,efrom,eto,emsgid,ectime,"
1119 "emtime,ensend)"
1120 "VALUES(%Q,%Q,%Q,%lld,now(),0,0)",
1121 zAddr+i+1, p->zFrom, zAddr, p->idMsg
1122 );
1123 p->nRef++;
1124 }
1125 }
1126 return;
1127 }
1128 blob_init(&policy, zPolicy, -1);
1129 while( blob_line(&policy, &line) ){
1130 blob_trim(&line);
1131 blob_token(&line, &token);
1132 blob_tail(&line, &tail);
1133 if( blob_size(&tail)==0 ) continue;
1134 if( blob_eq_str(&token, "mbox", 4) ){
1135 Blob subj;
1136 email_header_value(&p->msg, "subject", &subj);
1137 db_multi_exec(
1138 "INSERT INTO emailbox(euser,edate,efrom,emsgid,estate,esubject)"
1139 " VALUES(%Q,now(),%Q,%lld,0,%Q)",
1140 blob_str(&tail), p->zFrom, p->idMsg,
1141 blob_str(&subj)
1142 );
1143 blob_reset(&subj);
1144 p->nRef++;
1145 }
1146 if( blob_eq_str(&token, "forward", 7) ){
1147 smtp_append_to(p, fossil_strdup(blob_str(&tail)), 1);
1148 }
1149 blob_reset(&tail);
1150 }
1151 }
1152
1153 /*
1154 ** The SmtpServer object contains a complete incoming email.
1155 ** Add this email to the database.
1156 */
1157 static void smtp_server_route_incoming(SmtpServer *p, int bFinish){
1158 Stmt s;
1159 int i;
1160 int nEtsStart = p->nEts;
1161 if( p->zFrom
1162 && p->nTo
1163 && blob_size(&p->msg)
1164 && (p->srvrFlags & SMTPSRV_DRYRUN)==0
1165 ){
1166 db_begin_write();
1167 if( p->idTranscript==0 ) smtp_server_schema(0);
1168 p->nRef = 0;
1169 db_prepare(&s,
1170 "INSERT INTO emailblob(ets,etime,etxt,enref,esz)"
1171 " VALUES(:ets,now(),compress(:etxt),0,:esz)"
1172 );
1173 p->nEts++;
1174 if( !bFinish && p->idTranscript==0 ){
1175 db_bind_null(&s, ":ets");
1176 db_bind_null(&s, ":etxt");
1177 db_bind_null(&s, ":esz");
1178 db_step(&s);
1179 db_reset(&s);
1180 p->idTranscript = db_last_insert_rowid();
1181 }else if( bFinish ){
1182 if( p->idTranscript ){
1183 db_multi_exec(
1184 "UPDATE emailblob SET etxt=compress(%Q), enref=%d, esz=%d"
1185 " WHERE emailid=%lld",
1186 blob_str(&p->transcript), p->nEts, blob_size(&p->transcript),
1187 p->idTranscript);
1188 }else{
1189 db_bind_null(&s, ":ets");
1190 db_bind_str(&s, ":etxt", &p->transcript);
1191 db_bind_int(&s, ":esz", blob_size(&p->transcript));
1192 db_step(&s);
1193 db_reset(&s);
1194 p->idTranscript = db_last_insert_rowid();
1195 db_multi_exec(
1196 "UPDATE emailblob SET enref=%d WHERE emailid=%lld",
1197 p->nEts, p->idTranscript);
1198 }
1199 /* smtp_server_send(p, "221-Transcript id %lld nref %d\r\n",
1200 ** p->idTranscript, p->nEts); */
1201 }
1202 db_bind_int64(&s, ":ets", p->idTranscript);
1203 db_bind_str(&s, ":etxt", &p->msg);
1204 db_bind_int(&s, ":esz", blob_size(&p->msg));
1205 db_step(&s);
1206 db_finalize(&s);
1207 p->idMsg = db_last_insert_rowid();
1208
1209 /* make entries in emailbox and emailoutq */
1210 for(i=0; i<p->nTo; i++){
1211 int okRemote = p->aTo[i].okRemote;
1212 p->aTo[i].okRemote = 1;
1213 smtp_server_send_one_user(p, p->aTo[i].z, okRemote);
1214 }
1215
1216 /* Fix up the emailblob.enref field of the email message body */
1217 if( p->nRef ){
1218 db_multi_exec(
1219 "UPDATE emailblob SET enref=%d WHERE emailid=%lld",
1220 p->nRef, p->idMsg
1221 );
1222 }else{
1223 db_multi_exec(
1224 "DELETE FROM emailblob WHERE emailid=%lld", p->idMsg
1225 );
1226 p->nEts = nEtsStart;
1227 }
1228
1229 /* Clean out legacy entries */
1230 if( bFinish ){
1231 db_multi_exec("DELETE FROM emailblob WHERE enref<=0");
1232 }
1233
1234 /* Finish the transaction after all changes are implemented */
1235 db_commit_transaction();
1236 }
1237 smtp_server_clear(p, SMTPSRV_CLEAR_MSG);
1238 }
1239
1240 /*
1241 ** Remove stale content from the emailblob table.
1242 */
1243 int smtp_cleanup(void){
1244 int nAction = 0;
1245 if( db_table_exists("repository","emailblob") ){
1246 db_begin_transaction();
1247 db_multi_exec(
1248 "UPDATE emailblob SET ets=NULL WHERE enref<=0;"
1249 "DELETE FROM emailblob WHERE enref<=0;"
1250 );
1251 nAction = db_changes();
1252 db_end_transaction(0);
1253 }
1254 return nAction;
1255 }
1256
1257 /*
1258 ** COMMAND: test-emailblob-refcheck
1259 **
1260 ** Usage: %fossil test-emailblob-refcheck [--repair] [--full] [--clean]
1261 **
1262 ** Verify that the emailblob.enref field is correct. Report any errors.
1263 ** Use the --repair command to fix up the enref field. The --full option
1264 ** gives a full report showing the enref value on all entries in the
1265 ** emailblob table. If the --clean flags is used together with --repair,
1266 ** then emailblob table entires with enref==0 are removed.
1267 */
1268 void test_refcheck_emailblob(void){
1269 int doRepair;
1270 int fullReport;
1271 int doClean;
1272 Blob sql;
1273 Stmt q;
1274 int nErr = 0;
1275 db_find_and_open_repository(0, 0);
1276 fullReport = find_option("full",0,0)!=0;
1277 doRepair = find_option("repair",0,0)!=0;
1278 doClean = find_option("clean",0,0)!=0;
1279 verify_all_options();
1280 if( !db_table_exists("repository","emailblob") ){
1281 fossil_print("emailblob table is not configured - nothing to check\n");
1282 return;
1283 }
1284 db_multi_exec(
1285 "CREATE TEMP TABLE refcnt(id INTEGER PRIMARY KEY, n);"
1286 "INSERT INTO refcnt SELECT ets, count(*) FROM ("
1287 " SELECT ets FROM emailblob"
1288 " UNION ALL"
1289 " SELECT emsgid FROM emailbox"
1290 " UNION ALL"
1291 " SELECT emsgid FROM emailoutq"
1292 ") WHERE ets IS NOT NULL GROUP BY 1;"
1293 "INSERT OR IGNORE INTO refcnt(id,n) SELECT emailid, 0 FROM emailblob;"
1294 );
1295 if( doRepair ){
1296 db_multi_exec(
1297 "UPDATE emailblob SET enref=(SELECT n FROM refcnt WHERE id=emailid)"
1298 );
1299 if( doClean ){
1300 smtp_cleanup();
1301 }
1302 }
1303 blob_init(&sql, 0, 0);
1304 blob_append_sql(&sql,
1305 "SELECT a.emailid, a.enref, b.n"
1306 " FROM emailblob AS a JOIN refcnt AS b ON a.emailid=b.id"
1307 );
1308 if( !fullReport ){
1309 blob_append_sql(&sql, " WHERE a.enref!=b.n");
1310 }
1311 db_prepare_blob(&q, &sql);
1312 blob_reset(&sql);
1313 while( db_step(&q)==SQLITE_ROW ){
1314 sqlite3_int64 id = db_column_int64(&q,0);
1315 int n1 = db_column_int(&q, 1);
1316 int n2 = db_column_int(&q, 2);
1317 if( n1!=n2 ) nErr++;
1318 fossil_print("%12lld %4d %4d%s\n", id, n1, n2, n1!=n2 ? " ERROR" : "");
1319 }
1320 db_finalize(&q);
1321 if( nErr ){
1322 fossil_print("Number of incorrect emailblob.enref values: %d\n",nErr);
1323 }
1324 }
1325
1326
1327 /*
1328 ** COMMAND: smtpd*
1329 **
1330 ** Usage: %fossil smtpd [OPTIONS] REPOSITORY
1331 **
1332 ** Begin a SMTP conversation with a client using stdin/stdout. The
1333 ** received email is stored in REPOSITORY.
1334 **
1335 ** Options:
1336 **
1337 ** --dryrun Do not record any emails in the database
1338 **
1339 ** --trace Print a transcript of the conversation on stderr
1340 ** for debugging and analysis
1341 **
1342 ** --ipaddr ADDR The SMTP connection originates at ADDR. Or if ADDR
1343 ** is the name of an environment variable, the address
1344 ** is taken from that environment variable.
1345 */
1346 void smtp_server(void){
1347 char *zDbName;
1348 const char *zDomain;
1349 SmtpServer x;
1350 char z[5000];
1351
1352 smtp_server_init(&x);
1353 zDomain = find_option("domain",0,1);
1354 if( zDomain==0 ) zDomain = "";
1355 x.srvrFlags = SMTPSRV_LOG;
1356 if( find_option("trace",0,0)!=0 ) x.srvrFlags |= SMTPSRV_STDERR;
1357 if( find_option("dryrun",0,0)!=0 ) x.srvrFlags |= SMTPSRV_DRYRUN;
1358 x.zIpAddr = find_option("ipaddr",0,1);
1359 if( x.zIpAddr ){
1360 const char *zNew = fossil_getenv(x.zIpAddr);
1361 if( zNew && zNew[0] ) x.zIpAddr = zNew;
1362 }
1363 if( x.zIpAddr==0 ){
1364 x.zIpAddr = cgi_remote_ip(0);
1365 if( x.zIpAddr==0 ) x.zIpAddr = "?.?.?.?";
1366 }
1367 verify_all_options();
1368 if( g.argc!=3 ) usage("DBNAME");
1369 zDbName = g.argv[2];
1370 zDbName = enter_chroot_jail(zDbName, 0);
1371 db_open_repository(zDbName);
1372 add_content_sql_commands(g.db);
1373 smtp_server_send(&x, "220 %s ESMTP https://fossil-scm.org/ %s\r\n",
1374 zDomain, MANIFEST_VERSION);
1375 while( smtp_server_gets(&x, z, sizeof(z)) ){
1376 if( strncmp(z, "EHLO", 4)==0 && fossil_isspace(z[4]) ){
1377 smtp_server_send(&x, "250 ok\r\n");
1378 }else
1379 if( strncmp(z, "HELO", 4)==0 && fossil_isspace(z[4]) ){
1380 smtp_server_send(&x, "250 ok\r\n");
1381 }else
1382 if( strncmp(z, "MAIL FROM:<", 11)==0 ){
1383 smtp_server_route_incoming(&x, 0);
1384 smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
1385 x.zFrom = email_copy_addr(z+11,'>');
1386 if( x.zFrom==0 ){
1387 smtp_server_send(&x, "500 unacceptable email address\r\n");
1388 }else{
1389 smtp_server_send(&x, "250 ok\r\n");
1390 }
1391 }else
1392 if( strncmp(z, "RCPT TO:<", 9)==0 ){
1393 char *zAddr;
1394 if( x.zFrom==0 ){
1395 smtp_server_send(&x, "500 missing MAIL FROM\r\n");
1396 continue;
1397 }
1398 zAddr = email_copy_addr(z+9, '>');
1399 if( zAddr==0 ){
1400 smtp_server_send(&x, "505 no such user\r\n");
1401 continue;
1402 }
1403 smtp_append_to(&x, zAddr, 0);
1404 if( x.nTo>=100 ){
1405 smtp_server_send(&x, "452 too many recipients\r\n");
1406 continue;
1407 }
1408 smtp_server_send(&x, "250 ok\r\n");
1409 }else
1410 if( strncmp(z, "DATA", 4)==0 && fossil_isspace(z[4]) ){
1411 if( x.zFrom==0 || x.nTo==0 ){
1412 smtp_server_send(&x, "500 missing RCPT TO\r\n");
1413 continue;
1414 }
1415 smtp_server_send(&x, "354 ready\r\n");
1416 smtp_server_prepend_header_lines(&x);
1417 smtp_server_capture_data(&x, z, sizeof(z));
1418 smtp_server_send(&x, "250 ok\r\n");
1419 }else
1420 if( strncmp(z, "QUIT", 4)==0 && fossil_isspace(z[4]) ){
1421 smtp_server_route_incoming(&x, 1);
1422 smtp_server_send(&x, "221 closing connection\r\n");
1423 break;
1424 }else
1425 {
1426 smtp_server_send(&x, "500 unknown command\r\n");
1427 }
1428 }
1429 smtp_server_clear(&x, SMTPSRV_CLEAR_ALL);
1430 }
1431
1432 /*
1433 ** Zero-terminate the argument. Return a pointer the start of the
1434 ** next argument, or to NULL if there are no more arguments.
1435 */
1436 static char *pop3d_arg(char *z){
1437 if( z[0]==0 || fossil_isspace(z[0]) ){
1438 return 0;
1439 }
1440 z++;
1441 while( z[0] && !fossil_isspace(z[0]) ){ z++; }
1442 if( z[0]==0 ) return 0;
1443 z[0] = 0;
1444 z++;
1445 if( z[0]==0 || fossil_isspace(z[0]) ) return 0;
1446 return z;
1447 }
1448
1449 /*
1450 ** Write formatted output back to the pop3 client, and also to the
1451 ** log file, if there is a log file.
1452 */
1453 static void pop3_print(FILE *pLog, const char *zFormat, ...){
1454 va_list ap;
1455 char zLine[500];
1456 va_start(ap, zFormat);
1457 sqlite3_vsnprintf(sizeof(zLine),zLine,zFormat,ap);
1458 va_end(ap);
1459 printf("%s\r\n", zLine);
1460 fflush(stdout);
1461 if( pLog ) fprintf(pLog, "S: %s\n", zLine);
1462 }
1463
1464 /*
1465 ** Try to log in for zUser and zPass.
1466 **
1467 ** zUser can either point to a Fossil user name or to an email address
1468 ** found in the user table's info field, in angle brackets.
1469 */
1470 static int pop3_login(const char *zUser, char *zPass){
1471 return login_search_uid(&zUser, zPass) != 0;
1472 }
1473
1474 /*
1475 ** COMMAND: pop3d*
1476 **
1477 ** Usage: %fossil pop3d [OPTIONS] REPOSITORY
1478 **
1479 ** Begin a POP3 conversation with a client using stdin/stdout using
1480 ** the mailboxes stored in REPOSITORY.
1481 **
1482 ** If launched as root, the process first enters a chroot jail using
1483 ** the directory of REPOSITORY as root, then drops all privileges and
1484 ** assumes the user and group of REPOSITORY before reading any content
1485 ** off of the wire.
1486 **
1487 ** --logdir DIR Each pop3d session creates a new logfile
1488 ** in the directory DIR and records a transcript
1489 ** of the session there. The logfile is opened
1490 ** before entering the chroot jail.
1491 */
1492 void pop3d_command(void){
1493 char *zDbName;
1494 char *zA1, *zA2, *zCmd, *z;
1495 int inAuth = 1;
1496 int i;
1497 FILE *pLog = 0;
1498 const char *zDir;
1499 Stmt q;
1500 char zIn[1000];
1501 char zUser[100];
1502 zDir = find_option("logdir",0,1);
1503 if( zDir ){
1504 char *zFile = file_time_tempname(zDir, ".txt");
1505 pLog = fossil_fopen(zFile, "w");
1506 fossil_free(zFile);
1507 }
1508 verify_all_options();
1509 if( g.argc!=3 ) usage("DBNAME");
1510 zDbName = g.argv[2];
1511 zDbName = enter_chroot_jail(zDbName, 0);
1512 db_open_repository(zDbName);
1513 add_content_sql_commands(g.db);
1514 pop3_print(pLog, "+OK POP3 server ready");
1515 while( fgets(zIn, sizeof(zIn), stdin) ){
1516 if( pLog ) fprintf(pLog, "C: %s", zIn);
1517 zCmd = zIn;
1518 zA1 = pop3d_arg(zCmd);
1519 zA2 = zA1 ? pop3d_arg(zA1) : 0;
1520 for(i=0; zCmd[i]; i++){ zCmd[i] = fossil_tolower(zCmd[i]); }
1521 if( strcmp(zCmd,"quit")==0 ){
1522 if( !inAuth ){
1523 db_multi_exec(
1524 "UPDATE emailbox SET estate=2"
1525 " WHERE estate<2 AND ebid IN (SELECT ebid FROM pop3 WHERE isDel);"
1526 );
1527 }
1528 pop3_print(pLog, "+OK");
1529 break;
1530 }
1531 if( strcmp(zCmd,"capa")==0 ){
1532 static const char *const azCap[] = {
1533 "TOP", "USER", "UIDL",
1534 };
1535 int i;
1536 pop3_print(pLog, "+OK");
1537 for(i=0; i<sizeof(azCap)/sizeof(azCap[0]); i++){
1538 pop3_print(pLog, "%s", azCap[i]);
1539 }
1540 pop3_print(pLog, ".");
1541 continue;
1542 }
1543 if( inAuth ){
1544 if( strcmp(zCmd,"user")==0 ){
1545 if( zA1==0 || zA2!=0 ) goto cmd_error;
1546 sqlite3_snprintf(sizeof(zUser),zUser,"%s",zA1);
1547 goto cmd_ok;
1548 }
1549 if( strcmp(zCmd,"pass")==0 ){
1550 if( zA1==0 || zA2!=0 ) goto cmd_error;
1551 if( pop3_login(zUser,zA1)==0 ){
1552 goto cmd_error;
1553 }else{
1554 inAuth = 0;
1555 db_multi_exec(
1556 "CREATE TEMP TABLE pop3("
1557 " id INTEGER PRIMARY KEY,"
1558 " emailid INT,"
1559 " ebid INT,"
1560 " isDel INT,"
1561 " esz INT"
1562 ");"
1563 "INSERT INTO pop3(id,emailid,ebid,isDel,esz)"
1564 " SELECT NULL, emailid, ebid, 0, esz FROM emailblob, emailbox"
1565 " WHERE emailid=emsgid AND euser=%Q AND estate<=1"
1566 " ORDER BY edate;",
1567 zUser
1568 );
1569 goto cmd_ok;
1570 }
1571 }
1572 /* Fossil cannot process APOP since the users clear-text password is
1573 ** unknown. */
1574 goto cmd_error;
1575 }else{
1576 if( strcmp(zCmd,"stat")==0 ){
1577 db_prepare(&q, "SELECT count(*), sum(esz) FROM pop3 WHERE NOT isDel");
1578 if( db_step(&q)==SQLITE_ROW ){
1579 pop3_print(pLog, "+OK %d %d",
1580 db_column_int(&q,0), db_column_int(&q,1));
1581 }else{
1582 pop3_print(pLog,"-ERR");
1583 }
1584 db_finalize(&q);
1585 continue;
1586 }
1587 if( strcmp(zCmd,"list")==0 ){
1588 if( zA1 ){
1589 db_prepare(&q, "SELECT id, esz FROM pop3"
1590 " WHERE id=%d AND NOT isDel", atoi(zA1));
1591 if( db_step(&q)==SQLITE_ROW ){
1592 pop3_print(pLog, "+OK %d %d",
1593 db_column_int(&q,0), db_column_int(&q,1));
1594 }else{
1595 pop3_print(pLog, "-ERR");
1596 }
1597 }else{
1598 pop3_print(pLog, "+OK");
1599 db_prepare(&q, "SELECT id, esz FROM pop3 WHERE NOT isDel");
1600 while( db_step(&q)==SQLITE_ROW ){
1601 pop3_print(pLog, "%d %d",
1602 db_column_int(&q,0), db_column_int(&q,1));
1603 }
1604 pop3_print(pLog, ".");
1605 }
1606 db_finalize(&q);
1607 continue;
1608 }
1609 if( strcmp(zCmd,"retr")==0 || strcmp(zCmd,"top")==0 ){
1610 Blob all, line;
1611 int nLine = 0;
1612 int iLimit;
1613 int hdrPending = 1;
1614 if( zA1==0 ) goto cmd_error;
1615 iLimit = zA2 ? atoi(zA2) : 2147483647;
1616 if( iLimit<0 ) goto cmd_error;
1617 z = db_text(0, "SELECT decompress(emailblob.etxt) "
1618 " FROM emailblob, pop3"
1619 " WHERE emailblob.emailid=pop3.emailid"
1620 " AND pop3.id=%d AND NOT pop3.isDel",
1621 atoi(zA1));
1622 if( z==0 ) goto cmd_error;
1623 pop3_print(pLog, "+OK");
1624 blob_init(&all, z, -1);
1625 while( (hdrPending || iLimit>0) && blob_line(&all, &line) ){
1626 char c = blob_buffer(&line)[0];
1627 if( c=='.' ){
1628 fputc('.', stdout);
1629 }else if( c=='\r' || c=='\n' ){
1630 hdrPending = 0;
1631 }
1632 fwrite(blob_buffer(&line), 1, blob_size(&line), stdout);
1633 nLine++;
1634 if( !hdrPending ) iLimit--;
1635 }
1636 if( pLog ) fprintf(pLog, "S: # %d lines of content\n", nLine);
1637 pop3_print(pLog, ".");
1638 fossil_free(z);
1639 blob_reset(&all);
1640 blob_reset(&line);
1641 fflush(stdout);
1642 continue;
1643 }
1644 if( strcmp(zCmd,"dele")==0 ){
1645 if( zA1==0 ) goto cmd_error;
1646 db_multi_exec("UPDATE pop3 SET isDel=1 WHERE id=%d",atoi(zA1));
1647 goto cmd_ok;
1648 }
1649 if( strcmp(zCmd,"rset")==0 ){
1650 db_multi_exec("UPDATE pop3 SET isDel=0");
1651 goto cmd_ok;
1652 }
1653 if( strcmp(zCmd,"uidl")==0 ){
1654 if( zA1 ){
1655 db_prepare(&q, "SELECT id, emailid FROM pop3"
1656 " WHERE id=%d AND NOT isDel", atoi(zA1));
1657 if( db_step(&q)==SQLITE_ROW ){
1658 pop3_print(pLog, "+OK %d %d",
1659 db_column_int(&q,0), db_column_int(&q,1));
1660 }else{
1661 pop3_print(pLog,"-ERR");
1662 }
1663 }else{
1664 pop3_print(pLog, "+OK");
1665 db_prepare(&q, "SELECT id, emailid FROM pop3 WHERE NOT isDel");
1666 while( db_step(&q)==SQLITE_ROW ){
1667 pop3_print(pLog, "%d %d",
1668 db_column_int(&q,0), db_column_int(&q,1));
1669 }
1670 pop3_print(pLog, ".");
1671 }
1672 db_finalize(&q);
1673 continue;
1674 }
1675 if( strcmp(zCmd,"noop")==0 ){
1676 goto cmd_ok;
1677 }
1678 /* Else, fall through into cmd_error */
1679 }
1680 cmd_error:
1681 pop3_print(pLog, "-ERR");
1682 continue;
1683 cmd_ok:
1684 pop3_print(pLog, "+OK");
1685 continue;
1686 }
1687 if( pLog ) fclose(pLog);
1688 }
1689
--- src/smtp.c
+++ src/smtp.c
@@ -646,1043 +646,5 @@
646 fossil_fatal("ERROR: %s\n", p->zErr);
647 }
648 smtp_session_free(p);
649 blob_reset(&body);
650 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
651

Keyboard Shortcuts

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