Skip to content

Commit

Permalink
Use Libidn2 to translate U-labels to A-labels
Browse files Browse the repository at this point in the history
  • Loading branch information
flowerysong committed Oct 24, 2024
1 parent 816db23 commit 41f1f29
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
run: sudo apt update

- name: Install dependencies
run: sudo apt install libbsd-dev libmilter-dev libssl-dev
run: sudo apt install libbsd-dev libidn2-dev libmilter-dev libssl-dev

- name: Build dist tarball
run: |
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ All notable changes to this project will be documented in this file.
`--with-libjansson=/path` to `configure`.
- Building the milter defaults to requiring Jansson. You can explicitly
disable it by passing `--without-libjansson` to `configure`.
- Libidn2 is now required to build OpenARC.
- libopenarc - `ARC-Message-Signature` and `ARC-Authentication-Results` headers
are excluded from the AMS, as required by RFC 8617.
- libopenarc - ARC headers are returned with a space before the header value.
Expand All @@ -29,6 +30,8 @@ All notable changes to this project will be documented in this file.
- libopenarc - ARC headers with a misplaced instance tag are rejected.
- libopenarc - unlikely memory leak after memory allocation failures.
- libopenarc - The installed pkg-config file is more correct.
- libopenarc - U-labels (domain labels encoded as UTF-8) are allowed in `d=`
and `s=` tags.
- openarc - use after free.
- openarc - unlikely division by zero.
- openarc - small memory leak during config loading.
Expand Down
8 changes: 4 additions & 4 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ libopenarc_libopenarc_la_SOURCES = \
libopenarc/arc-util.h \
util/arc-dstring.c \
util/arc-dstring.h
libopenarc_libopenarc_la_CPPFLAGS = -I$(srcdir)/util $(OPENSSL_CFLAGS)
libopenarc_libopenarc_la_CPPFLAGS = -I$(srcdir)/util $(OPENSSL_CFLAGS) $(LIBIDN2_CFLAGS)
libopenarc_libopenarc_la_LDFLAGS = -no-undefined -version-info $(LIBOPENARC_VERSION_INFO)
libopenarc_libopenarc_la_LIBADD = $(OPENSSL_LIBS)
libopenarc_libopenarc_la_LIBADD = $(OPENSSL_LIBS) $(LIBIDN2_LIBS)
if !ALL_SYMBOLS
libopenarc_libopenarc_la_DEPENDENCIES = libopenarc/symbols.map
libopenarc_libopenarc_la_LDFLAGS += -export-symbols libopenarc/symbols.map
Expand Down Expand Up @@ -88,9 +88,9 @@ openarc_openarc_SOURCES = \
util/arc-dstring.h
openarc_openarc_CC = $(PTHREAD_CC)
openarc_openarc_CFLAGS = $(PTHREAD_CFLAGS)
openarc_openarc_CPPFLAGS = -I$(srcdir)/libopenarc -I$(srcdir)/util $(OPENSSL_CFLAGS) $(LIBMILTER_INCDIRS) $(LIBJANSSON_CFLAGS)
openarc_openarc_CPPFLAGS = -I$(srcdir)/libopenarc -I$(srcdir)/util $(OPENSSL_CFLAGS) $(LIBIDN2_CFLAGS) $(LIBMILTER_INCDIRS) $(LIBJANSSON_CFLAGS)
openarc_openarc_LDFLAGS = $(LIBMILTER_LIBDIRS) $(PTHREAD_CFLAGS)
openarc_openarc_LDADD = libopenarc/libopenarc.la $(LIBMILTER_LIBS) $(OPENSSL_LIBS) $(PTHREAD_LIBS) $(LIBJANSSON_LIBS) $(LIBRESOLV)
openarc_openarc_LDADD = libopenarc/libopenarc.la $(LIBMILTER_LIBS) $(OPENSSL_LIBS) $(LIBIDN2_LIBS) $(PTHREAD_LIBS) $(LIBJANSSON_LIBS) $(LIBRESOLV)
endif

$(DIST_ARCHIVES).sha1: $(DIST_ARCHIVES)
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,16 @@ file to determine which license(s) are applicable to that file.

