Skip to content

Commit 41f1f29

Browse files
committed
Use Libidn2 to translate U-labels to A-labels
1 parent 816db23 commit 41f1f29

File tree

11 files changed

+88
-19
lines changed

11 files changed

+88
-19
lines changed

.github/workflows/build-dist.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
run: sudo apt update
1818

1919
- name: Install dependencies
20-
run: sudo apt install libbsd-dev libmilter-dev libssl-dev
20+
run: sudo apt install libbsd-dev libidn2-dev libmilter-dev libssl-dev
2121

2222
- name: Build dist tarball
2323
run: |

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file.
1717
`--with-libjansson=/path` to `configure`.
1818
- Building the milter defaults to requiring Jansson. You can explicitly
1919
disable it by passing `--without-libjansson` to `configure`.
20+
- Libidn2 is now required to build OpenARC.
2021
- libopenarc - `ARC-Message-Signature` and `ARC-Authentication-Results` headers
2122
are excluded from the AMS, as required by RFC 8617.
2223
- libopenarc - ARC headers are returned with a space before the header value.
@@ -29,6 +30,8 @@ All notable changes to this project will be documented in this file.
2930
- libopenarc - ARC headers with a misplaced instance tag are rejected.
3031
- libopenarc - unlikely memory leak after memory allocation failures.
3132
- libopenarc - The installed pkg-config file is more correct.
33+
- libopenarc - U-labels (domain labels encoded as UTF-8) are allowed in `d=`
34+
and `s=` tags.
3235
- openarc - use after free.
3336
- openarc - unlikely division by zero.
3437
- openarc - small memory leak during config loading.

Makefile.am

+4-4
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ libopenarc_libopenarc_la_SOURCES = \
4343
libopenarc/arc-util.h \
4444
util/arc-dstring.c \
4545
util/arc-dstring.h
46-
libopenarc_libopenarc_la_CPPFLAGS = -I$(srcdir)/util $(OPENSSL_CFLAGS)
46+
libopenarc_libopenarc_la_CPPFLAGS = -I$(srcdir)/util $(OPENSSL_CFLAGS) $(LIBIDN2_CFLAGS)
4747
libopenarc_libopenarc_la_LDFLAGS = -no-undefined -version-info $(LIBOPENARC_VERSION_INFO)
48-
libopenarc_libopenarc_la_LIBADD = $(OPENSSL_LIBS)
48+
libopenarc_libopenarc_la_LIBADD = $(OPENSSL_LIBS) $(LIBIDN2_LIBS)
4949
if !ALL_SYMBOLS
5050
libopenarc_libopenarc_la_DEPENDENCIES = libopenarc/symbols.map
5151
libopenarc_libopenarc_la_LDFLAGS += -export-symbols libopenarc/symbols.map
@@ -88,9 +88,9 @@ openarc_openarc_SOURCES = \
8888
util/arc-dstring.h
8989
openarc_openarc_CC = $(PTHREAD_CC)
9090
openarc_openarc_CFLAGS = $(PTHREAD_CFLAGS)
91-
openarc_openarc_CPPFLAGS = -I$(srcdir)/libopenarc -I$(srcdir)/util $(OPENSSL_CFLAGS) $(LIBMILTER_INCDIRS) $(LIBJANSSON_CFLAGS)
91+
openarc_openarc_CPPFLAGS = -I$(srcdir)/libopenarc -I$(srcdir)/util $(OPENSSL_CFLAGS) $(LIBIDN2_CFLAGS) $(LIBMILTER_INCDIRS) $(LIBJANSSON_CFLAGS)
9292
openarc_openarc_LDFLAGS = $(LIBMILTER_LIBDIRS) $(PTHREAD_CFLAGS)
93-
openarc_openarc_LDADD = libopenarc/libopenarc.la $(LIBMILTER_LIBS) $(OPENSSL_LIBS) $(PTHREAD_LIBS) $(LIBJANSSON_LIBS) $(LIBRESOLV)
93+
openarc_openarc_LDADD = libopenarc/libopenarc.la $(LIBMILTER_LIBS) $(OPENSSL_LIBS) $(LIBIDN2_LIBS) $(PTHREAD_LIBS) $(LIBJANSSON_LIBS) $(LIBRESOLV)
9494
endif
9595

9696
$(DIST_ARCHIVES).sha1: $(DIST_ARCHIVES)

README.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,16 @@ file to determine which license(s) are applicable to that file.
3535

3636
In order to build OpenARC, you will need:
3737

