Fossil SCM

Lots of additional error checking on the "fossil smtpd" input.

drh 2018-06-29 21:37 UTC smtp
Commit cf1c84299ff071c85a5e40357c96c88e569a8231f34762717d65cce43b8d3880
1 file changed +80 -10
+80 -10
--- src/smtp.c
+++ src/smtp.c
@@ -688,10 +688,11 @@
688688
689689
#define SMTPSRV_CLEAR_MSG 1 /* smtp_server_clear() last message only */
690690
#define SMTPSRV_CLEAR_ALL 2 /* smtp_server_clear() everything */
691691
#define SMTPSRV_LOG 0x001 /* Record a transcript of the interaction */
692692
#define SMTPSRV_STDERR 0x002 /* Transcription written to stderr */
693
+#define SMTPSRV_DRYRUN 0x004 /* Do not record anything in database */
693694
694695
#endif /* LOCAL_INTERFACE */
695696
696697
/*
697698
** Clear the SmtpServer object. Deallocate resources.
@@ -873,11 +874,15 @@
873874
** Add this email to the database.
874875
*/
875876
static void smtp_server_route_incoming(SmtpServer *p, int bFinish){
876877
Stmt s;
877878
int i, j;
878
- if( p->zFrom && p->nTo && blob_size(&p->msg) ){
879
+ if( p->zFrom
880
+ && p->nTo
881
+ && blob_size(&p->msg)
882
+ && (p->srvrFlags & SMTPSRV_DRYRUN)==0
883
+ ){
879884
db_begin_transaction();
880885
if( p->idTranscript==0 ) smtp_server_schema(0);
881886
db_prepare(&s,
882887
"INSERT INTO emailblob(ets,etime,etxt)"
883888
" VALUES(:ets,now(),:etxt)"
@@ -920,25 +925,72 @@
920925
}
921926
922927
/*
923928
** Make a copy of the input string up to but not including the
924929
** first ">" character.
930
+**
931
+** Verify that the string really that is to be copied really is a
932
+** valid email address. If it is not, then return NULL.
933
+**
934
+** This routine is more restrictive than necessary. It does not
935
+** allow comments, IP address, quoted strings, or certain uncommon
936
+** characters. The only non-alphanumerics allowed in the local
937
+** part are "_", "+", "-" and "+".
925938
*/
926939
static char *extractEmail(const char *z){
927940
int i;
928
- for(i=0; z[i] && z[i]!='>'; i++){}
941
+ int nAt = 0;
942
+ int nDot = 0;
943
+ char c;
944
+ if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */
945
+ for(i=0; (c = z[i])!=0 && c!='>'; i++){
946
+ if( fossil_isalnum(c) ){
947
+ /* Alphanumerics are always ok */
948
+ }else if( c=='@' ){
949
+ if( nAt ) return 0; /* Only a single "@" allowed */
950
+ if( i>64 ) return 0; /* Local part too big */
951
+ nAt = 1;
952
+ nDot = 0;
953
+ if( i==0 ) return 0; /* Disallow empty local part */
954
+ if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */
955
+ if( z[i+1]=='.' || z[i+1]=='-' ){
956
+ return 0; /* Domain cannot begin with "." or "-" */
957
+ }
958
+ }else if( c=='-' ){
959
+ if( z[i+1]=='>' ) return 0; /* Last character cannot be "-" */
960
+ }else if( c=='.' ){
961
+ if( z[i+1]=='.' ) return 0; /* Do not allow ".." */
962
+ if( z[i+1]=='>' ) return 0; /* Domain may not end with . */
963
+ nDot++;
964
+ }else if( (c=='_' || c=='+') && nAt==0 ){
965
+ /* _ and + are ok in the local part */
966
+ }else{
967
+ return 0; /* Anything else is an error */
968
+ }
969
+ }
970
+ if( c!='>' ) return 0; /* Missing final ">" */
971
+ if( nAt==0 ) return 0; /* No "@" found anywhere */
972
+ if( nDot==0 ) return 0; /* No "." in the domain */
973
+
974
+ /* If we reach this point, the email address is valid */
929975
return mprintf("%.*s", i, z);
930976
}
931977
932978
/*
933979
** COMMAND: smtpd
934980
**
935981
** Usage: %fossil smtpd [OPTIONS] REPOSITORY
936982
**
937983
** Begin a SMTP conversation with a client using stdin/stdout. The
938
-** received email is stored in REPOSITORY
984
+** received email is stored in REPOSITORY.
985
+**
986
+** Options:
987
+**
988
+** --dryrun Do not record any emails in the database
939989
**
990
+** --trace Print a transcript of the conversation on stderr
991
+** for debugging and analysis
940992
*/
941993
void smtp_server(void){
942994
char *zDbName;
943995
const char *zDomain;
944996
SmtpServer x;
@@ -947,50 +999,68 @@
947999
smtp_server_init(&x);
9481000
zDomain = find_option("domain",0,1);
9491001
if( zDomain==0 ) zDomain = "";
9501002
x.srvrFlags = SMTPSRV_LOG;
9511003
if( find_option("trace",0,0)!=0 ) x.srvrFlags |= SMTPSRV_STDERR;
1004
+ if( find_option("dryrun",0,0)!=0 ) x.srvrFlags |= SMTPSRV_DRYRUN;
9521005
verify_all_options();
9531006
if( g.argc!=3 ) usage("DBNAME");
9541007
zDbName = g.argv[2];
9551008
zDbName = enter_chroot_jail(zDbName, 0);
9561009
db_open_repository(zDbName);
9571010
smtp_server_send(&x, "220 %s ESMTP https://fossil-scm.org/ %s\r\n",
9581011
zDomain, MANIFEST_VERSION);
9591012
while( smtp_server_gets(&x, z, sizeof(z)) ){
960
- if( strncmp(z, "EHLO ", 5)==0 ){
1013
+ if( strncmp(z, "EHLO", 4)==0 && fossil_isspace(z[4]) ){
9611014
smtp_server_send(&x, "250 ok\r\n");
9621015
}else
963
- if( strncmp(z, "HELO ", 5)==0 ){
1016
+ if( strncmp(z, "HELO", 4)==0 && fossil_isspace(z[4]) ){
9641017
smtp_server_send(&x, "250 ok\r\n");
9651018
}else
9661019
if( strncmp(z, "MAIL FROM:<", 11)==0 ){
9671020
smtp_server_route_incoming(&x, 0);
9681021
smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
9691022
x.zFrom = extractEmail(z+11);
970
- smtp_server_send(&x, "250 ok\r\n");
1023
+ if( x.zFrom==0 ){
1024
+ smtp_server_send(&x, "500 unacceptable email address\r\n");
1025
+ }else{
1026
+ smtp_server_send(&x, "250 ok\r\n");
1027
+ }
9711028
}else
9721029
if( strncmp(z, "RCPT TO:<", 9)==0 ){
973
- char *zAddr = extractEmail(z+9);
1030
+ char *zAddr;
1031
+ if( x.zFrom==0 ){
1032
+ smtp_server_send(&x, "500 missing MAIL FROM\r\n");
1033
+ continue;
1034
+ }
1035
+ zAddr = extractEmail(z+9);
1036
+ if( zAddr==0 ){
1037
+ smtp_server_send(&x, "505 no such user\r\n");
1038
+ continue;
1039
+ }
9741040
smtp_append_to(&x, zAddr, 0);
9751041
if( x.nTo>=100 ){
9761042
smtp_server_send(&x, "452 too many recipients\r\n");
9771043
continue;
9781044
}
9791045
smtp_server_send(&x, "250 ok\r\n");
9801046
}else
981
- if( strncmp(z, "DATA", 4)==0 ){
1047
+ if( strncmp(z, "DATA", 4)==0 && fossil_isspace(z[4]) ){
1048
+ if( x.zFrom==0 || x.nTo==0 ){
1049
+ smtp_server_send(&x, "500 missing RCPT TO\r\n");
1050
+ continue;
1051
+ }
9821052
smtp_server_send(&x, "354 ready\r\n");
9831053
smtp_server_capture_data(&x, z, sizeof(z));
9841054
smtp_server_send(&x, "250 ok\r\n");
9851055
}else
986
- if( strncmp(z, "QUIT", 4)==0 ){
1056
+ if( strncmp(z, "QUIT", 4)==0 && fossil_isspace(z[4]) ){
9871057
smtp_server_send(&x, "221 closing connection\r\n");
1058
+ smtp_server_route_incoming(&x, 1);
9881059
break;
9891060
}else
9901061
{
9911062
smtp_server_send(&x, "500 unknown command\r\n");
9921063
}
9931064
}
994
- smtp_server_route_incoming(&x, 1);
9951065
smtp_server_clear(&x, SMTPSRV_CLEAR_ALL);
9961066
}
9971067
--- src/smtp.c
+++ src/smtp.c
@@ -688,10 +688,11 @@
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 */
692 #define SMTPSRV_STDERR 0x002 /* Transcription written to stderr */
 
693
694 #endif /* LOCAL_INTERFACE */
695
696 /*
697 ** Clear the SmtpServer object. Deallocate resources.
@@ -873,11 +874,15 @@
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)"
@@ -920,25 +925,72 @@
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;
@@ -947,50 +999,68 @@
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");
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
--- src/smtp.c
+++ src/smtp.c
@@ -688,10 +688,11 @@
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 */
692 #define SMTPSRV_STDERR 0x002 /* Transcription written to stderr */
693 #define SMTPSRV_DRYRUN 0x004 /* Do not record anything in database */
694
695 #endif /* LOCAL_INTERFACE */
696
697 /*
698 ** Clear the SmtpServer object. Deallocate resources.
@@ -873,11 +874,15 @@
874 ** Add this email to the database.
875 */
876 static void smtp_server_route_incoming(SmtpServer *p, int bFinish){
877 Stmt s;
878 int i, j;
879 if( p->zFrom
880 && p->nTo
881 && blob_size(&p->msg)
882 && (p->srvrFlags & SMTPSRV_DRYRUN)==0
883 ){
884 db_begin_transaction();
885 if( p->idTranscript==0 ) smtp_server_schema(0);
886 db_prepare(&s,
887 "INSERT INTO emailblob(ets,etime,etxt)"
888 " VALUES(:ets,now(),:etxt)"
@@ -920,25 +925,72 @@
925 }
926
927 /*
928 ** Make a copy of the input string up to but not including the
929 ** first ">" character.
930 **
931 ** Verify that the string really that is to be copied really is a
932 ** valid email address. If it is not, then return NULL.
933 **
934 ** This routine is more restrictive than necessary. It does not
935 ** allow comments, IP address, quoted strings, or certain uncommon
936 ** characters. The only non-alphanumerics allowed in the local
937 ** part are "_", "+", "-" and "+".
938 */
939 static char *extractEmail(const char *z){
940 int i;
941 int nAt = 0;
942 int nDot = 0;
943 char c;
944 if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */
945 for(i=0; (c = z[i])!=0 && c!='>'; i++){
946 if( fossil_isalnum(c) ){
947 /* Alphanumerics are always ok */
948 }else if( c=='@' ){
949 if( nAt ) return 0; /* Only a single "@" allowed */
950 if( i>64 ) return 0; /* Local part too big */
951 nAt = 1;
952 nDot = 0;
953 if( i==0 ) return 0; /* Disallow empty local part */
954 if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */
955 if( z[i+1]=='.' || z[i+1]=='-' ){
956 return 0; /* Domain cannot begin with "." or "-" */
957 }
958 }else if( c=='-' ){
959 if( z[i+1]=='>' ) return 0; /* Last character cannot be "-" */
960 }else if( c=='.' ){
961 if( z[i+1]=='.' ) return 0; /* Do not allow ".." */
962 if( z[i+1]=='>' ) return 0; /* Domain may not end with . */
963 nDot++;
964 }else if( (c=='_' || c=='+') && nAt==0 ){
965 /* _ and + are ok in the local part */
966 }else{
967 return 0; /* Anything else is an error */
968 }
969 }
970 if( c!='>' ) return 0; /* Missing final ">" */
971 if( nAt==0 ) return 0; /* No "@" found anywhere */
972 if( nDot==0 ) return 0; /* No "." in the domain */
973
974 /* If we reach this point, the email address is valid */
975 return mprintf("%.*s", i, z);
976 }
977
978 /*
979 ** COMMAND: smtpd
980 **
981 ** Usage: %fossil smtpd [OPTIONS] REPOSITORY
982 **
983 ** Begin a SMTP conversation with a client using stdin/stdout. The
984 ** received email is stored in REPOSITORY.
985 **
986 ** Options:
987 **
988 ** --dryrun Do not record any emails in the database
989 **
990 ** --trace Print a transcript of the conversation on stderr
991 ** for debugging and analysis
992 */
993 void smtp_server(void){
994 char *zDbName;
995 const char *zDomain;
996 SmtpServer x;
@@ -947,50 +999,68 @@
999 smtp_server_init(&x);
1000 zDomain = find_option("domain",0,1);
1001 if( zDomain==0 ) zDomain = "";
1002 x.srvrFlags = SMTPSRV_LOG;
1003 if( find_option("trace",0,0)!=0 ) x.srvrFlags |= SMTPSRV_STDERR;
1004 if( find_option("dryrun",0,0)!=0 ) x.srvrFlags |= SMTPSRV_DRYRUN;
1005 verify_all_options();
1006 if( g.argc!=3 ) usage("DBNAME");
1007 zDbName = g.argv[2];
1008 zDbName = enter_chroot_jail(zDbName, 0);
1009 db_open_repository(zDbName);
1010 smtp_server_send(&x, "220 %s ESMTP https://fossil-scm.org/ %s\r\n",
1011 zDomain, MANIFEST_VERSION);
1012 while( smtp_server_gets(&x, z, sizeof(z)) ){
1013 if( strncmp(z, "EHLO", 4)==0 && fossil_isspace(z[4]) ){
1014 smtp_server_send(&x, "250 ok\r\n");
1015 }else
1016 if( strncmp(z, "HELO", 4)==0 && fossil_isspace(z[4]) ){
1017 smtp_server_send(&x, "250 ok\r\n");
1018 }else
1019 if( strncmp(z, "MAIL FROM:<", 11)==0 ){
1020 smtp_server_route_incoming(&x, 0);
1021 smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
1022 x.zFrom = extractEmail(z+11);
1023 if( x.zFrom==0 ){
1024 smtp_server_send(&x, "500 unacceptable email address\r\n");
1025 }else{
1026 smtp_server_send(&x, "250 ok\r\n");
1027 }
1028 }else
1029 if( strncmp(z, "RCPT TO:<", 9)==0 ){
1030 char *zAddr;
1031 if( x.zFrom==0 ){
1032 smtp_server_send(&x, "500 missing MAIL FROM\r\n");
1033 continue;
1034 }
1035 zAddr = extractEmail(z+9);
1036 if( zAddr==0 ){
1037 smtp_server_send(&x, "505 no such user\r\n");
1038 continue;
1039 }
1040 smtp_append_to(&x, zAddr, 0);
1041 if( x.nTo>=100 ){
1042 smtp_server_send(&x, "452 too many recipients\r\n");
1043 continue;
1044 }
1045 smtp_server_send(&x, "250 ok\r\n");
1046 }else
1047 if( strncmp(z, "DATA", 4)==0 && fossil_isspace(z[4]) ){
1048 if( x.zFrom==0 || x.nTo==0 ){
1049 smtp_server_send(&x, "500 missing RCPT TO\r\n");
1050 continue;
1051 }
1052 smtp_server_send(&x, "354 ready\r\n");
1053 smtp_server_capture_data(&x, z, sizeof(z));
1054 smtp_server_send(&x, "250 ok\r\n");
1055 }else
1056 if( strncmp(z, "QUIT", 4)==0 && fossil_isspace(z[4]) ){
1057 smtp_server_send(&x, "221 closing connection\r\n");
1058 smtp_server_route_incoming(&x, 1);
1059 break;
1060 }else
1061 {
1062 smtp_server_send(&x, "500 unknown command\r\n");
1063 }
1064 }
 
1065 smtp_server_clear(&x, SMTPSRV_CLEAR_ALL);
1066 }
1067

Keyboard Shortcuts

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