Fossil SCM

Baseline implementation of the "smtp" command.

drh 2018-06-29 03:12 UTC smtp
Commit be55fc60c07438490f94d9b56dd9204ef29258a9a6248f38ab7307e523e32ba3
2 files changed +1 -1 +215 -19
+1 -1
--- src/main.c
+++ src/main.c
@@ -1205,11 +1205,11 @@
12051205
**
12061206
** The noJail flag means that the chroot jail is not entered. But
12071207
** privileges are still lowered to that of the user-id and group-id
12081208
** of the repository file.
12091209
*/
1210
-static char *enter_chroot_jail(char *zRepo, int noJail){
1210
+char *enter_chroot_jail(char *zRepo, int noJail){
12111211
#if !defined(_WIN32)
12121212
if( getuid()==0 ){
12131213
int i;
12141214
struct stat sStat;
12151215
Blob dir;
12161216
--- src/main.c
+++ src/main.c
@@ -1205,11 +1205,11 @@
1205 **
1206 ** The noJail flag means that the chroot jail is not entered. But
1207 ** privileges are still lowered to that of the user-id and group-id
1208 ** of the repository file.
1209 */
1210 static char *enter_chroot_jail(char *zRepo, int noJail){
1211 #if !defined(_WIN32)
1212 if( getuid()==0 ){
1213 int i;
1214 struct stat sStat;
1215 Blob dir;
1216
--- src/main.c
+++ src/main.c
@@ -1205,11 +1205,11 @@
1205 **
1206 ** The noJail flag means that the chroot jail is not entered. But
1207 ** privileges are still lowered to that of the user-id and group-id
1208 ** of the repository file.
1209 */
1210 char *enter_chroot_jail(char *zRepo, int noJail){
1211 #if !defined(_WIN32)
1212 if( getuid()==0 ){
1213 int i;
1214 struct stat sStat;
1215 Blob dir;
1216
+215 -19
--- src/smtp.c
+++ src/smtp.c
@@ -116,20 +116,21 @@
116116
const char *zDest; /* Domain that will receive the email */
117117
char *zHostname; /* Hostname of SMTP server for zDest */
118118
u32 smtpFlags; /* Flags changing the operation */
119119
FILE *logFile; /* Write session transcript to this log file */
120120
Blob *pTranscript; /* Record session transcript here */
121
- const char *zLabel; /* Either "CS" or "SC" */
122121
int atEof; /* True after connection closes */
123122
char *zErr; /* Error message */
124123
Blob inbuf; /* Input buffer */
125124
};
126125
127126
/* Allowed values for SmtpSession.smtpFlags */
128127
#define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */
129128
#define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */
130129
#define SMTP_TRACE_BLOB 0x00004 /* Record transcript */
130
+#define SMTP_DIRECT 0x00008 /* Skip the MX lookup */
131
+#define SMTP_PORT 0x00010 /* Use an alternate port number */
131132
132133
#endif
133134
134135
/*
135136
** Shutdown an SmtpSession
@@ -143,16 +144,21 @@
143144
}
144145
145146
/*
146147
** Allocate a new SmtpSession object.
147148
**
148
-** Both zFrom and zDest must be specified for a client side SMTP connection.
149
-** For a server-side, specify only zFrom.
149
+** Both zFrom and zDest must be specified.
150
+**
151
+** The ... arguments are in this order:
152
+**
153
+** SMTP_PORT: int
154
+** SMTP_TRACE_FILE: FILE*
155
+** SMTP_TRACE_BLOB: Blob*
150156
*/
151157
SmtpSession *smtp_session_new(
152
- const char *zFrom, /* Domain name of our end. */
153
- const char *zDest, /* Domain of the other end. */
158
+ const char *zFrom, /* Domain for the client */
159
+ const char *zDest, /* Domain of the server */
154160
u32 smtpFlags, /* Flags */
155161
... /* Arguments depending on the flags */
156162
){
157163
SmtpSession *p;
158164
va_list ap;
@@ -160,29 +166,36 @@
160166
161167
p = fossil_malloc( sizeof(*p) );
162168
memset(p, 0, sizeof(*p));
163169
p->zFrom = zFrom;
164170
p->zDest = zDest;
165
- p->zLabel = zDest==0 ? "CS" : "SC";
166171
p->smtpFlags = smtpFlags;
172
+ memset(&url, 0, sizeof(url));
173
+ url.port = 25;
167174
blob_init(&p->inbuf, 0, 0);
168175
va_start(ap, smtpFlags);
176
+ if( smtpFlags & SMTP_PORT ){
177
+ url.port = va_arg(ap, int);
178
+ }
169179
if( smtpFlags & SMTP_TRACE_FILE ){
170180
p->logFile = va_arg(ap, FILE*);
171
- }else if( smtpFlags & SMTP_TRACE_BLOB ){
181
+ }
182
+ if( smtpFlags & SMTP_TRACE_BLOB ){
172183
p->pTranscript = va_arg(ap, Blob*);
173184
}
174185
va_end(ap);
175
- p->zHostname = smtp_mx_host(zDest);
186
+ if( (smtpFlags & SMTP_DIRECT)!=0 ){
187
+ p->zHostname = fossil_strdup(zDest);
188
+ }else{
189
+ p->zHostname = smtp_mx_host(zDest);
190
+ }
176191
if( p->zHostname==0 ){
177192
p->atEof = 1;
178193
p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest);
179194
return p;
180195
}
181
- memset(&url, 0, sizeof(url));
182196
url.name = p->zHostname;
183
- url.port = 25;
184197
socket_global_init();
185198
if( socket_open(&url) ){
186199
p->atEof = 1;
187200
p->zErr = socket_errmsg();
188201
socket_close();
@@ -206,17 +219,17 @@
206219
n = blob_size(&b);
207220
assert( n>=2 );
208221
assert( z[n-1]=='\n' );
209222
assert( z[n-2]=='\r' );
210223
if( p->smtpFlags & SMTP_TRACE_STDOUT ){
211
- fossil_print("%c: %.*s\n", p->zLabel[1], n-2, z);
224
+ fossil_print("C: %.*s\n", n-2, z);
212225
}
213226
if( p->smtpFlags & SMTP_TRACE_FILE ){
214
- fprintf(p->logFile, "%c: %.*s\n", p->zLabel[1], n-2, z);
227
+ fprintf(p->logFile, "C: %.*s\n", n-2, z);
215228
}
216229
if( p->smtpFlags & SMTP_TRACE_BLOB ){
217
- blob_appendf(p->pTranscript, "%c: %.*s\n", p->zLabel[1], n-2, z);
230
+ blob_appendf(p->pTranscript, "C: %.*s\n", n-2, z);
218231
}
219232
socket_send(0, z, n);
220233
blob_zero(&b);
221234
}
222235
@@ -271,17 +284,17 @@
271284
z = blob_buffer(in);
272285
n = blob_size(in);
273286
if( n && z[n-1]=='\n' ) n--;
274287
if( n && z[n-1]=='\r' ) n--;
275288
if( p->smtpFlags & SMTP_TRACE_STDOUT ){
276
- fossil_print("%c: %.*s\n", p->zLabel[0], n, z);
289
+ fossil_print("S: %.*s\n", n, z);
277290
}
278291
if( p->smtpFlags & SMTP_TRACE_FILE ){
279
- fprintf(p->logFile, "%c: %.*s\n", p->zLabel[0], n, z);
292
+ fprintf(p->logFile, "S: %.*s\n", n, z);
280293
}
281294
if( p->smtpFlags & SMTP_TRACE_BLOB ){
282
- blob_appendf(p->pTranscript, "%c: %.*s\n", p->zLabel[0], n-2, z);
295
+ blob_appendf(p->pTranscript, "S: %.*s\n", n-2, z);
283296
}
284297
}
285298
286299
/*
287300
** Capture a single-line server reply.
@@ -362,19 +375,32 @@
362375
** Usage: %fossil test-smtp-probe DOMAIN [ME]
363376
**
364377
** Interact with the SMTP server for DOMAIN by setting up a connection
365378
** and then immediately shutting it back down. Log all interaction
366379
** on the console. Use ME as the domain name of the sender.
380
+**
381
+** Options:
382
+**
383
+** --direct Use DOMAIN directly without going through MX
384
+** --port N Talk on TCP port N
367385
*/
368386
void test_smtp_probe(void){
369387
SmtpSession *p;
370388
const char *zDomain;
371389
const char *zSelf;
390
+ const char *zPort;
391
+ int iPort = 25;
392
+ u32 smtpFlags = SMTP_TRACE_STDOUT|SMTP_PORT;
393
+
394
+ if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
395
+ zPort = find_option("port",0,1);
396
+ if( zPort ) iPort = atoi(zPort);
397
+ verify_all_options();
372398
if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]");
373399
zDomain = g.argv[2];
374400
zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org";
375
- p = smtp_session_new(zSelf, zDomain, SMTP_TRACE_STDOUT);
401
+ p = smtp_session_new(zSelf, zDomain, smtpFlags, iPort);
376402
if( p->zErr ){
377403
fossil_fatal("%s", p->zErr);
378404
}
379405
fossil_print("Connection to \"%s\"\n", p->zHostname);
380406
smtp_client_startup(p);
@@ -523,31 +549,38 @@
523549
** Use SMTP to send the email message contained in the file named EMAIL
524550
** to the list of users TO. FROM is the sender of the email.
525551
**
526552
** Options:
527553
**
554
+** --direct Go directly to the TO domain. Bypass MX lookup
555
+** --port N Use TCP port N instead of 25
528556
** --trace Show the SMTP conversation on the console
529557
*/
530558
void test_smtp_send(void){
531559
SmtpSession *p;
532560
const char *zFrom;
533561
int nTo;
534562
const char *zToDomain;
535563
const char *zFromDomain;
536564
const char **azTo;
565
+ int smtpPort = 25;
566
+ const char *zPort;
537567
Blob body;
538
- u32 smtpFlags = 0;
568
+ u32 smtpFlags = SMTP_PORT;
539569
if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
570
+ if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
571
+ zPort = find_option("port",0,1);
572
+ if( zPort ) smtpPort = atoi(zPort);
540573
verify_all_options();
541574
if( g.argc<5 ) usage("EMAIL FROM TO ...");
542575
blob_read_from_file(&body, g.argv[2], ExtFILE);
543576
zFrom = g.argv[3];
544577
nTo = g.argc-4;
545578
azTo = (const char**)g.argv+4;
546579
zFromDomain = domainOfAddr(zFrom);
547580
zToDomain = domainOfAddr(azTo[0]);
548
- p = smtp_session_new(zFromDomain, zToDomain, smtpFlags);
581
+ p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
549582
if( p->zErr ){
550583
fossil_fatal("%s", p->zErr);
551584
}
552585
fossil_print("Connection to \"%s\"\n", p->zHostname);
553586
smtp_client_startup(p);
@@ -557,5 +590,168 @@
557590
fossil_fatal("ERROR: %s\n", p->zErr);
558591
}
559592
smtp_session_free(p);
560593
blob_zero(&body);
561594
}
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 */
618
+
619
+#endif /* LOCAL_INTERFACE */
620
+
621
+/*
622
+** Clear the SmtpServer object. Deallocate resources.
623
+** How much to clear depends on eHowMuch
624
+*/
625
+static void smtp_server_clear(SmtpServer *p, int eHowMuch){
626
+ int i;
627
+ if( eHowMuch>=SMTPSRV_CLEAR_MSG ){
628
+ fossil_free(p->zFrom);
629
+ p->zFrom = 0;
630
+ for(i=0; i<p->nTo; i++) fossil_free(p->azTo[0]);
631
+ fossil_free(p->azTo);
632
+ p->azTo = 0;
633
+ p->nTo = 0;
634
+ blob_zero(&p->msg);
635
+ }
636
+ if( eHowMuch>=SMTPSRV_CLEAR_ALL ){
637
+ blob_zero(&p->transcript);
638
+ sqlite3_close(p->db);
639
+ p->db = 0;
640
+ fossil_free(p->zEhlo);
641
+ p->zEhlo = 0;
642
+ }
643
+}
644
+
645
+/*
646
+** Turn raw memory into an SmtpServer object.
647
+*/
648
+static void smtp_server_init(SmtpServer *p){
649
+ memset(p, 0, sizeof(*p));
650
+ blob_init(&p->msg, 0, 0);
651
+ blob_init(&p->transcript, 0, 0);
652
+}
653
+
654
+/*
655
+** Send a single line of output from the server to the client.
656
+*/
657
+static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){
658
+ Blob b = empty_blob;
659
+ va_list ap;
660
+ char *z;
661
+ int n;
662
+ va_start(ap, zFormat);
663
+ blob_vappendf(&b, zFormat, ap);
664
+ va_end(ap);
665
+ z = blob_buffer(&b);
666
+ n = blob_size(&b);
667
+ assert( n>=2 );
668
+ assert( z[n-1]=='\n' );
669
+ assert( z[n-2]=='\r' );
670
+ if( p->srvrFlags & SMTPSRV_LOG ){
671
+ blob_appendf(&p->transcript, "S: %.*s\n", n-2, z);
672
+ }
673
+ fwrite(z, n, 1, stdout);
674
+ fflush(stdout);
675
+ blob_zero(&b);
676
+}
677
+
678
+/*
679
+** Read a single line from the client.
680
+*/
681
+static int smtp_server_gets(SmtpServer *p, char *aBuf, int nBuf){
682
+ int rc = fgets(aBuf, nBuf, stdin)!=0;
683
+ if( rc && (p->srvrFlags & SMTPSRV_LOG)!=0 ){
684
+ blob_appendf(&p->transcript, "C: %s\n", aBuf);
685
+ }
686
+ return rc;
687
+}
688
+
689
+/*
690
+** Capture the incoming email data into the p->msg blob. Dequote
691
+** lines of "..\r\n" into just ".\r\n".
692
+*/
693
+static void smtp_server_capture_data(SmtpServer *p, char *z, int n){
694
+ while( fgets(z, n, stdin) ){
695
+ if( strncmp(z, ".\r\n", 3)==0 ) return;
696
+ if( strncmp(z, "..\r\n", 4)==0 ){
697
+ memmove(z, z+1, 4);
698
+ }
699
+ blob_append(&p->msg, z, -1);
700
+ }
701
+}
702
+
703
+/*
704
+** COMMAND: smtp
705
+**
706
+** Usage: %fossil smtp [options] DBNAME
707
+**
708
+** Begin a SMTP conversation with a client using stdin/stdout. (This
709
+** command is expected to be launched from xinetd or the equivalent.)
710
+** Use information in the SQLite database at DBNAME to find configuration
711
+** information and as a place to store the incoming content.
712
+*/
713
+void smtp_server(void){
714
+ char *zDbName;
715
+ const char *zDomain;
716
+ SmtpServer x;
717
+ char z[5000];
718
+
719
+ smtp_server_init(&x);
720
+ zDomain = find_option("domain",0,1);
721
+ if( zDomain==0 ) zDomain = "unspecified.domain";
722
+ verify_all_options();
723
+ if( g.argc!=3 ) usage("DBNAME");
724
+ zDbName = g.argv[2];
725
+ zDbName = enter_chroot_jail(zDbName, 0);
726
+ smtp_server_send(&x, "220 %s ESMTP Fossil ([%.*s] %s)\r\n",
727
+ zDomain, 16, MANIFEST_VERSION, MANIFEST_DATE);
728
+ while( smtp_server_gets(&x, z, sizeof(z)) ){
729
+ if( strncmp(z, "EHLO ", 5)==0 ){
730
+ smtp_server_send(&x, "250 ok\r\n");
731
+ }else
732
+ if( strncmp(z, "HELO ", 5)==0 ){
733
+ smtp_server_send(&x, "250 ok\r\n");
734
+ }else
735
+ if( strncmp(z, "MAIL FROM:<", 11)==0 ){
736
+ smtp_server_send(&x, "250 ok\r\n");
737
+ }else
738
+ if( strncmp(z, "RCPT TO:<", 9)==0 ){
739
+ smtp_server_send(&x, "250 ok\r\n");
740
+ }else
741
+ if( strncmp(z, "DATA", 4)==0 ){
742
+ smtp_server_send(&x, "354 ready\r\n");
743
+ smtp_server_capture_data(&x, z, sizeof(z));
744
+ smtp_server_send(&x, "250 ok\r\n");
745
+ smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
746
+ }else
747
+ if( strncmp(z, "QUIT", 4)==0 ){
748
+ smtp_server_send(&x, "221 closing connection\r\n");
749
+ break;
750
+ }else
751
+ {
752
+ smtp_server_send(&x, "500 unknown command\r\n");
753
+ }
754
+ }
755
+ fclose(stdin);
756
+ smtp_server_clear(&x, SMTPSRV_CLEAR_ALL);
757
+}
562758
--- src/smtp.c
+++ src/smtp.c
@@ -116,20 +116,21 @@
116 const char *zDest; /* Domain that will receive the email */
117 char *zHostname; /* Hostname of SMTP server for zDest */
118 u32 smtpFlags; /* Flags changing the operation */
119 FILE *logFile; /* Write session transcript to this log file */
120 Blob *pTranscript; /* Record session transcript here */
121 const char *zLabel; /* Either "CS" or "SC" */
122 int atEof; /* True after connection closes */
123 char *zErr; /* Error message */
124 Blob inbuf; /* Input buffer */
125 };
126
127 /* Allowed values for SmtpSession.smtpFlags */
128 #define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */
129 #define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */
130 #define SMTP_TRACE_BLOB 0x00004 /* Record transcript */
 
 
131
132 #endif
133
134 /*
135 ** Shutdown an SmtpSession
@@ -143,16 +144,21 @@
143 }
144
145 /*
146 ** Allocate a new SmtpSession object.
147 **
148 ** Both zFrom and zDest must be specified for a client side SMTP connection.
149 ** For a server-side, specify only zFrom.
 
 
 
 
 
150 */
151 SmtpSession *smtp_session_new(
152 const char *zFrom, /* Domain name of our end. */
153 const char *zDest, /* Domain of the other end. */
154 u32 smtpFlags, /* Flags */
155 ... /* Arguments depending on the flags */
156 ){
157 SmtpSession *p;
158 va_list ap;
@@ -160,29 +166,36 @@
160
161 p = fossil_malloc( sizeof(*p) );
162 memset(p, 0, sizeof(*p));
163 p->zFrom = zFrom;
164 p->zDest = zDest;
165 p->zLabel = zDest==0 ? "CS" : "SC";
166 p->smtpFlags = smtpFlags;
 
 
167 blob_init(&p->inbuf, 0, 0);
168 va_start(ap, smtpFlags);
 
 
 
169 if( smtpFlags & SMTP_TRACE_FILE ){
170 p->logFile = va_arg(ap, FILE*);
171 }else if( smtpFlags & SMTP_TRACE_BLOB ){
 
172 p->pTranscript = va_arg(ap, Blob*);
173 }
174 va_end(ap);
175 p->zHostname = smtp_mx_host(zDest);
 
 
 
 
176 if( p->zHostname==0 ){
177 p->atEof = 1;
178 p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest);
179 return p;
180 }
181 memset(&url, 0, sizeof(url));
182 url.name = p->zHostname;
183 url.port = 25;
184 socket_global_init();
185 if( socket_open(&url) ){
186 p->atEof = 1;
187 p->zErr = socket_errmsg();
188 socket_close();
@@ -206,17 +219,17 @@
206 n = blob_size(&b);
207 assert( n>=2 );
208 assert( z[n-1]=='\n' );
209 assert( z[n-2]=='\r' );
210 if( p->smtpFlags & SMTP_TRACE_STDOUT ){
211 fossil_print("%c: %.*s\n", p->zLabel[1], n-2, z);
212 }
213 if( p->smtpFlags & SMTP_TRACE_FILE ){
214 fprintf(p->logFile, "%c: %.*s\n", p->zLabel[1], n-2, z);
215 }
216 if( p->smtpFlags & SMTP_TRACE_BLOB ){
217 blob_appendf(p->pTranscript, "%c: %.*s\n", p->zLabel[1], n-2, z);
218 }
219 socket_send(0, z, n);
220 blob_zero(&b);
221 }
222
@@ -271,17 +284,17 @@
271 z = blob_buffer(in);
272 n = blob_size(in);
273 if( n && z[n-1]=='\n' ) n--;
274 if( n && z[n-1]=='\r' ) n--;
275 if( p->smtpFlags & SMTP_TRACE_STDOUT ){
276 fossil_print("%c: %.*s\n", p->zLabel[0], n, z);
277 }
278 if( p->smtpFlags & SMTP_TRACE_FILE ){
279 fprintf(p->logFile, "%c: %.*s\n", p->zLabel[0], n, z);
280 }
281 if( p->smtpFlags & SMTP_TRACE_BLOB ){
282 blob_appendf(p->pTranscript, "%c: %.*s\n", p->zLabel[0], n-2, z);
283 }
284 }
285
286 /*
287 ** Capture a single-line server reply.
@@ -362,19 +375,32 @@
362 ** Usage: %fossil test-smtp-probe DOMAIN [ME]
363 **
364 ** Interact with the SMTP server for DOMAIN by setting up a connection
365 ** and then immediately shutting it back down. Log all interaction
366 ** on the console. Use ME as the domain name of the sender.
 
 
 
 
 
367 */
368 void test_smtp_probe(void){
369 SmtpSession *p;
370 const char *zDomain;
371 const char *zSelf;
 
 
 
 
 
 
 
 
372 if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]");
373 zDomain = g.argv[2];
374 zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org";
375 p = smtp_session_new(zSelf, zDomain, SMTP_TRACE_STDOUT);
376 if( p->zErr ){
377 fossil_fatal("%s", p->zErr);
378 }
379 fossil_print("Connection to \"%s\"\n", p->zHostname);
380 smtp_client_startup(p);
@@ -523,31 +549,38 @@
523 ** Use SMTP to send the email message contained in the file named EMAIL
524 ** to the list of users TO. FROM is the sender of the email.
525 **
526 ** Options:
527 **
 
 
528 ** --trace Show the SMTP conversation on the console
529 */
530 void test_smtp_send(void){
531 SmtpSession *p;
532 const char *zFrom;
533 int nTo;
534 const char *zToDomain;
535 const char *zFromDomain;
536 const char **azTo;
 
 
537 Blob body;
538 u32 smtpFlags = 0;
539 if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
 
 
 
540 verify_all_options();
541 if( g.argc<5 ) usage("EMAIL FROM TO ...");
542 blob_read_from_file(&body, g.argv[2], ExtFILE);
543 zFrom = g.argv[3];
544 nTo = g.argc-4;
545 azTo = (const char**)g.argv+4;
546 zFromDomain = domainOfAddr(zFrom);
547 zToDomain = domainOfAddr(azTo[0]);
548 p = smtp_session_new(zFromDomain, zToDomain, smtpFlags);
549 if( p->zErr ){
550 fossil_fatal("%s", p->zErr);
551 }
552 fossil_print("Connection to \"%s\"\n", p->zHostname);
553 smtp_client_startup(p);
@@ -557,5 +590,168 @@
557 fossil_fatal("ERROR: %s\n", p->zErr);
558 }
559 smtp_session_free(p);
560 blob_zero(&body);
561 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
562
--- src/smtp.c
+++ src/smtp.c
@@ -116,20 +116,21 @@
116 const char *zDest; /* Domain that will receive the email */
117 char *zHostname; /* Hostname of SMTP server for zDest */
118 u32 smtpFlags; /* Flags changing the operation */
119 FILE *logFile; /* Write session transcript to this log file */
120 Blob *pTranscript; /* Record session transcript here */
 
121 int atEof; /* True after connection closes */
122 char *zErr; /* Error message */
123 Blob inbuf; /* Input buffer */
124 };
125
126 /* Allowed values for SmtpSession.smtpFlags */
127 #define SMTP_TRACE_STDOUT 0x00001 /* Debugging info to console */
128 #define SMTP_TRACE_FILE 0x00002 /* Debugging info to logFile */
129 #define SMTP_TRACE_BLOB 0x00004 /* Record transcript */
130 #define SMTP_DIRECT 0x00008 /* Skip the MX lookup */
131 #define SMTP_PORT 0x00010 /* Use an alternate port number */
132
133 #endif
134
135 /*
136 ** Shutdown an SmtpSession
@@ -143,16 +144,21 @@
144 }
145
146 /*
147 ** Allocate a new SmtpSession object.
148 **
149 ** Both zFrom and zDest must be specified.
150 **
151 ** The ... arguments are in this order:
152 **
153 ** SMTP_PORT: int
154 ** SMTP_TRACE_FILE: FILE*
155 ** SMTP_TRACE_BLOB: Blob*
156 */
157 SmtpSession *smtp_session_new(
158 const char *zFrom, /* Domain for the client */
159 const char *zDest, /* Domain of the server */
160 u32 smtpFlags, /* Flags */
161 ... /* Arguments depending on the flags */
162 ){
163 SmtpSession *p;
164 va_list ap;
@@ -160,29 +166,36 @@
166
167 p = fossil_malloc( sizeof(*p) );
168 memset(p, 0, sizeof(*p));
169 p->zFrom = zFrom;
170 p->zDest = zDest;
 
171 p->smtpFlags = smtpFlags;
172 memset(&url, 0, sizeof(url));
173 url.port = 25;
174 blob_init(&p->inbuf, 0, 0);
175 va_start(ap, smtpFlags);
176 if( smtpFlags & SMTP_PORT ){
177 url.port = va_arg(ap, int);
178 }
179 if( smtpFlags & SMTP_TRACE_FILE ){
180 p->logFile = va_arg(ap, FILE*);
181 }
182 if( smtpFlags & SMTP_TRACE_BLOB ){
183 p->pTranscript = va_arg(ap, Blob*);
184 }
185 va_end(ap);
186 if( (smtpFlags & SMTP_DIRECT)!=0 ){
187 p->zHostname = fossil_strdup(zDest);
188 }else{
189 p->zHostname = smtp_mx_host(zDest);
190 }
191 if( p->zHostname==0 ){
192 p->atEof = 1;
193 p->zErr = mprintf("cannot locate SMTP server for \"%s\"", zDest);
194 return p;
195 }
 
196 url.name = p->zHostname;
 
197 socket_global_init();
198 if( socket_open(&url) ){
199 p->atEof = 1;
200 p->zErr = socket_errmsg();
201 socket_close();
@@ -206,17 +219,17 @@
219 n = blob_size(&b);
220 assert( n>=2 );
221 assert( z[n-1]=='\n' );
222 assert( z[n-2]=='\r' );
223 if( p->smtpFlags & SMTP_TRACE_STDOUT ){
224 fossil_print("C: %.*s\n", n-2, z);
225 }
226 if( p->smtpFlags & SMTP_TRACE_FILE ){
227 fprintf(p->logFile, "C: %.*s\n", n-2, z);
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
@@ -271,17 +284,17 @@
284 z = blob_buffer(in);
285 n = blob_size(in);
286 if( n && z[n-1]=='\n' ) n--;
287 if( n && z[n-1]=='\r' ) n--;
288 if( p->smtpFlags & SMTP_TRACE_STDOUT ){
289 fossil_print("S: %.*s\n", n, z);
290 }
291 if( p->smtpFlags & SMTP_TRACE_FILE ){
292 fprintf(p->logFile, "S: %.*s\n", n, z);
293 }
294 if( p->smtpFlags & SMTP_TRACE_BLOB ){
295 blob_appendf(p->pTranscript, "S: %.*s\n", n-2, z);
296 }
297 }
298
299 /*
300 ** Capture a single-line server reply.
@@ -362,19 +375,32 @@
375 ** Usage: %fossil test-smtp-probe DOMAIN [ME]
376 **
377 ** Interact with the SMTP server for DOMAIN by setting up a connection
378 ** and then immediately shutting it back down. Log all interaction
379 ** on the console. Use ME as the domain name of the sender.
380 **
381 ** Options:
382 **
383 ** --direct Use DOMAIN directly without going through MX
384 ** --port N Talk on TCP port N
385 */
386 void test_smtp_probe(void){
387 SmtpSession *p;
388 const char *zDomain;
389 const char *zSelf;
390 const char *zPort;
391 int iPort = 25;
392 u32 smtpFlags = SMTP_TRACE_STDOUT|SMTP_PORT;
393
394 if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
395 zPort = find_option("port",0,1);
396 if( zPort ) iPort = atoi(zPort);
397 verify_all_options();
398 if( g.argc!=3 && g.argc!=4 ) usage("DOMAIN [ME]");
399 zDomain = g.argv[2];
400 zSelf = g.argc==4 ? g.argv[3] : "fossil-scm.org";
401 p = smtp_session_new(zSelf, zDomain, smtpFlags, iPort);
402 if( p->zErr ){
403 fossil_fatal("%s", p->zErr);
404 }
405 fossil_print("Connection to \"%s\"\n", p->zHostname);
406 smtp_client_startup(p);
@@ -523,31 +549,38 @@
549 ** Use SMTP to send the email message contained in the file named EMAIL
550 ** to the list of users TO. FROM is the sender of the email.
551 **
552 ** Options:
553 **
554 ** --direct Go directly to the TO domain. Bypass MX lookup
555 ** --port N Use TCP port N instead of 25
556 ** --trace Show the SMTP conversation on the console
557 */
558 void test_smtp_send(void){
559 SmtpSession *p;
560 const char *zFrom;
561 int nTo;
562 const char *zToDomain;
563 const char *zFromDomain;
564 const char **azTo;
565 int smtpPort = 25;
566 const char *zPort;
567 Blob body;
568 u32 smtpFlags = SMTP_PORT;
569 if( find_option("trace",0,0)!=0 ) smtpFlags |= SMTP_TRACE_STDOUT;
570 if( find_option("direct",0,0)!=0 ) smtpFlags |= SMTP_DIRECT;
571 zPort = find_option("port",0,1);
572 if( zPort ) smtpPort = atoi(zPort);
573 verify_all_options();
574 if( g.argc<5 ) usage("EMAIL FROM TO ...");
575 blob_read_from_file(&body, g.argv[2], ExtFILE);
576 zFrom = g.argv[3];
577 nTo = g.argc-4;
578 azTo = (const char**)g.argv+4;
579 zFromDomain = domainOfAddr(zFrom);
580 zToDomain = domainOfAddr(azTo[0]);
581 p = smtp_session_new(zFromDomain, zToDomain, smtpFlags, smtpPort);
582 if( p->zErr ){
583 fossil_fatal("%s", p->zErr);
584 }
585 fossil_print("Connection to \"%s\"\n", p->zHostname);
586 smtp_client_startup(p);
@@ -557,5 +590,168 @@
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 */
618
619 #endif /* LOCAL_INTERFACE */
620
621 /*
622 ** Clear the SmtpServer object. Deallocate resources.
623 ** How much to clear depends on eHowMuch
624 */
625 static void smtp_server_clear(SmtpServer *p, int eHowMuch){
626 int i;
627 if( eHowMuch>=SMTPSRV_CLEAR_MSG ){
628 fossil_free(p->zFrom);
629 p->zFrom = 0;
630 for(i=0; i<p->nTo; i++) fossil_free(p->azTo[0]);
631 fossil_free(p->azTo);
632 p->azTo = 0;
633 p->nTo = 0;
634 blob_zero(&p->msg);
635 }
636 if( eHowMuch>=SMTPSRV_CLEAR_ALL ){
637 blob_zero(&p->transcript);
638 sqlite3_close(p->db);
639 p->db = 0;
640 fossil_free(p->zEhlo);
641 p->zEhlo = 0;
642 }
643 }
644
645 /*
646 ** Turn raw memory into an SmtpServer object.
647 */
648 static void smtp_server_init(SmtpServer *p){
649 memset(p, 0, sizeof(*p));
650 blob_init(&p->msg, 0, 0);
651 blob_init(&p->transcript, 0, 0);
652 }
653
654 /*
655 ** Send a single line of output from the server to the client.
656 */
657 static void smtp_server_send(SmtpServer *p, const char *zFormat, ...){
658 Blob b = empty_blob;
659 va_list ap;
660 char *z;
661 int n;
662 va_start(ap, zFormat);
663 blob_vappendf(&b, zFormat, ap);
664 va_end(ap);
665 z = blob_buffer(&b);
666 n = blob_size(&b);
667 assert( n>=2 );
668 assert( z[n-1]=='\n' );
669 assert( z[n-2]=='\r' );
670 if( p->srvrFlags & SMTPSRV_LOG ){
671 blob_appendf(&p->transcript, "S: %.*s\n", n-2, z);
672 }
673 fwrite(z, n, 1, stdout);
674 fflush(stdout);
675 blob_zero(&b);
676 }
677
678 /*
679 ** Read a single line from the client.
680 */
681 static int smtp_server_gets(SmtpServer *p, char *aBuf, int nBuf){
682 int rc = fgets(aBuf, nBuf, stdin)!=0;
683 if( rc && (p->srvrFlags & SMTPSRV_LOG)!=0 ){
684 blob_appendf(&p->transcript, "C: %s\n", aBuf);
685 }
686 return rc;
687 }
688
689 /*
690 ** Capture the incoming email data into the p->msg blob. Dequote
691 ** lines of "..\r\n" into just ".\r\n".
692 */
693 static void smtp_server_capture_data(SmtpServer *p, char *z, int n){
694 while( fgets(z, n, stdin) ){
695 if( strncmp(z, ".\r\n", 3)==0 ) return;
696 if( strncmp(z, "..\r\n", 4)==0 ){
697 memmove(z, z+1, 4);
698 }
699 blob_append(&p->msg, z, -1);
700 }
701 }
702
703 /*
704 ** COMMAND: smtp
705 **
706 ** Usage: %fossil smtp [options] DBNAME
707 **
708 ** Begin a SMTP conversation with a client using stdin/stdout. (This
709 ** command is expected to be launched from xinetd or the equivalent.)
710 ** Use information in the SQLite database at DBNAME to find configuration
711 ** information and as a place to store the incoming content.
712 */
713 void smtp_server(void){
714 char *zDbName;
715 const char *zDomain;
716 SmtpServer x;
717 char z[5000];
718
719 smtp_server_init(&x);
720 zDomain = find_option("domain",0,1);
721 if( zDomain==0 ) zDomain = "unspecified.domain";
722 verify_all_options();
723 if( g.argc!=3 ) usage("DBNAME");
724 zDbName = g.argv[2];
725 zDbName = enter_chroot_jail(zDbName, 0);
726 smtp_server_send(&x, "220 %s ESMTP Fossil ([%.*s] %s)\r\n",
727 zDomain, 16, MANIFEST_VERSION, MANIFEST_DATE);
728 while( smtp_server_gets(&x, z, sizeof(z)) ){
729 if( strncmp(z, "EHLO ", 5)==0 ){
730 smtp_server_send(&x, "250 ok\r\n");
731 }else
732 if( strncmp(z, "HELO ", 5)==0 ){
733 smtp_server_send(&x, "250 ok\r\n");
734 }else
735 if( strncmp(z, "MAIL FROM:<", 11)==0 ){
736 smtp_server_send(&x, "250 ok\r\n");
737 }else
738 if( strncmp(z, "RCPT TO:<", 9)==0 ){
739 smtp_server_send(&x, "250 ok\r\n");
740 }else
741 if( strncmp(z, "DATA", 4)==0 ){
742 smtp_server_send(&x, "354 ready\r\n");
743 smtp_server_capture_data(&x, z, sizeof(z));
744 smtp_server_send(&x, "250 ok\r\n");
745 smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
746 }else
747 if( strncmp(z, "QUIT", 4)==0 ){
748 smtp_server_send(&x, "221 closing connection\r\n");
749 break;
750 }else
751 {
752 smtp_server_send(&x, "500 unknown command\r\n");
753 }
754 }
755 fclose(stdin);
756 smtp_server_clear(&x, SMTPSRV_CLEAR_ALL);
757 }
758

Keyboard Shortcuts

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