38+
* A C compiler. Compilation has been tested with [GCC](https://gcc.gnu.org/)
39+
and [clang](https://clang.llvm.org/), and other modern compilers should also
40+
work.
41+
* make
42+
* pkg-config or a compatible replacement.
3843
* [OpenSSL](https://openssl.org) >= 1.0.0
3944
* Native implementations of `strlcat()` and `strlcpy()`,
4045
[libbsd](https://libbsd.freedesktop.org/), or some other library that
4146
provides them.
47+
* [Libidn2](https://gitlab.com/libidn/libidn2)
4248

4349
If you are building the filter, you will also need:
4450

@@ -57,13 +63,13 @@ you will also need:
5763
### DNF-based systems
5864

5965
```
60-
$ dnf install autoconf automake gcc jansson-devel libbsd-devel libtool openssl-devel sendmail-milter-devel
66+
$ dnf install autoconf automake gcc jansson-devel libbsd-devel libidn2-devel libtool openssl-devel sendmail-milter-devel
6167
```
6268

6369
### Ubuntu
6470

6571
```
66-
$ apt install build-essential libbsd-dev libjansson-dev libmilter-dev libssl-dev
72+
$ apt install build-essential libbsd-dev libidn2-dev libjansson-dev libmilter-dev libssl-dev
6773
```
6874

6975
## Installation

configure.ac

+8
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,14 @@ PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.0])
537537
AC_SUBST(OPENSSL_CFLAGS)
538538
AC_SUBST(OPENSSL_LIBS)
539539

540+
#
541+
# Libidn2
542+
#
543+
544+
PKG_CHECK_MODULES([LIBIDN2], [libidn2])
545+
AC_SUBST(LIBIDN2_CFLAGS)
546+
AC_SUBST(LIBIDN2_LIBS)
547+
540548
#
541549
# libmilter
542550
#

libopenarc/arc-keys.c

+40-8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
#include "arc-types.h"
2929
#include "arc-util.h"
3030

31+
/* libidn2 */
32+
#include <idn2.h>
33+
3134
/* libbsd if found */
3235
#ifdef USE_BSD_H
3336
#include <bsd/string.h>
@@ -85,7 +88,7 @@ arc_get_key_dns(ARC_MESSAGE *msg, char *buf, size_t buflen)
8588
unsigned char *cp;
8689
unsigned char *eom;
8790
char *eob;
88-
unsigned char qname[ARC_MAXHOSTNAMELEN + 1];
91+
char qname[ARC_MAXHOSTNAMELEN + 1];
8992
unsigned char ansbuf[MAXPACKET];
9093
struct timeval timeout;
9194
HEADER hdr;
@@ -96,14 +99,32 @@ arc_get_key_dns(ARC_MESSAGE *msg, char *buf, size_t buflen)
9699

97100
lib = msg->arc_library;
98101

99-
n = snprintf((char *) qname, sizeof qname - 1, "%s.%s.%s",
100-
msg->arc_selector, ARC_DNSKEYNAME, msg->arc_domain);
102+
n = snprintf(qname, sizeof qname - 1, "%s.%s.%s", msg->arc_selector,
103+
ARC_DNSKEYNAME, msg->arc_domain);
101104
if (n == -1 || n > sizeof qname - 1)
102105
{
103106
arc_error(msg, "key query name too large");
104107
return ARC_STAT_NORESOURCE;
105108
}
106109

110+
char *qname_idn;
111+
status = idn2_to_ascii_8z(qname, &qname_idn,
112+
IDN2_NONTRANSITIONAL | IDN2_NFC_INPUT);
113+
if (status != IDN2_OK)
114+
{
115+
arc_error(msg, "failed to translate %s to ASCII: %s", qname,
116+
idn2_strerror(status));
117+
return ARC_STAT_KEYFAIL;
118+
}
119+
120+
if (strlcpy(qname, qname_idn, sizeof qname) >= sizeof qname)
121+
{
122+
arc_error(msg, "key query name too large");
123+
idn2_free(qname_idn);
124+
return ARC_STAT_NORESOURCE;
125+
}
126+
idn2_free(qname_idn);
127+
107128
anslen = sizeof ansbuf;
108129

109130
timeout.tv_sec = msg->arc_timeout;
@@ -116,8 +137,8 @@ arc_get_key_dns(ARC_MESSAGE *msg, char *buf, size_t buflen)
116137
return ARC_STAT_KEYFAIL;
117138
}
118139

119-
status = lib->arcl_dns_start(lib->arcl_dns_service, T_TXT, qname, ansbuf,
120-
anslen, &q);
140+
status = lib->arcl_dns_start(lib->arcl_dns_service, T_TXT,
141+
(unsigned char *) qname, ansbuf, anslen, &q);
121142

122143
if (status != 0)
123144
{
@@ -199,7 +220,7 @@ arc_get_key_dns(ARC_MESSAGE *msg, char *buf, size_t buflen)
199220
for (qdcount = ntohs((unsigned short) hdr.qdcount); qdcount > 0; qdcount--)
200221
{
201222
/* copy it first */
202-
(void) dn_expand((unsigned char *) &ansbuf, eom, cp, (char *) qname,
223+
(void) dn_expand((unsigned char *) &ansbuf, eom, cp, qname,
203224
sizeof qname);
204225

205226
if ((n = dn_skipname(cp, eom)) < 0)
@@ -408,8 +429,17 @@ arc_get_key_file(ARC_MESSAGE *msg, char *buf, size_t buflen)
408429
return ARC_STAT_NORESOURCE;
409430
}
410431

432+
char *idn_name;
433+
if (idn2_to_ascii_8z(name, &idn_name,
434+
IDN2_NONTRANSITIONAL | IDN2_NFC_INPUT) != IDN2_OK)
435+
{
436+
arc_error(msg, "failed to translate %s to ASCII", name);
437+
fclose(f);
438+
return ARC_STAT_KEYFAIL;
439+
}
440+
411441
memset(buf, '\0', buflen);
412-
while (fgets((char *) buf, buflen, f) != NULL)
442+
while (fgets(buf, buflen, f) != NULL)
413443
{
414444
if (buf[0] == '#')
415445
{
@@ -436,14 +466,16 @@ arc_get_key_file(ARC_MESSAGE *msg, char *buf, size_t buflen)
436466
}
437467
}
438468

439-
if (strcasecmp((char *) name, (char *) buf) == 0 && p2 != NULL)
469+
if (strcasecmp(idn_name, buf) == 0 && p2 != NULL)
440470
{
441471
memmove(buf, p2, strlen(p2) + 1);
472+
idn2_free(idn_name);
442473
fclose(f);
443474
return ARC_STAT_OK;
444475
}
445476
}
446477

478+
idn2_free(idn_name);
447479
fclose(f);
448480

449481
return ARC_STAT_NOKEY;

libopenarc/arc.c

+4-3
Original file line numberDiff line numberDiff line change
@@ -1576,8 +1576,9 @@ arc_process_set(ARC_MESSAGE *msg,
15761576

15771577
for (p = hcopy; *p != '\0' && !stop; p++)
15781578
{
1579-
if (!isascii(*p) || (!isprint(*p) && !isspace(*p)))
1579+
if (isascii(*p) && !isprint(*p) && !isspace(*p))
15801580
{
1581+
/* FIXME: should this do more validation of UTF-8? */
15811582
arc_error(
15821583
msg, "invalid character (ASCII 0x%02x at offset %d) in %s data",
15831584
*p, p - hcopy, settype);
@@ -1600,7 +1601,7 @@ arc_process_set(ARC_MESSAGE *msg,
16001601
else
16011602
{
16021603
arc_error(msg,
1603-
"syntax error in %s data (ASCII 0x%02x at offset %d)",
1604+
"syntax error in %s data (char 0x%02x at offset %d)",
16041605
settype, *p, p - hcopy);
16051606
set->set_bad = true;
16061607
return ARC_STAT_SYNTAX;
@@ -1621,7 +1622,7 @@ arc_process_set(ARC_MESSAGE *msg,
16211622
else if (*p == ';' || spaced)
16221623
{
16231624
arc_error(msg,
1624-
"syntax error in %s data (ASCII 0x%02x at offset %d)",
1625+
"syntax error in %s data (char 0x%02x at offset %d)",
16251626
settype, *p, p - hcopy);
16261627
set->set_bad = true;
16271628
return ARC_STAT_SYNTAX;

libopenarc/openarc.pc.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@ URL: https://github.com/flowerysong/OpenARC
99
Version: @VERSION@
1010
Libs: -L${libdir} -lopenarc
1111
Libs.private: @STRL_LIBS@
12-
Requires.private: openssl >= 1.0.0
12+
Requires.private: openssl >= 1.0.0, libidn2
1313
Cflags: -I${includedir}

test/conftest.py

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def private_key(scope='session'):
3939
'ZuhB/1/U3f1oG3Upx5o/jXTQk/dwVaaeEXnRmTsfGYn4GQ9ziity1ijLsQIDAQAB\n'
4040
)
4141
f.write(f'elpmaxe._domainkey.example.com v=DKIM1; k=rsa; h=sha256; p={key}\n')
42+
f.write(f'xn--2j5b._domainkey.xn--vv4b606a.example.com v=DKIM1; k=rsa; h=sha256; p={key}\n')
4243

4344

4445
@pytest.fixture()

test/files/test_milter_idna.conf

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Domain 시험.example.com
2+
AuthservID example.com
3+
KeyFile private.key
4+
TestKeys public.key
5+
Selector 예
6+
Mode s
7+
FixedTimestamp 1234567890

test/test_milter.py

+11
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,17 @@ def test_milter_seal_failed(run_miltertest):
477477
assert res1['headers'] == res2['headers']
478478

479479

480+
def test_milter_idna(run_miltertest):
481+
"""U-labels in domains and selectors"""
482+
res = run_miltertest()
483+
484+
assert res['headers'][1][1].startswith(' i=1; a=rsa-sha256; d=시험.example.com; s=예;')
485+
486+
res = run_miltertest(res['headers'])
487+
assert 'cv=pass' in res['headers'][0][1]
488+
assert res['headers'][2] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass smtp.remote-ip=127.0.0.1']
489+
490+
480491
def test_milter_authresip(run_miltertest):
481492
"""AuthResIP false disables smtp.remote-ip"""
482493
res = run_miltertest()

0 commit comments

Comments
 (0)