In order to build OpenARC, you will need:

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

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

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

```
$ dnf install autoconf automake gcc jansson-devel libbsd-devel libtool openssl-devel sendmail-milter-devel
$ dnf install autoconf automake gcc jansson-devel libbsd-devel libidn2-devel libtool openssl-devel sendmail-milter-devel
```

### Ubuntu

```
$ apt install build-essential libbsd-dev libjansson-dev libmilter-dev libssl-dev
$ apt install build-essential libbsd-dev libidn2-dev libjansson-dev libmilter-dev libssl-dev
```

## Installation
Expand Down
8 changes: 8 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,14 @@ PKG_CHECK_MODULES([OPENSSL], [openssl >= 1.0.0])
AC_SUBST(OPENSSL_CFLAGS)
AC_SUBST(OPENSSL_LIBS)

#
# Libidn2
#

PKG_CHECK_MODULES([LIBIDN2], [libidn2])
AC_SUBST(LIBIDN2_CFLAGS)
AC_SUBST(LIBIDN2_LIBS)

#
# libmilter
#
Expand Down
48 changes: 40 additions & 8 deletions libopenarc/arc-keys.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
#include "arc-types.h"
#include "arc-util.h"

/* libidn2 */
#include <idn2.h>

/* libbsd if found */
#ifdef USE_BSD_H
#include <bsd/string.h>
Expand Down Expand Up @@ -85,7 +88,7 @@ arc_get_key_dns(ARC_MESSAGE *msg, char *buf, size_t buflen)
unsigned char *cp;
unsigned char *eom;
char *eob;
unsigned char qname[ARC_MAXHOSTNAMELEN + 1];
char qname[ARC_MAXHOSTNAMELEN + 1];
unsigned char ansbuf[MAXPACKET];
struct timeval timeout;
HEADER hdr;
Expand All @@ -96,14 +99,32 @@ arc_get_key_dns(ARC_MESSAGE *msg, char *buf, size_t buflen)

lib = msg->arc_library;

n = snprintf((char *) qname, sizeof qname - 1, "%s.%s.%s",
msg->arc_selector, ARC_DNSKEYNAME, msg->arc_domain);
n = snprintf(qname, sizeof qname - 1, "%s.%s.%s", msg->arc_selector,
ARC_DNSKEYNAME, msg->arc_domain);
if (n == -1 || n > sizeof qname - 1)
{
arc_error(msg, "key query name too large");
return ARC_STAT_NORESOURCE;
}

char *qname_idn;
status = idn2_to_ascii_8z(qname, &qname_idn,
IDN2_NONTRANSITIONAL | IDN2_NFC_INPUT);
if (status != IDN2_OK)
{
arc_error(msg, "failed to translate %s to ASCII: %s", qname,
idn2_strerror(status));
return ARC_STAT_KEYFAIL;
}

if (strlcpy(qname, qname_idn, sizeof qname) >= sizeof qname)
{
arc_error(msg, "key query name too large");
idn2_free(qname_idn);
return ARC_STAT_NORESOURCE;
}
idn2_free(qname_idn);

anslen = sizeof ansbuf;

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

status = lib->arcl_dns_start(lib->arcl_dns_service, T_TXT, qname, ansbuf,
anslen, &q);
status = lib->arcl_dns_start(lib->arcl_dns_service, T_TXT,
(unsigned char *) qname, ansbuf, anslen, &q);

