summaryrefslogtreecommitdiffstats
path: root/qmail-remote.c
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2026-06-02 14:09:09 +0200
committermanuel <manuel@mausz.at>2026-06-02 14:09:09 +0200
commiteeea9df85e742f312eafca4f81ffae5494d88ca1 (patch)
treeb569077b9838249a0b2307b883b305315792c79f /qmail-remote.c
parenta48b423084b6c31aa8d23997fa31e47a2ec3c513 (diff)
downloadqmail-eeea9df85e742f312eafca4f81ffae5494d88ca1.tar.gz
qmail-eeea9df85e742f312eafca4f81ffae5494d88ca1.tar.bz2
qmail-eeea9df85e742f312eafca4f81ffae5494d88ca1.zip
qmail-remote: reimplement SMTPUTF8 support
make sure to deliver the mail to non-SMTPUTF8 capable remotes unless there are upper byte values found in headers/envelopes. this is more-or-less the same as postfix does.
Diffstat (limited to 'qmail-remote.c')
-rw-r--r--qmail-remote.c135
1 files changed, 91 insertions, 44 deletions
diff --git a/qmail-remote.c b/qmail-remote.c
index 7eb2714..d7c8746 100644
--- a/qmail-remote.c
+++ b/qmail-remote.c
@@ -47,7 +47,6 @@ stralloc helohost = {0};
47stralloc routes = {0}; 47stralloc routes = {0};
48struct constmap maproutes; 48struct constmap maproutes;
49stralloc host = {0}; 49stralloc host = {0};
50stralloc idnhost = {0};
51stralloc sender = {0}; 50stralloc sender = {0};
52stralloc auth_smtp_user = {0}; 51stralloc auth_smtp_user = {0};
53stralloc auth_smtp_pass = {0}; 52stralloc auth_smtp_pass = {0};
@@ -74,7 +73,9 @@ char **myargv;
74 73
75#ifdef SMTPUTF8 74#ifdef SMTPUTF8
76# include <idn2.h> 75# include <idn2.h>
77int flagutf8 = 0; 76int withutf8 = 0; /* received header has WITH SMTPUTF8 */
77int utf8message = 0; /* headers contain utf8 */
78stralloc idnhost = {0};
78stralloc header = {0}; 79stralloc header = {0};
79#endif 80#endif
80 81
@@ -244,6 +245,15 @@ unsigned long smtpcode()
244saa ehlokw = {0}; /* list of EHLO keywords and parameters */ 245saa ehlokw = {0}; /* list of EHLO keywords and parameters */
245int maxehlokwlen = 0; 246int maxehlokwlen = 0;
246 247
248/* look for <capa> among EHLO keywords */
249int get_capa(const char *capa)
250{
251 stralloc *sa = ehlokw.sa;
252 unsigned int len = ehlokw.len;
253 for (; len && case_diffs(sa->s, capa); ++sa, --len);
254 return len > 0;
255}
256
247unsigned long ehlo() 257unsigned long ehlo()
248{ 258{
249 stralloc *sa; 259 stralloc *sa;
@@ -473,12 +483,13 @@ static int tls_init(unsigned long code, struct ip_mx *current_mx)
473 tls_required = 1; 483 tls_required = 1;
474 } 484 }
475 485
476 if (!smtps) {
477 stralloc *sa = ehlokw.sa; 486 stralloc *sa = ehlokw.sa;
478 unsigned int len = ehlokw.len; 487 unsigned int len = ehlokw.len;
479 /* look for STARTTLS among EHLO keywords */
480 for ( ; len && case_diffs(sa->s, "STARTTLS"); ++sa, --len) ; 488 for ( ; len && case_diffs(sa->s, "STARTTLS"); ++sa, --len) ;
481 if (!len) { 489 /* look for STARTTLS among EHLO keywords */
490
491 if (!smtps) {
492 if (!get_capa("STARTTLS")) {
482 if (!tls_required) return 0; 493 if (!tls_required) return 0;
483 out(failure_class); 494 out(failure_class);
484 out("TLS is required, but was not offered by host"); 495 out("TLS is required, but was not offered by host");
@@ -682,9 +693,6 @@ static int tls_init(unsigned long code, struct ip_mx *current_mx)
682 } 693 }
683 694
684 if (servercert) { 695 if (servercert) {
685 X509 *peercert;
686 STACK_OF(GENERAL_NAME) *gens;
687
688 int r = SSL_get_verify_result(myssl); 696 int r = SSL_get_verify_result(myssl);
689 if (r != X509_V_OK) { 697 if (r != X509_V_OK) {
690 out("ZTLS unable to verify server with "); 698 out("ZTLS unable to verify server with ");
@@ -724,41 +732,74 @@ int utf8string(unsigned char *ch, int len)
724 return 0; 732 return 0;
725} 733}
726 734
727int utf8received() 735void checkutf8message()
728{ 736{
729 int r, i, received = 0; 737 if (utf8string((unsigned char *)sender.s, sender.len)) {
730 char ch; 738 utf8message = 1;
731 stralloc receivedline = {0}; 739 return;
740 }
741 for (unsigned int i = 0; i < reciplist.len; ++i) {
742 if (utf8string((unsigned char *)reciplist.sa[i].s, reciplist.sa[i].len)) {
743 utf8message = 1;
744 return;
745 }
746 }
732 747
733 for (;;) { /* we consider only our own last written header */ 748 int state = 0;
734 r = substdio_get(&ssin,&ch,1); 749 int pos = 0;
750 for (;;) {
751 char ch;
752 int r = substdio_get(&ssin, &ch, 1);
735 if (r == 0) break; 753 if (r == 0) break;
736 if (r == -1) temp_read(); 754 if (r == -1) temp_read();
737 755
738 if (ch == '\n' && receivedline.len) { 756 if (ch == '\n' && !stralloc_cats(&header, "\r")) temp_nomem(); /* received.c does not add '\r' */
739 if (!stralloc_append(&header,"\r")) temp_nomem(); /* received.c does not add '\r' */ 757 if (!stralloc_append(&header, &ch)) temp_nomem();
740 if (!stralloc_append(&header,"\n")) temp_nomem(); 758 if (ch == '\r')
741 if (case_startb(receivedline.s,5,"Date:")) return 0; /* header to quit asap */ 759 continue;
742 if (case_startb(receivedline.s,14,"Received: from")) received++; /* found Received header */ 760 if ((unsigned char)ch > 127)
743 if (received) { 761 utf8message = 1;
744 if (case_startb(receivedline.s,5," by ")) { 762 if (utf8message) {
745 for (i = 6; i < receivedline.len-6; ++i) 763 /* no need to look any further */
746 if (*(receivedline.s+i) == ' ') 764 if (ch == '\n') return; /* end of header */
747 if (case_startb(receivedline.s+i+1,9,"with UTF8")) return 1; 765 continue;
748 return 0; 766 }
749 } 767 if (ch == '\t')
750 } 768 ch = ' ';
751 if (!stralloc_copys(&receivedline,"")) temp_nomem(); 769
752 } else if (ch == '\n' && !receivedline.len) { /* we got an empty newline. probably body start */ 770 switch (state) {
753 if (!stralloc_append(&header,"\r")) temp_nomem(); 771 case 6: /* in Received, at LF but before WITH clause */
754 if (!stralloc_append(&header,"\n")) temp_nomem(); 772 if (ch == ' ') { state = 3; pos = 1; continue; }
755 return 0; 773 state = 0;
756 } else { 774 /* FALL THROUGH */
757 if (!stralloc_append(&header,&ch)) temp_nomem(); 775 case 0: /* start of header field */
758 if (!stralloc_catb(&receivedline,&ch,1)) temp_nomem(); 776 if (ch == '\n') return; /* end of headers */
777 state = 1;
778 pos = 0;
779 /* FALL THROUGH */
780 case 1: /* partway through "Received:" */
781 if (ch != "RECEIVED:"[pos] && ch != "received:"[pos]) { state = 2; continue; }
782 if (++pos == 9) { state = 3; pos = 0; }
783 continue;
784 case 2: /* other header field */
785 if (ch == '\n') state = 0;
786 continue;
787 case 3: /* in Received, before WITH clause or partway though " with " */
788 if (ch == '\n') { state = 6; continue; }
789 if (ch != " WITH "[pos] && ch != " with "[pos]) { pos = 0; continue; }
790 if (++pos == 6) { state = 4; pos = 0; }
791 continue;
792 case 4: /* in Received, having seen with, before the argument */
793 if (pos == 0 && (ch == ' ' || ch == '\t')) continue;
794 if (ch != "UTF8"[pos] && ch != "utf8"[pos]) { state = 5; continue; }
795 if (++pos == 4) { withutf8 = 1; state = 5; continue; }
796 continue;
797 case 5: /* after the RECEIVED WITH argument */
798 if (ch == '\n') state = 0;
799 /* blast() assumes that it copies whole lines */
800 continue;
759 } 801 }
760 } 802 }
761 return 0;
762} 803}
763#endif 804#endif
764 805
@@ -768,7 +809,7 @@ void mail_without_auth()
768 substdio_put(&smtpto,sender.s,sender.len); 809 substdio_put(&smtpto,sender.s,sender.len);
769 substdio_put(&smtpto,">",1); 810 substdio_put(&smtpto,">",1);
770#ifdef SMTPUTF8 811#ifdef SMTPUTF8
771 if (flagutf8 || utf8received()) 812 if (utf8message || withutf8)
772 substdio_puts(&smtpto, " SMTPUTF8"); 813 substdio_puts(&smtpto, " SMTPUTF8");
773#endif 814#endif
774 substdio_puts(&smtpto,"\r\n"); 815 substdio_puts(&smtpto,"\r\n");
@@ -826,6 +867,17 @@ static void smtp(struct ip_mx *current_mx)
826 if (code != 250) quit("ZConnected to "," but my name was rejected"); 867 if (code != 250) quit("ZConnected to "," but my name was rejected");
827 } 868 }
828 869
870#ifdef SMTPUTF8
871 checkutf8message();
872 int can_utf8 = get_capa("SMTPUTF8");
873 if (utf8message && !can_utf8) {
874 smtptext.len = 0;
875 quit("DConnected to "," but the remote host does not support SMTPUTF8 and the message requires it");
876 }
877 if (!utf8message && withutf8 && !can_utf8)
878 withutf8 = 0; // downgrade
879#endif
880
829 int i = 0; 881 int i = 0;
830 if (auth_smtp_user.len && auth_smtp_pass.len) { 882 if (auth_smtp_user.len && auth_smtp_pass.len) {
831 while((i += str_chr(smtptext.s+i,'\n') + 1) && 883 while((i += str_chr(smtptext.s+i,'\n') + 1) &&
@@ -857,7 +909,7 @@ static void smtp(struct ip_mx *current_mx)
857 substdio_put(&smtpto,sender.s,sender.len); 909 substdio_put(&smtpto,sender.s,sender.len);
858 substdio_put(&smtpto,">",1); 910 substdio_put(&smtpto,">",1);
859#ifdef SMTPUTF8 911#ifdef SMTPUTF8
860 if (flagutf8 || utf8received()) 912 if (utf8message || withutf8)
861 substdio_puts(&smtpto, " SMTPUTF8"); 913 substdio_puts(&smtpto, " SMTPUTF8");
862#endif 914#endif
863 substdio_puts(&smtpto,"\r\n"); 915 substdio_puts(&smtpto,"\r\n");
@@ -1032,11 +1084,6 @@ char *s;
1032{ 1084{
1033 int j; 1085 int j;
1034 1086
1035#ifdef SMTPUTF8
1036 if (!flagutf8)
1037 flagutf8 = utf8string(s, str_len(s));
1038#endif
1039
1040 j = str_rchr(s,'@'); 1087 j = str_rchr(s,'@');
1041 if (!s[j]) { 1088 if (!s[j]) {
1042 if (!stralloc_copys(saout,s)) temp_nomem(); 1089 if (!stralloc_copys(saout,s)) temp_nomem();
@@ -1124,7 +1171,7 @@ char **argv;
1124 char *asciihost = 0; 1171 char *asciihost = 0;
1125 if (!stralloc_0(&host)) temp_nomem(); 1172 if (!stralloc_0(&host)) temp_nomem();
1126 --host.len; 1173 --host.len;
1127 switch (idn2_lookup_u8(host.s,(uint8_t**)&asciihost,IDN2_NFC_INPUT)) { 1174 switch (idn2_lookup_u8((uint8_t*)host.s,(uint8_t**)&asciihost,IDN2_NFC_INPUT)) {
1128 case IDN2_OK: break; 1175 case IDN2_OK: break;
1129 case IDN2_MALLOC: temp_nomem(); 1176 case IDN2_MALLOC: temp_nomem();
1130 default: perm_dns(); 1177 default: perm_dns();