Fossil SCM

Add "SMTP relay" as a new method for sending alert emails.

drh 2018-06-30 17:27 UTC smtp
Commit b96415f047707df245270cbe40bd72f4389e463a4a504182dc800a2db24b5216
2 files changed +172 -11 +9 -97
+172 -11
--- src/email.c
+++ src/email.c
@@ -188,14 +188,15 @@
188188
** Administrative page for configuring and controlling email notification.
189189
** Normally accessible via the /Admin/Email menu.
190190
*/
191191
void setup_email(void){
192192
static const char *const azSendMethods[] = {
193
- "off", "Disabled",
194
- "pipe", "Pipe to a command",
195
- "db", "Store in a database",
196
- "dir", "Store in a directory"
193
+ "off", "Disabled",
194
+ "pipe", "Pipe to a command",
195
+ "db", "Store in a database",
196
+ "dir", "Store in a directory",
197
+ "relay", "SMTP relay"
197198
};
198199
login_check_credentials();
199200
if( !g.perm.Setup ){
200201
login_needed(0);
201202
return;
@@ -244,17 +245,14 @@
244245
@ (Property: "email-autoexec")</p>
245246
@ <hr>
246247
247248
multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
248249
"off", count(azSendMethods)/2, azSendMethods);
249
- @ <p>How to send email. The "Pipe to a command"
250
- @ method is the usual choice in production.
251
- @ (Property: "email-send-method")</p>
252
- @ <hr>
250
+ @ <p>How to send email. Requires auxiliary information from the fields
251
+ @ that follow. (Property: "email-send-method")</p>
253252
email_schema(1);
254
-
255
- entry_attribute("Command To Pipe Email To", 80, "email-send-command",
253
+ entry_attribute("Command To Pipe Email To", 60, "email-send-command",
256254
"ecmd", "sendmail -t", 0);
257255
@ <p>When the send method is "pipe to a command", this is the command
258256
@ that is run. Email messages are piped into the standard input of this
259257
@ command. The command is expected to extract the sender address,
260258
@ recepient addresses, and subject from the header of the piped email
@@ -269,10 +267,19 @@
269267
entry_attribute("Directory In Which To Store Email", 60, "email-send-dir",
270268
"esdir", "", 0);
271269
@ <p>When the send method is "store in a directory", each email message is
272270
@ stored as a separate file in the directory shown here.
273271
@ (Property: "email-send-dir")</p>
272
+
273
+ entry_attribute("SMTP relay host", 60, "email-send-relayhost",
274
+ "esrh", "", 0);
275
+ @ <p>When the send method is "SMTP relay", each email message is
276
+ @ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission
277
+ @ Agent" or "MSA" (rfc4409) at the hostname shown here. Optionally
278
+ @ append a colon and TCP port number (ex: smtp.example.com:587).
279
+ @ The default TCP port number is 25.
280
+ @ (Property: "email-send-relayhost")</p>
274281
@ <hr>
275282
276283
entry_attribute("Administrator email address", 40, "email-admin",
277284
"eadmin", "", 0);
278285
@ <p>This is the email for the human administrator for the system.
@@ -376,10 +383,11 @@
376383
const char *zDest; /* How to send email. */
377384
const char *zDb; /* Name of database file */
378385
const char *zDir; /* Directory in which to store as email files */
379386
const char *zCmd; /* Command to run for each email */
380387
const char *zFrom; /* Emails come from here */
388
+ SmtpSession *pSmtp; /* SMTP relay connection */
381389
Blob out; /* For zDest=="blob" */
382390
char *zErr; /* Error message */
383391
int bImmediateFail; /* On any error, call fossil_fatal() */
384392
};
385393
#endif /* INTERFACE */
@@ -393,11 +401,14 @@
393401
sqlite3_close(p->db);
394402
p->db = 0;
395403
p->zDb = 0;
396404
p->zDir = 0;
397405
p->zCmd = 0;
398
- p->zDest = "off";
406
+ if( p->pSmtp ){
407
+ smtp_session_free(p->pSmtp);
408
+ p->pSmtp = 0;
409
+ }
399410
blob_reset(&p->out);
400411
}
401412
402413
/*
403414
** Put the EmailSender into an error state.
@@ -460,10 +471,11 @@
460471
EmailSender *email_sender_new(const char *zAltDest, int bImmediateFail){
461472
EmailSender *p;
462473
463474
p = fossil_malloc(sizeof(*p));
464475
memset(p, 0, sizeof(*p));
476
+ blob_init(&p->out, 0, 0);
465477
p->bImmediateFail = bImmediateFail;
466478
if( zAltDest ){
467479
p->zDest = zAltDest;
468480
}else{
469481
p->zDest = db_get("email-send-method","off");
@@ -499,13 +511,146 @@
499511
emailerGetSetting(p, &p->zCmd, "email-send-command");
500512
}else if( fossil_strcmp(p->zDest, "dir")==0 ){
501513
emailerGetSetting(p, &p->zDir, "email-send-dir");
502514
}else if( fossil_strcmp(p->zDest, "blob")==0 ){
503515
blob_init(&p->out, 0, 0);
516
+ }else if( fossil_strcmp(p->zDest, "relay")==0 ){
517
+ const char *zRelay = 0;
518
+ emailerGetSetting(p, &zRelay, "email-send-relayhost");
519
+ if( zRelay ){
520
+ p->pSmtp = smtp_session_new(p->zFrom, zRelay, SMTP_DIRECT);
521
+ }
504522
}
505523
return p;
506524
}
525
+
526
+/*
527
+** Scan the header of the email message in pMsg looking for the
528
+** (first) occurrance of zField. Fill pValue with the content of
529
+** that field.
530
+**
531
+** This routine initializes pValue. Any prior content of pValue is
532
+** discarded (leaked).
533
+**
534
+** Return non-zero on success. Return 0 if no instance of the header
535
+** is found.
536
+*/
537
+int email_header_value(Blob *pMsg, const char *zField, Blob *pValue){
538
+ int nField = (int)strlen(zField);
539
+ Blob line;
540
+ blob_rewind(pMsg);
541
+ blob_init(pValue,0,0);
542
+ while( blob_line(pMsg, &line) ){
543
+ int n, i;
544
+ char *z;
545
+ blob_trim(&line);
546
+ n = blob_size(&line);
547
+ if( n==0 ) return 0;
548
+ if( n<nField+1 ) continue;
549
+ z = blob_buffer(&line);
550
+ if( sqlite3_strnicmp(z, zField, nField)==0 && z[nField]==':' ){
551
+ for(i=nField+1; i<n && fossil_isspace(z[i]); i++){}
552
+ blob_init(pValue, z+i, n-i);
553
+ while( blob_line(pMsg, &line) ){
554
+ blob_trim(&line);
555
+ n = blob_size(&line);
556
+ if( n==0 ) break;
557
+ z = blob_buffer(&line);
558
+ if( !fossil_isspace(z[0]) ) break;
559
+ for(i=1; i<n && fossil_isspace(z[i]); i++){}
560
+ blob_append(pValue, " ", 1);
561
+ blob_append(pValue, z+i, n-i);
562
+ }
563
+ return 1;
564
+ }
565
+ }
566
+ return 0;
567
+}
568
+
569
+/*
570
+** Make a copy of the input string up to but not including the
571
+** first ">" character.
572
+**
573
+** Verify that the string really that is to be copied really is a
574
+** valid email address. If it is not, then return NULL.
575
+**
576
+** This routine is more restrictive than necessary. It does not
577
+** allow comments, IP address, quoted strings, or certain uncommon
578
+** characters. The only non-alphanumerics allowed in the local
579
+** part are "_", "+", "-" and "+".
580
+*/
581
+char *email_copy_addr(const char *z){
582
+ int i;
583
+ int nAt = 0;
584
+ int nDot = 0;
585
+ char c;
586
+ if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */
587
+ for(i=0; (c = z[i])!=0 && c!='>'; i++){
588
+ if( fossil_isalnum(c) ){
589
+ /* Alphanumerics are always ok */
590
+ }else if( c=='@' ){
591
+ if( nAt ) return 0; /* Only a single "@" allowed */
592
+ if( i>64 ) return 0; /* Local part too big */
593
+ nAt = 1;
594
+ nDot = 0;
595
+ if( i==0 ) return 0; /* Disallow empty local part */
596
+ if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */
597
+ if( z[i+1]=='.' || z[i+1]=='-' ){
598
+ return 0; /* Domain cannot begin with "." or "-" */
599
+ }
600
+ }else if( c=='-' ){
601
+ if( z[i+1]=='>' ) return 0; /* Last character cannot be "-" */
602
+ }else if( c=='.' ){
603
+ if( z[i+1]=='.' ) return 0; /* Do not allow ".." */
604
+ if( z[i+1]=='>' ) return 0; /* Domain may not end with . */
605
+ nDot++;
606
+ }else if( (c=='_' || c=='+') && nAt==0 ){
607
+ /* _ and + are ok in the local part */
608
+ }else{
609
+ return 0; /* Anything else is an error */
610
+ }
611
+ }
612
+ if( c!='>' ) return 0; /* Missing final ">" */
613
+ if( nAt==0 ) return 0; /* No "@" found anywhere */
614
+ if( nDot==0 ) return 0; /* No "." in the domain */
615
+
616
+ /* If we reach this point, the email address is valid */
617
+ return mprintf("%.*s", i, z);
618
+}
619
+
620
+/*
621
+** Extract all To: header values from the email header supplied.
622
+** Store them in the array list.
623
+*/
624
+void email_header_to(Blob *pMsg, int *pnTo, char ***pazTo){
625
+ int nTo = 0;
626
+ char **azTo = 0;
627
+ Blob v;
628
+ char *z, *zAddr;
629
+ int i;
630
+
631
+ email_header_value(pMsg, "to", &v);
632
+ z = blob_str(&v);
633
+ for(i=0; z[i]; i++){
634
+ if( z[i]=='<' && (zAddr = email_copy_addr(&z[i+1]))!=0 ){
635
+ azTo = fossil_realloc(azTo, sizeof(azTo[0])*(nTo+1) );
636
+ azTo[nTo++] = zAddr;
637
+ }
638
+ }
639
+ *pnTo = nTo;
640
+ *pazTo = azTo;
641
+}
642
+
643
+/*
644
+** Free a list of To addresses obtained from a prior call to
645
+** email_header_to()
646
+*/
647
+void email_header_to_free(int nTo, char **azTo){
648
+ int i;
649
+ for(i=0; i<nTo; i++) fossil_free(azTo[i]);
650
+ fossil_free(azTo);
651
+}
507652
508653
/*
509654
** Send a single email message.
510655
**
511656
** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields
@@ -579,11 +724,27 @@
579724
}
580725
}else if( p->zDir ){
581726
char *zFile = emailTempFilename(p->zDir);
582727
blob_write_to_file(&all, zFile);
583728
fossil_free(zFile);
729
+ }else if( p->pSmtp ){
730
+ char **azTo = 0;
731
+ int nTo = 0;
732
+ email_header_to(pHdr, &nTo, &azTo);
733
+ if( nTo>0 ){
734
+ smtp_send_msg(p->pSmtp, p->zFrom, nTo, azTo, blob_str(&all));
735
+ email_header_to_free(nTo, azTo);
736
+ }
584737
}else if( strcmp(p->zDest, "stdout")==0 ){
738
+ char **azTo = 0;
739
+ int nTo = 0;
740
+ int i;
741
+ email_header_to(pHdr, &nTo, &azTo);
742
+ for(i=0; i<nTo; i++){
743
+ fossil_print("X-To-Test-%d: [%s]\r\n", i, azTo[i]);
744
+ }
745
+ email_header_to_free(nTo, azTo);
585746
blob_add_final_newline(&all);
586747
fossil_print("%s", blob_str(&all));
587748
}
588749
blob_reset(&all);
589750
}
590751
--- src/email.c
+++ src/email.c
@@ -188,14 +188,15 @@
188 ** Administrative page for configuring and controlling email notification.
189 ** Normally accessible via the /Admin/Email menu.
190 */
191 void setup_email(void){
192 static const char *const azSendMethods[] = {
193 "off", "Disabled",
194 "pipe", "Pipe to a command",
195 "db", "Store in a database",
196 "dir", "Store in a directory"
 
197 };
198 login_check_credentials();
199 if( !g.perm.Setup ){
200 login_needed(0);
201 return;
@@ -244,17 +245,14 @@
244 @ (Property: "email-autoexec")</p>
245 @ <hr>
246
247 multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
248 "off", count(azSendMethods)/2, azSendMethods);
249 @ <p>How to send email. The "Pipe to a command"
250 @ method is the usual choice in production.
251 @ (Property: "email-send-method")</p>
252 @ <hr>
253 email_schema(1);
254
255 entry_attribute("Command To Pipe Email To", 80, "email-send-command",
256 "ecmd", "sendmail -t", 0);
257 @ <p>When the send method is "pipe to a command", this is the command
258 @ that is run. Email messages are piped into the standard input of this
259 @ command. The command is expected to extract the sender address,
260 @ recepient addresses, and subject from the header of the piped email
@@ -269,10 +267,19 @@
269 entry_attribute("Directory In Which To Store Email", 60, "email-send-dir",
270 "esdir", "", 0);
271 @ <p>When the send method is "store in a directory", each email message is
272 @ stored as a separate file in the directory shown here.
273 @ (Property: "email-send-dir")</p>
 
 
 
 
 
 
 
 
 
274 @ <hr>
275
276 entry_attribute("Administrator email address", 40, "email-admin",
277 "eadmin", "", 0);
278 @ <p>This is the email for the human administrator for the system.
@@ -376,10 +383,11 @@
376 const char *zDest; /* How to send email. */
377 const char *zDb; /* Name of database file */
378 const char *zDir; /* Directory in which to store as email files */
379 const char *zCmd; /* Command to run for each email */
380 const char *zFrom; /* Emails come from here */
 
381 Blob out; /* For zDest=="blob" */
382 char *zErr; /* Error message */
383 int bImmediateFail; /* On any error, call fossil_fatal() */
384 };
385 #endif /* INTERFACE */
@@ -393,11 +401,14 @@
393 sqlite3_close(p->db);
394 p->db = 0;
395 p->zDb = 0;
396 p->zDir = 0;
397 p->zCmd = 0;
398 p->zDest = "off";
 
 
 
