Fossil SCM
For email notifications abound events from a single user (such as individual forum post notifications) make the From: address be a "noreply" hash based on the users human-readable name or handle. Always include a Return-Path: field in the header that is the email-self setting, to be used by MTUs that need to bounce the message.
Commit
475c9d11e9028f9b060b3b57c0b15e6749e176ba6dd3bb46cef776d78cb809ff
Parent
16c42a08084c2f9…
1 file changed
+56
-4
+56
-4
| --- src/email.c | ||
| +++ src/email.c | ||
| @@ -238,15 +238,18 @@ | ||
| 238 | 238 | @ email alert text. Omit the trailing "/". |
| 239 | 239 | @ Suggested value: "%h(g.zBaseURL)" |
| 240 | 240 | @ (Property: "email-url")</p> |
| 241 | 241 | @ <hr> |
| 242 | 242 | |
| 243 | - entry_attribute("\"From\" email address", 20, "email-self", | |
| 243 | + entry_attribute("\"Return-Path\" email address", 20, "email-self", | |
| 244 | 244 | "eself", "", 0); |
| 245 | 245 | @ <p><b>Required.</b> |
| 246 | - @ This is the email from which email notifications are sent. The | |
| 247 | - @ system administrator should arrange for emails sent to this address | |
| 246 | + @ This is the email to which email notification bounces should be sent. | |
| 247 | + @ In cases where the email notification does not align with a specific | |
| 248 | + @ Fossil login account (for example, digest messages), this is also | |
| 249 | + @ the "From:" address of the email notification. | |
| 250 | + @ The system administrator should arrange for emails sent to this address | |
| 248 | 251 | @ to be handed off to the "fossil email incoming" command so that Fossil |
| 249 | 252 | @ can handle bounces. (Property: "email-self")</p> |
| 250 | 253 | @ <hr> |
| 251 | 254 | |
| 252 | 255 | entry_attribute("Repository Nickname", 16, "email-subname", |
| @@ -669,10 +672,49 @@ | ||
| 669 | 672 | }else{ |
| 670 | 673 | z = (char*)zAddr; |
| 671 | 674 | } |
| 672 | 675 | return z; |
| 673 | 676 | } |
| 677 | + | |
| 678 | +/* | |
| 679 | +** Return a pointer to a fake email mailbox name that corresponds | |
| 680 | +** to human-readable name zFromName. The fake mailbox name is based | |
| 681 | +** on a hash. No huge problems arise if there is a hash collisions, | |
| 682 | +** but it is still better if collisions can be avoided. | |
| 683 | +** | |
| 684 | +** The returned string is held in a static buffer and is overwritten | |
| 685 | +** by each subsequent call to this routine. | |
| 686 | +*/ | |
| 687 | +static char *email_mailbox_name(const char *zFromName){ | |
| 688 | + static char zHash[20]; | |
| 689 | + unsigned int x = 0; | |
| 690 | + int n = 0; | |
| 691 | + while( zFromName[0] ){ | |
| 692 | + n++; | |
| 693 | + x = x*1103515245 + 12345 + ((unsigned char*)zFromName)[0]; | |
| 694 | + zFromName++; | |
| 695 | + } | |
| 696 | + sqlite3_snprintf(sizeof(zHash), zHash, | |
| 697 | + "noreply%x%08x", n, x); | |
| 698 | + return zHash; | |
| 699 | +} | |
| 700 | + | |
| 701 | +/* | |
| 702 | +** COMMAND: test-mailbox-hashname | |
| 703 | +** | |
| 704 | +** Usage: %fossil test-mailbox-hashname HUMAN-NAME ... | |
| 705 | +** | |
| 706 | +** Return the mailbox hash name corresponding to each human-readable | |
| 707 | +** name on the command line. This is a test interface for the | |
| 708 | +** email_mailbox_name() function. | |
| 709 | +*/ | |
| 710 | +void email_test_mailbox_hashname(void){ | |
| 711 | + int i; | |
| 712 | + for(i=2; i<g.argc; i++){ | |
| 713 | + fossil_print("%30s: %s\n", g.argv[i], email_mailbox_name(g.argv[i])); | |
| 714 | + } | |
| 715 | +} | |
| 674 | 716 | |
| 675 | 717 | /* |
| 676 | 718 | ** Extract all To: header values from the email header supplied. |
| 677 | 719 | ** Store them in the array list. |
| 678 | 720 | */ |
| @@ -714,19 +756,27 @@ | ||
| 714 | 756 | ** "In-Reply-To:". |
| 715 | 757 | ** |
| 716 | 758 | ** This routine will add fields to the header as follows: |
| 717 | 759 | ** |
| 718 | 760 | ** From: |
| 761 | +** Return-Path: | |
| 719 | 762 | ** Date: |
| 720 | 763 | ** Message-Id: |
| 721 | 764 | ** Content-Type: |
| 722 | 765 | ** Content-Transfer-Encoding: |
| 723 | 766 | ** MIME-Version: |
| 724 | 767 | ** |
| 725 | 768 | ** The caller maintains ownership of the input Blobs. This routine will |
| 726 | 769 | ** read the Blobs and send them onward to the email system, but it will |
| 727 | 770 | ** not free them. |
| 771 | +** | |
| 772 | +** If the zFromName argument is not NULL, then it should be a human-readable | |
| 773 | +** name or handle for the sender. In that case, "From:" becomes a made-up | |
| 774 | +** email address based on a hash of zFromName and the domain of email-self, | |
| 775 | +** and an additional "Reply-To:" field is inserted with the email-self | |
| 776 | +** address. If zFromName is a NULL pointer, then both "From:" and | |
| 777 | +** Return-Path: are set to the email-self value. | |
| 728 | 778 | */ |
| 729 | 779 | void email_send( |
| 730 | 780 | EmailSender *p, /* Emailer context */ |
| 731 | 781 | Blob *pHdr, /* Email header (incomplete) */ |
| 732 | 782 | Blob *pBody, /* Email body */ |
| @@ -749,14 +799,16 @@ | ||
| 749 | 799 | blob_init(&all, 0, 0); |
| 750 | 800 | pOut = &all; |
| 751 | 801 | } |
| 752 | 802 | blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr)); |
| 753 | 803 | if( zFromName ){ |
| 754 | - blob_appendf(pOut, "From: %s <%s>\r\n", zFromName, p->zFrom); | |
| 804 | + blob_appendf(pOut, "From: %s <%s@%s>\r\n", | |
| 805 | + zFromName, email_mailbox_name(zFromName), email_hostname(p->zFrom)); | |
| 755 | 806 | }else{ |
| 756 | 807 | blob_appendf(pOut, "From: <%s>\r\n", p->zFrom); |
| 757 | 808 | } |
| 809 | + blob_appendf(pOut, "Return-Path: <%s>\r\n", p->zFrom); | |
| 758 | 810 | blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0))); |
| 759 | 811 | if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){ |
| 760 | 812 | /* Message-id format: "<$(date)x$(random).$(from)>" where $(date) is |
| 761 | 813 | ** the current unix-time in hex, $(random) is a 64-bit random number, |
| 762 | 814 | ** and $(from) is the sender. */ |
| 763 | 815 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -238,15 +238,18 @@ | |
| 238 | @ email alert text. Omit the trailing "/". |
| 239 | @ Suggested value: "%h(g.zBaseURL)" |
| 240 | @ (Property: "email-url")</p> |
| 241 | @ <hr> |
| 242 | |
| 243 | entry_attribute("\"From\" email address", 20, "email-self", |
| 244 | "eself", "", 0); |
| 245 | @ <p><b>Required.</b> |
| 246 | @ This is the email from which email notifications are sent. The |
| 247 | @ system administrator should arrange for emails sent to this address |
| 248 | @ to be handed off to the "fossil email incoming" command so that Fossil |
| 249 | @ can handle bounces. (Property: "email-self")</p> |
| 250 | @ <hr> |
| 251 | |
| 252 | entry_attribute("Repository Nickname", 16, "email-subname", |
| @@ -669,10 +672,49 @@ | |
| 669 | }else{ |
| 670 | z = (char*)zAddr; |
| 671 | } |
| 672 | return z; |
| 673 | } |
| 674 | |
| 675 | /* |
| 676 | ** Extract all To: header values from the email header supplied. |
| 677 | ** Store them in the array list. |
| 678 | */ |
| @@ -714,19 +756,27 @@ | |
| 714 | ** "In-Reply-To:". |
| 715 | ** |
| 716 | ** This routine will add fields to the header as follows: |
| 717 | ** |
| 718 | ** From: |
| 719 | ** Date: |
| 720 | ** Message-Id: |
| 721 | ** Content-Type: |
| 722 | ** Content-Transfer-Encoding: |
| 723 | ** MIME-Version: |
| 724 | ** |
| 725 | ** The caller maintains ownership of the input Blobs. This routine will |
| 726 | ** read the Blobs and send them onward to the email system, but it will |
| 727 | ** not free them. |
| 728 | */ |
| 729 | void email_send( |
| 730 | EmailSender *p, /* Emailer context */ |
| 731 | Blob *pHdr, /* Email header (incomplete) */ |
| 732 | Blob *pBody, /* Email body */ |
| @@ -749,14 +799,16 @@ | |
| 749 | blob_init(&all, 0, 0); |
| 750 | pOut = &all; |
| 751 | } |
| 752 | blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr)); |
| 753 | if( zFromName ){ |
| 754 | blob_appendf(pOut, "From: %s <%s>\r\n", zFromName, p->zFrom); |
| 755 | }else{ |
| 756 | blob_appendf(pOut, "From: <%s>\r\n", p->zFrom); |
| 757 | } |
| 758 | blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0))); |
| 759 | if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){ |
| 760 | /* Message-id format: "<$(date)x$(random).$(from)>" where $(date) is |
| 761 | ** the current unix-time in hex, $(random) is a 64-bit random number, |
| 762 | ** and $(from) is the sender. */ |
| 763 |
| --- src/email.c | |
| +++ src/email.c | |
| @@ -238,15 +238,18 @@ | |
| 238 | @ email alert text. Omit the trailing "/". |
| 239 | @ Suggested value: "%h(g.zBaseURL)" |
| 240 | @ (Property: "email-url")</p> |
| 241 | @ <hr> |
| 242 | |
| 243 | entry_attribute("\"Return-Path\" email address", 20, "email-self", |
| 244 | "eself", "", 0); |
| 245 | @ <p><b>Required.</b> |
| 246 | @ This is the email to which email notification bounces should be sent. |
| 247 | @ In cases where the email notification does not align with a specific |
| 248 | @ Fossil login account (for example, digest messages), this is also |
| 249 | @ the "From:" address of the email notification. |
| 250 | @ The system administrator should arrange for emails sent to this address |
| 251 | @ to be handed off to the "fossil email incoming" command so that Fossil |
| 252 | @ can handle bounces. (Property: "email-self")</p> |
| 253 | @ <hr> |
| 254 | |
| 255 | entry_attribute("Repository Nickname", 16, "email-subname", |
| @@ -669,10 +672,49 @@ | |
| 672 | }else{ |
| 673 | z = (char*)zAddr; |
| 674 | } |
| 675 | return z; |
| 676 | } |
| 677 | |
| 678 | /* |
| 679 | ** Return a pointer to a fake email mailbox name that corresponds |
| 680 | ** to human-readable name zFromName. The fake mailbox name is based |
| 681 | ** on a hash. No huge problems arise if there is a hash collisions, |
| 682 | ** but it is still better if collisions can be avoided. |
| 683 | ** |
| 684 | ** The returned string is held in a static buffer and is overwritten |
| 685 | ** by each subsequent call to this routine. |
| 686 | */ |
| 687 | static char *email_mailbox_name(const char *zFromName){ |
| 688 | static char zHash[20]; |
| 689 | unsigned int x = 0; |
| 690 | int n = 0; |
| 691 | while( zFromName[0] ){ |
| 692 | n++; |
| 693 | x = x*1103515245 + 12345 + ((unsigned char*)zFromName)[0]; |
| 694 | zFromName++; |
| 695 | } |
| 696 | sqlite3_snprintf(sizeof(zHash), zHash, |
| 697 | "noreply%x%08x", n, x); |
| 698 | return zHash; |
| 699 | } |
| 700 | |
| 701 | /* |
| 702 | ** COMMAND: test-mailbox-hashname |
| 703 | ** |
| 704 | ** Usage: %fossil test-mailbox-hashname HUMAN-NAME ... |
| 705 | ** |
| 706 | ** Return the mailbox hash name corresponding to each human-readable |
| 707 | ** name on the command line. This is a test interface for the |
| 708 | ** email_mailbox_name() function. |
| 709 | */ |
| 710 | void email_test_mailbox_hashname(void){ |
| 711 | int i; |
| 712 | for(i=2; i<g.argc; i++){ |
| 713 | fossil_print("%30s: %s\n", g.argv[i], email_mailbox_name(g.argv[i])); |
| 714 | } |
| 715 | } |
| 716 | |
| 717 | /* |
| 718 | ** Extract all To: header values from the email header supplied. |
| 719 | ** Store them in the array list. |
| 720 | */ |
| @@ -714,19 +756,27 @@ | |
| 756 | ** "In-Reply-To:". |
| 757 | ** |
| 758 | ** This routine will add fields to the header as follows: |
| 759 | ** |
| 760 | ** From: |
| 761 | ** Return-Path: |
| 762 | ** Date: |
| 763 | ** Message-Id: |
| 764 | ** Content-Type: |
| 765 | ** Content-Transfer-Encoding: |
| 766 | ** MIME-Version: |
| 767 | ** |
| 768 | ** The caller maintains ownership of the input Blobs. This routine will |
| 769 | ** read the Blobs and send them onward to the email system, but it will |
| 770 | ** not free them. |
| 771 | ** |
| 772 | ** If the zFromName argument is not NULL, then it should be a human-readable |
| 773 | ** name or handle for the sender. In that case, "From:" becomes a made-up |
| 774 | ** email address based on a hash of zFromName and the domain of email-self, |
| 775 | ** and an additional "Reply-To:" field is inserted with the email-self |
| 776 | ** address. If zFromName is a NULL pointer, then both "From:" and |
| 777 | ** Return-Path: are set to the email-self value. |
| 778 | */ |
| 779 | void email_send( |
| 780 | EmailSender *p, /* Emailer context */ |
| 781 | Blob *pHdr, /* Email header (incomplete) */ |
| 782 | Blob *pBody, /* Email body */ |
| @@ -749,14 +799,16 @@ | |
| 799 | blob_init(&all, 0, 0); |
| 800 | pOut = &all; |
| 801 | } |
| 802 | blob_append(pOut, blob_buffer(pHdr), blob_size(pHdr)); |
| 803 | if( zFromName ){ |
| 804 | blob_appendf(pOut, "From: %s <%s@%s>\r\n", |
| 805 | zFromName, email_mailbox_name(zFromName), email_hostname(p->zFrom)); |
| 806 | }else{ |
| 807 | blob_appendf(pOut, "From: <%s>\r\n", p->zFrom); |
| 808 | } |
| 809 | blob_appendf(pOut, "Return-Path: <%s>\r\n", p->zFrom); |
| 810 | blob_appendf(pOut, "Date: %z\r\n", cgi_rfc822_datestamp(time(0))); |
| 811 | if( strstr(blob_str(pHdr), "\r\nMessage-Id:")==0 ){ |
| 812 | /* Message-id format: "<$(date)x$(random).$(from)>" where $(date) is |
| 813 | ** the current unix-time in hex, $(random) is a 64-bit random number, |
| 814 | ** and $(from) is the sender. */ |
| 815 |