if (status != 0)
{
Expand Down Expand Up @@ -199,7 +220,7 @@ arc_get_key_dns(ARC_MESSAGE *msg, char *buf, size_t buflen)
for (qdcount = ntohs((unsigned short) hdr.qdcount); qdcount > 0; qdcount--)
{
/* copy it first */
(void) dn_expand((unsigned char *) &ansbuf, eom, cp, (char *) qname,
(void) dn_expand((unsigned char *) &ansbuf, eom, cp, qname,
sizeof qname);

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

char *idn_name;
if (idn2_to_ascii_8z(name, &idn_name,
IDN2_NONTRANSITIONAL | IDN2_NFC_INPUT) != IDN2_OK)
{
arc_error(msg, "failed to translate %s to ASCII", name);
fclose(f);
return ARC_STAT_KEYFAIL;
}

memset(buf, '\0', buflen);
while (fgets((char *) buf, buflen, f) != NULL)
while (fgets(buf, buflen, f) != NULL)
{
if (buf[0] == '#')
{
Expand All @@ -436,14 +466,16 @@ arc_get_key_file(ARC_MESSAGE *msg, char *buf, size_t buflen)
}
}

if (strcasecmp((char *) name, (char *) buf) == 0 && p2 != NULL)
if (strcasecmp(idn_name, buf) == 0 && p2 != NULL)
{
memmove(buf, p2, strlen(p2) + 1);
idn2_free(idn_name);
fclose(f);
return ARC_STAT_OK;
}
}

idn2_free(idn_name);
fclose(f);

return ARC_STAT_NOKEY;
Expand Down
7 changes: 4 additions & 3 deletions libopenarc/arc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1576,8 +1576,9 @@ arc_process_set(ARC_MESSAGE *msg,

for (p = hcopy; *p != '\0' && !stop; p++)
{
if (!isascii(*p) || (!isprint(*p) && !isspace(*p)))
if (isascii(*p) && !isprint(*p) && !isspace(*p))
{
/* FIXME: should this do more validation of UTF-8? */
arc_error(
msg, "invalid character (ASCII 0x%02x at offset %d) in %s data",
*p, p - hcopy, settype);
Expand All @@ -1600,7 +1601,7 @@ arc_process_set(ARC_MESSAGE *msg,
else
{
arc_error(msg,
"syntax error in %s data (ASCII 0x%02x at offset %d)",
"syntax error in %s data (char 0x%02x at offset %d)",
settype, *p, p - hcopy);
set->set_bad = true;
return ARC_STAT_SYNTAX;
Expand All @@ -1621,7 +1622,7 @@ arc_process_set(ARC_MESSAGE *msg,
else if (*p == ';' || spaced)
{
arc_error(msg,
"syntax error in %s data (ASCII 0x%02x at offset %d)",
"syntax error in %s data (char 0x%02x at offset %d)",
settype, *p, p - hcopy);
set->set_bad = true;
return ARC_STAT_SYNTAX;
Expand Down
2 changes: 1 addition & 1 deletion libopenarc/openarc.pc.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ URL: https://github.com/flowerysong/OpenARC
Version: @VERSION@
Libs: -L${libdir} -lopenarc
Libs.private: @STRL_LIBS@
Requires.private: openssl >= 1.0.0
Requires.private: openssl >= 1.0.0, libidn2
Cflags: -I${includedir}
1 change: 1 addition & 0 deletions test/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def private_key(scope='session'):
'ZuhB/1/U3f1oG3Upx5o/jXTQk/dwVaaeEXnRmTsfGYn4GQ9ziity1ijLsQIDAQAB\n'
)
f.write(f'elpmaxe._domainkey.example.com v=DKIM1; k=rsa; h=sha256; p={key}\n')
f.write(f'xn--2j5b._domainkey.xn--vv4b606a.example.com v=DKIM1; k=rsa; h=sha256; p={key}\n')


@pytest.fixture()
Expand Down
7 changes: 7 additions & 0 deletions test/files/test_milter_idna.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Domain 시험.example.com
AuthservID example.com
KeyFile private.key
TestKeys public.key
Selector 예
Mode s
FixedTimestamp 1234567890
11 changes: 11 additions & 0 deletions test/test_milter.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,17 @@ def test_milter_seal_failed(run_miltertest):
assert res1['headers'] == res2['headers']


def test_milter_idna(run_miltertest):
"""U-labels in domains and selectors"""
res = run_miltertest()

assert res['headers'][1][1].startswith(' i=1; a=rsa-sha256; d=시험.example.com; s=예;')

res = run_miltertest(res['headers'])
assert 'cv=pass' in res['headers'][0][1]
assert res['headers'][2] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass smtp.remote-ip=127.0.0.1']


def test_milter_authresip(run_miltertest):
"""AuthResIP false disables smtp.remote-ip"""
res = run_miltertest()
Expand Down

0 comments on commit 41f1f29

Please sign in to comment.