399 blob_reset(&p->out);
400 }
401
402 /*
403 ** Put the EmailSender into an error state.
@@ -460,10 +471,11 @@
460 EmailSender *email_sender_new(const char *zAltDest, int bImmediateFail){
461 EmailSender *p;
462
463 p = fossil_malloc(sizeof(*p));
464 memset(p, 0, sizeof(*p));
 
465 p->bImmediateFail = bImmediateFail;
466 if( zAltDest ){
467 p->zDest = zAltDest;
468 }else{
469 p->zDest = db_get("email-send-method","off");
@@ -499,13 +511,146 @@
499 emailerGetSetting(p, &p->zCmd, "email-send-command");
500 }else if( fossil_strcmp(p->zDest, "dir")==0 ){
501 emailerGetSetting(p, &p->zDir, "email-send-dir");
502 }else if( fossil_strcmp(p->zDest, "blob")==0 ){
503 blob_init(&p->out, 0, 0);
 
 
 
 
 
 
504 }
505 return p;
506 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
508 /*
509 ** Send a single email message.
510 **
511 ** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields
@@ -579,11 +724,27 @@
579 }
580 }else if( p->zDir ){
581 char *zFile = emailTempFilename(p->zDir);
582 blob_write_to_file(&all, zFile);
583 fossil_free(zFile);
 
 
 
 
 
 
 
 
584 }else if( strcmp(p->zDest, "stdout")==0 ){
 
 
 
 
 
 
 
 
585 blob_add_final_newline(&all);
586 fossil_print("%s", blob_str(&all));
587 }
588 blob_reset(&all);
589 }
590
--- src/email.c
+++ src/email.c
@@ -188,14 +188,15 @@
188 ** Administrative page for configuring and controlling email notification.
189 ** Normally accessible via the /Admin/Email menu.
190 */
191 void setup_email(void){
192 static const char *const azSendMethods[] = {
193 "off", "Disabled",
194 "pipe", "Pipe to a command",
195 "db", "Store in a database",
196 "dir", "Store in a directory",
197 "relay", "SMTP relay"
198 };
199 login_check_credentials();
200 if( !g.perm.Setup ){
201 login_needed(0);
202 return;
@@ -244,17 +245,14 @@
245 @ (Property: "email-autoexec")</p>
246 @ <hr>
247
248 multiple_choice_attribute("Email Send Method", "email-send-method", "esm",
249 "off", count(azSendMethods)/2, azSendMethods);
250 @ <p>How to send email. Requires auxiliary information from the fields
251 @ that follow. (Property: "email-send-method")</p>
 
 
252 email_schema(1);
253 entry_attribute("Command To Pipe Email To", 60, "email-send-command",
 
254 "ecmd", "sendmail -t", 0);
255 @ <p>When the send method is "pipe to a command", this is the command
256 @ that is run. Email messages are piped into the standard input of this
257 @ command. The command is expected to extract the sender address,
258 @ recepient addresses, and subject from the header of the piped email
@@ -269,10 +267,19 @@
267 entry_attribute("Directory In Which To Store Email", 60, "email-send-dir",
268 "esdir", "", 0);
269 @ <p>When the send method is "store in a directory", each email message is
270 @ stored as a separate file in the directory shown here.
271 @ (Property: "email-send-dir")</p>
272
273 entry_attribute("SMTP relay host", 60, "email-send-relayhost",
274 "esrh", "", 0);
275 @ <p>When the send method is "SMTP relay", each email message is
276 @ transmitted via the SMTP protocol (rfc5321) to a "Mail Submission
277 @ Agent" or "MSA" (rfc4409) at the hostname shown here. Optionally
278 @ append a colon and TCP port number (ex: smtp.example.com:587).
279 @ The default TCP port number is 25.
280 @ (Property: "email-send-relayhost")</p>
281 @ <hr>
282
283 entry_attribute("Administrator email address", 40, "email-admin",
284 "eadmin", "", 0);
285 @ <p>This is the email for the human administrator for the system.
@@ -376,10 +383,11 @@
383 const char *zDest; /* How to send email. */
384 const char *zDb; /* Name of database file */
385 const char *zDir; /* Directory in which to store as email files */
386 const char *zCmd; /* Command to run for each email */
387 const char *zFrom; /* Emails come from here */
388 SmtpSession *pSmtp; /* SMTP relay connection */
389 Blob out; /* For zDest=="blob" */
390 char *zErr; /* Error message */
391 int bImmediateFail; /* On any error, call fossil_fatal() */
392 };
393 #endif /* INTERFACE */
@@ -393,11 +401,14 @@
401 sqlite3_close(p->db);
402 p->db = 0;
403 p->zDb = 0;
404 p->zDir = 0;
405 p->zCmd = 0;
406 if( p->pSmtp ){
407 smtp_session_free(p->pSmtp);
408 p->pSmtp = 0;
409 }
410 blob_reset(&p->out);
411 }
412
413 /*
414 ** Put the EmailSender into an error state.
@@ -460,10 +471,11 @@
471 EmailSender *email_sender_new(const char *zAltDest, int bImmediateFail){
472 EmailSender *p;
473
474 p = fossil_malloc(sizeof(*p));
475 memset(p, 0, sizeof(*p));
476 blob_init(&p->out, 0, 0);
477 p->bImmediateFail = bImmediateFail;
478 if( zAltDest ){
479 p->zDest = zAltDest;
480 }else{
481 p->zDest = db_get("email-send-method","off");
@@ -499,13 +511,146 @@
511 emailerGetSetting(p, &p->zCmd, "email-send-command");
512 }else if( fossil_strcmp(p->zDest, "dir")==0 ){
513 emailerGetSetting(p, &p->zDir, "email-send-dir");
514 }else if( fossil_strcmp(p->zDest, "blob")==0 ){
515 blob_init(&p->out, 0, 0);
516 }else if( fossil_strcmp(p->zDest, "relay")==0 ){
517 const char *zRelay = 0;
518 emailerGetSetting(p, &zRelay, "email-send-relayhost");
519 if( zRelay ){
520 p->pSmtp = smtp_session_new(p->zFrom, zRelay, SMTP_DIRECT);
521 }
522 }
523 return p;
524 }
525
526 /*
527 ** Scan the header of the email message in pMsg looking for the
528 ** (first) occurrance of zField. Fill pValue with the content of
529 ** that field.
530 **
531 ** This routine initializes pValue. Any prior content of pValue is
532 ** discarded (leaked).
533 **
534 ** Return non-zero on success. Return 0 if no instance of the header
535 ** is found.
536 */
537 int email_header_value(Blob *pMsg, const char *zField, Blob *pValue){
538 int nField = (int)strlen(zField);
539 Blob line;
540 blob_rewind(pMsg);
541 blob_init(pValue,0,0);
542 while( blob_line(pMsg, &line) ){
543 int n, i;
544 char *z;
545 blob_trim(&line);
546 n = blob_size(&line);
547 if( n==0 ) return 0;
548 if( n<nField+1 ) continue;
549 z = blob_buffer(&line);
550 if( sqlite3_strnicmp(z, zField, nField)==0 && z[nField]==':' ){
551 for(i=nField+1; i<n && fossil_isspace(z[i]); i++){}
552 blob_init(pValue, z+i, n-i);
553 while( blob_line(pMsg, &line) ){
554 blob_trim(&line);
555 n = blob_size(&line);
556 if( n==0 ) break;
557 z = blob_buffer(&line);
558 if( !fossil_isspace(z[0]) ) break;
559 for(i=1; i<n && fossil_isspace(z[i]); i++){}
560 blob_append(pValue, " ", 1);
561 blob_append(pValue, z+i, n-i);
562 }
563 return 1;
564 }
565 }
566 return 0;
567 }
568
569 /*
570 ** Make a copy of the input string up to but not including the
571 ** first ">" character.
572 **
573 ** Verify that the string really that is to be copied really is a
574 ** valid email address. If it is not, then return NULL.
575 **
576 ** This routine is more restrictive than necessary. It does not
577 ** allow comments, IP address, quoted strings, or certain uncommon
578 ** characters. The only non-alphanumerics allowed in the local
579 ** part are "_", "+", "-" and "+".
580 */
581 char *email_copy_addr(const char *z){
582 int i;
583 int nAt = 0;
584 int nDot = 0;
585 char c;
586 if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */
587 for(i=0; (c = z[i])!=0 && c!='>'; i++){
588 if( fossil_isalnum(c) ){
589 /* Alphanumerics are always ok */
590 }else if( c=='@' ){
591 if( nAt ) return 0; /* Only a single "@" allowed */
592 if( i>64 ) return 0; /* Local part too big */
593 nAt = 1;
594 nDot = 0;
595 if( i==0 ) return 0; /* Disallow empty local part */
596 if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */
597 if( z[i+1]=='.' || z[i+1]=='-' ){
598 return 0; /* Domain cannot begin with "." or "-" */
599 }
600 }else if( c=='-' ){
601 if( z[i+1]=='>' ) return 0; /* Last character cannot be "-" */
602 }else if( c=='.' ){
603 if( z[i+1]=='.' ) return 0; /* Do not allow ".." */
604 if( z[i+1]=='>' ) return 0; /* Domain may not end with . */
605 nDot++;
606 }else if( (c=='_' || c=='+') && nAt==0 ){
607 /* _ and + are ok in the local part */
608 }else{
609 return 0; /* Anything else is an error */
610 }
611 }
612 if( c!='>' ) return 0; /* Missing final ">" */
613 if( nAt==0 ) return 0; /* No "@" found anywhere */
614 if( nDot==0 ) return 0; /* No "." in the domain */
615
616 /* If we reach this point, the email address is valid */
617 return mprintf("%.*s", i, z);
618 }
619
620 /*
621 ** Extract all To: header values from the email header supplied.
622 ** Store them in the array list.
623 */
624 void email_header_to(Blob *pMsg, int *pnTo, char ***pazTo){
625 int nTo = 0;
626 char **azTo = 0;
627 Blob v;
628 char *z, *zAddr;
629 int i;
630
631 email_header_value(pMsg, "to", &v);
632 z = blob_str(&v);
633 for(i=0; z[i]; i++){
634 if( z[i]=='<' && (zAddr = email_copy_addr(&z[i+1]))!=0 ){
635 azTo = fossil_realloc(azTo, sizeof(azTo[0])*(nTo+1) );
636 azTo[nTo++] = zAddr;
637 }
638 }
639 *pnTo = nTo;
640 *pazTo = azTo;
641 }
642
643 /*
644 ** Free a list of To addresses obtained from a prior call to
645 ** email_header_to()
646 */
647 void email_header_to_free(int nTo, char **azTo){
648 int i;
649 for(i=0; i<nTo; i++) fossil_free(azTo[i]);
650 fossil_free(azTo);
651 }
652
653 /*
654 ** Send a single email message.
655 **
656 ** The recepient(s) must be specified using "To:" or "Cc:" or "Bcc:" fields
@@ -579,11 +724,27 @@
724 }
725 }else if( p->zDir ){
726 char *zFile = emailTempFilename(p->zDir);
727 blob_write_to_file(&all, zFile);
728 fossil_free(zFile);
729 }else if( p->pSmtp ){
730 char **azTo = 0;
731 int nTo = 0;
732 email_header_to(pHdr, &nTo, &azTo);
733 if( nTo>0 ){
734 smtp_send_msg(p->pSmtp, p->zFrom, nTo, azTo, blob_str(&all));
735 email_header_to_free(nTo, azTo);
736 }
737 }else if( strcmp(p->zDest, "stdout")==0 ){
738 char **azTo = 0;
739 int nTo = 0;
740 int i;
741 email_header_to(pHdr, &nTo, &azTo);
742 for(i=0; i<nTo; i++){
743 fossil_print("X-To-Test-%d: [%s]\r\n", i, azTo[i]);
744 }
745 email_header_to_free(nTo, azTo);
746 blob_add_final_newline(&all);
747 fossil_print("%s", blob_str(&all));
748 }
749 blob_reset(&all);
750 }
751
+9 -97
--- src/smtp.c
+++ src/smtp.c
@@ -182,11 +182,17 @@
182182
if( smtpFlags & SMTP_TRACE_BLOB ){
183183
p->pTranscript = va_arg(ap, Blob*);
184184
}
185185
va_end(ap);
186186
if( (smtpFlags & SMTP_DIRECT)!=0 ){
187
+ int i;
187188
p->zHostname = fossil_strdup(zDest);
189
+ for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){}
190
+ if( p->zHostname[i]==':' ){
191
+ p->zHostname[i] = 0;
192
+ url.port = atoi(&p->zHostname[i+1]);
193
+ }
188194
}else{
189195
p->zHostname = smtp_mx_host(zDest);
190196
}
191197
if( p->zHostname==0 ){
192198
p->atEof = 1;
@@ -471,11 +477,11 @@
471477
472478
/*
473479
** Send a single email message to the SMTP server.
474480
**
475481
** All email addresses (zFrom and azTo) must be plain "local@domain"
476
-** format with the surrounding "<..>". This routine will add the
482
+** format without the surrounding "<..>". This routine will add the
477483
** necessary "<..>".
478484
**
479485
** The body of the email should be well-structured. This routine will
480486
** convert any \n line endings into \r\n and will escape lines containing
481487
** just ".", but will not make any other alterations or corrections to
@@ -595,53 +601,10 @@
595601
596602
/*****************************************************************************
597603
** Server implementation
598604
*****************************************************************************/
599605
600
-/*
601
-** Scan the header of the email message in pMsg looking for the
602
-** (first) occurrance of zField. Fill pValue with the content of
603
-** that field.
604
-**
605
-** This routine initializes pValue. Any prior content of pValue is
606
-** discarded (leaked).
607
-**
608
-** Return non-zero on success. Return 0 if no instance of the header
609
-** is found.
610
-*/
611
-int email_header_value(Blob *pMsg, const char *zField, Blob *pValue){
612
- int nField = (int)strlen(zField);
613
- Blob line;
614
- blob_rewind(pMsg);
615
- blob_init(pValue,0,0);
616
- while( blob_line(pMsg, &line) ){
617
- int n, i;
618
- char *z;
619
- blob_trim(&line);
620
- n = blob_size(&line);
621
- if( n==0 ) return 0;
622
- if( n<nField+1 ) continue;
623
- z = blob_buffer(&line);
624
- if( sqlite3_strnicmp(z, zField, nField)==0 && z[nField]==':' ){
625
- for(i=nField+1; i<n && fossil_isspace(z[i]); i++){}
626
- blob_init(pValue, z+i, n-i);
627
- while( blob_line(pMsg, &line) ){
628
- blob_trim(&line);
629
- n = blob_size(&line);
630
- if( n==0 ) break;
631
- z = blob_buffer(&line);
632
- if( !fossil_isspace(z[0]) ) break;
633
- for(i=1; i<n && fossil_isspace(z[i]); i++){}
634
- blob_append(pValue, " ", 1);
635
- blob_append(pValue, z+i, n-i);
636
- }
637
- return 1;
638
- }
639
- }
640
- return 0;
641
-}
642
-
643606
/*
644607
** Schema used by the email processing system.
645608
*/
646609
static const char zEmailSchema[] =
647610
@ -- bulk storage is in a separate table. This table can store either
@@ -969,61 +932,10 @@
969932
db_end_transaction(0);
970933
}
971934
smtp_server_clear(p, SMTPSRV_CLEAR_MSG);
972935
}
973936
974
-/*
975
-** Make a copy of the input string up to but not including the
976
-** first ">" character.
977
-**
978
-** Verify that the string really that is to be copied really is a
979
-** valid email address. If it is not, then return NULL.
980
-**
981
-** This routine is more restrictive than necessary. It does not
982
-** allow comments, IP address, quoted strings, or certain uncommon
983
-** characters. The only non-alphanumerics allowed in the local
984
-** part are "_", "+", "-" and "+".
985
-*/
986
-static char *extractEmail(const char *z){
987
- int i;
988
- int nAt = 0;
989
- int nDot = 0;
990
- char c;
991
- if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */
992
- for(i=0; (c = z[i])!=0 && c!='>'; i++){
993
- if( fossil_isalnum(c) ){
994
- /* Alphanumerics are always ok */
995
- }else if( c=='@' ){
996
- if( nAt ) return 0; /* Only a single "@" allowed */
997
- if( i>64 ) return 0; /* Local part too big */
998
- nAt = 1;
999
- nDot = 0;
1000
- if( i==0 ) return 0; /* Disallow empty local part */
1001
- if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */
1002
- if( z[i+1]=='.' || z[i+1]=='-' ){
1003
- return 0; /* Domain cannot begin with "." or "-" */
1004
- }
1005
- }else if( c=='-' ){
1006
- if( z[i+1]=='>' ) return 0; /* Last character cannot be "-" */
1007
- }else if( c=='.' ){
1008
- if( z[i+1]=='.' ) return 0; /* Do not allow ".." */
1009
- if( z[i+1]=='>' ) return 0; /* Domain may not end with . */
1010
- nDot++;
1011
- }else if( (c=='_' || c=='+') && nAt==0 ){
1012
- /* _ and + are ok in the local part */
1013
- }else{
1014
- return 0; /* Anything else is an error */
1015
- }
1016
- }
1017
- if( c!='>' ) return 0; /* Missing final ">" */
1018
- if( nAt==0 ) return 0; /* No "@" found anywhere */
1019
- if( nDot==0 ) return 0; /* No "." in the domain */
1020
-
1021
- /* If we reach this point, the email address is valid */
1022
- return mprintf("%.*s", i, z);
1023
-}
1024
-
1025937
/*
1026938
** COMMAND: smtpd
1027939
**
1028940
** Usage: %fossil smtpd [OPTIONS] REPOSITORY
1029941
**
@@ -1064,11 +976,11 @@
1064976
smtp_server_send(&x, "250 ok\r\n");
1065977
}else
1066978
if( strncmp(z, "MAIL FROM:<", 11)==0 ){
1067979
smtp_server_route_incoming(&x, 0);
1068980
smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
1069
- x.zFrom = extractEmail(z+11);
981
+ x.zFrom = email_copy_addr(z+11);
1070982
if( x.zFrom==0 ){
1071983
smtp_server_send(&x, "500 unacceptable email address\r\n");
1072984
}else{
1073985
smtp_server_send(&x, "250 ok\r\n");
1074986
}
@@ -1077,11 +989,11 @@
1077989
char *zAddr;
1078990
if( x.zFrom==0 ){
1079991
smtp_server_send(&x, "500 missing MAIL FROM\r\n");
1080992
continue;
1081993
}
1082
- zAddr = extractEmail(z+9);
994
+ zAddr = email_copy_addr(z+9);
1083995
if( zAddr==0 ){
1084996
smtp_server_send(&x, "505 no such user\r\n");
1085997
continue;
1086998
}
1087999
smtp_append_to(&x, zAddr, 0);
10881000
--- src/smtp.c
+++ src/smtp.c
@@ -182,11 +182,17 @@
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;
@@ -471,11 +477,11 @@
471
472 /*
473 ** Send a single email message to the SMTP server.
474 **
475 ** All email addresses (zFrom and azTo) must be plain "local@domain"
476 ** format with the surrounding "<..>". This routine will add the
477 ** necessary "<..>".
478 **
479 ** The body of the email should be well-structured. This routine will
480 ** convert any \n line endings into \r\n and will escape lines containing
481 ** just ".", but will not make any other alterations or corrections to
@@ -595,53 +601,10 @@
595
596 /*****************************************************************************
597 ** Server implementation
598 *****************************************************************************/
599
600 /*
601 ** Scan the header of the email message in pMsg looking for the
602 ** (first) occurrance of zField. Fill pValue with the content of
603 ** that field.
604 **
605 ** This routine initializes pValue. Any prior content of pValue is
606 ** discarded (leaked).
607 **
608 ** Return non-zero on success. Return 0 if no instance of the header
609 ** is found.
610 */
611 int email_header_value(Blob *pMsg, const char *zField, Blob *pValue){
612 int nField = (int)strlen(zField);
613 Blob line;
614 blob_rewind(pMsg);
615 blob_init(pValue,0,0);
616 while( blob_line(pMsg, &line) ){
617 int n, i;
618 char *z;
619 blob_trim(&line);
620 n = blob_size(&line);
621 if( n==0 ) return 0;
622 if( n<nField+1 ) continue;
623 z = blob_buffer(&line);
624 if( sqlite3_strnicmp(z, zField, nField)==0 && z[nField]==':' ){
625 for(i=nField+1; i<n && fossil_isspace(z[i]); i++){}
626 blob_init(pValue, z+i, n-i);
627 while( blob_line(pMsg, &line) ){
628 blob_trim(&line);
629 n = blob_size(&line);
630 if( n==0 ) break;
631 z = blob_buffer(&line);
632 if( !fossil_isspace(z[0]) ) break;
633 for(i=1; i<n && fossil_isspace(z[i]); i++){}
634 blob_append(pValue, " ", 1);
635 blob_append(pValue, z+i, n-i);
636 }
637 return 1;
638 }
639 }
640 return 0;
641 }
642
643 /*
644 ** Schema used by the email processing system.
645 */
646 static const char zEmailSchema[] =
647 @ -- bulk storage is in a separate table. This table can store either
@@ -969,61 +932,10 @@
969 db_end_transaction(0);
970 }
971 smtp_server_clear(p, SMTPSRV_CLEAR_MSG);
972 }
973
974 /*
975 ** Make a copy of the input string up to but not including the
976 ** first ">" character.
977 **
978 ** Verify that the string really that is to be copied really is a
979 ** valid email address. If it is not, then return NULL.
980 **
981 ** This routine is more restrictive than necessary. It does not
982 ** allow comments, IP address, quoted strings, or certain uncommon
983 ** characters. The only non-alphanumerics allowed in the local
984 ** part are "_", "+", "-" and "+".
985 */
986 static char *extractEmail(const char *z){
987 int i;
988 int nAt = 0;
989 int nDot = 0;
990 char c;
991 if( z[0]=='.' ) return 0; /* Local part cannot begin with "." */
992 for(i=0; (c = z[i])!=0 && c!='>'; i++){
993 if( fossil_isalnum(c) ){
994 /* Alphanumerics are always ok */
995 }else if( c=='@' ){
996 if( nAt ) return 0; /* Only a single "@" allowed */
997 if( i>64 ) return 0; /* Local part too big */
998 nAt = 1;
999 nDot = 0;
1000 if( i==0 ) return 0; /* Disallow empty local part */
1001 if( z[i-1]=='.' ) return 0; /* Last char of local cannot be "." */
1002 if( z[i+1]=='.' || z[i+1]=='-' ){
1003 return 0; /* Domain cannot begin with "." or "-" */
1004 }
1005 }else if( c=='-' ){
1006 if( z[i+1]=='>' ) return 0; /* Last character cannot be "-" */
1007 }else if( c=='.' ){
1008 if( z[i+1]=='.' ) return 0; /* Do not allow ".." */
1009 if( z[i+1]=='>' ) return 0; /* Domain may not end with . */
1010 nDot++;
1011 }else if( (c=='_' || c=='+') && nAt==0 ){
1012 /* _ and + are ok in the local part */
1013 }else{
1014 return 0; /* Anything else is an error */
1015 }
1016 }
1017 if( c!='>' ) return 0; /* Missing final ">" */
1018 if( nAt==0 ) return 0; /* No "@" found anywhere */
1019 if( nDot==0 ) return 0; /* No "." in the domain */
1020
1021 /* If we reach this point, the email address is valid */
1022 return mprintf("%.*s", i, z);
1023 }
1024
1025 /*
1026 ** COMMAND: smtpd
1027 **
1028 ** Usage: %fossil smtpd [OPTIONS] REPOSITORY
1029 **
@@ -1064,11 +976,11 @@
1064 smtp_server_send(&x, "250 ok\r\n");
1065 }else
1066 if( strncmp(z, "MAIL FROM:<", 11)==0 ){
1067 smtp_server_route_incoming(&x, 0);
1068 smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
1069 x.zFrom = extractEmail(z+11);
1070 if( x.zFrom==0 ){
1071 smtp_server_send(&x, "500 unacceptable email address\r\n");
1072 }else{
1073 smtp_server_send(&x, "250 ok\r\n");
1074 }
@@ -1077,11 +989,11 @@
1077 char *zAddr;
1078 if( x.zFrom==0 ){
1079 smtp_server_send(&x, "500 missing MAIL FROM\r\n");
1080 continue;
1081 }
1082 zAddr = extractEmail(z+9);
1083 if( zAddr==0 ){
1084 smtp_server_send(&x, "505 no such user\r\n");
1085 continue;
1086 }
1087 smtp_append_to(&x, zAddr, 0);
1088
--- src/smtp.c
+++ src/smtp.c
@@ -182,11 +182,17 @@
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 int i;
188 p->zHostname = fossil_strdup(zDest);
189 for(i=0; p->zHostname[i] && p->zHostname[i]!=':'; i++){}
190 if( p->zHostname[i]==':' ){
191 p->zHostname[i] = 0;
192 url.port = atoi(&p->zHostname[i+1]);
193 }
194 }else{
195 p->zHostname = smtp_mx_host(zDest);
196 }
197 if( p->zHostname==0 ){
198 p->atEof = 1;
@@ -471,11 +477,11 @@
477
478 /*
479 ** Send a single email message to the SMTP server.
480 **
481 ** All email addresses (zFrom and azTo) must be plain "local@domain"
482 ** format without the surrounding "<..>". This routine will add the
483 ** necessary "<..>".
484 **
485 ** The body of the email should be well-structured. This routine will
486 ** convert any \n line endings into \r\n and will escape lines containing
487 ** just ".", but will not make any other alterations or corrections to
@@ -595,53 +601,10 @@
601
602 /*****************************************************************************
603 ** Server implementation
604 *****************************************************************************/
605
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
606 /*
607 ** Schema used by the email processing system.
608 */
609 static const char zEmailSchema[] =
610 @ -- bulk storage is in a separate table. This table can store either
@@ -969,61 +932,10 @@
932 db_end_transaction(0);
933 }
934 smtp_server_clear(p, SMTPSRV_CLEAR_MSG);
935 }
936
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
937 /*
938 ** COMMAND: smtpd
939 **
940 ** Usage: %fossil smtpd [OPTIONS] REPOSITORY
941 **
@@ -1064,11 +976,11 @@
976 smtp_server_send(&x, "250 ok\r\n");
977 }else
978 if( strncmp(z, "MAIL FROM:<", 11)==0 ){
979 smtp_server_route_incoming(&x, 0);
980 smtp_server_clear(&x, SMTPSRV_CLEAR_MSG);
981 x.zFrom = email_copy_addr(z+11);
982 if( x.zFrom==0 ){
983 smtp_server_send(&x, "500 unacceptable email address\r\n");
984 }else{
985 smtp_server_send(&x, "250 ok\r\n");
986 }
@@ -1077,11 +989,11 @@
989 char *zAddr;
990 if( x.zFrom==0 ){
991 smtp_server_send(&x, "500 missing MAIL FROM\r\n");
992 continue;
993 }
994 zAddr = email_copy_addr(z+9);
995 if( zAddr==0 ){
996 smtp_server_send(&x, "505 no such user\r\n");
997 continue;
998 }
999 smtp_append_to(&x, zAddr, 0);
1000

Keyboard Shortcuts

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