From 675c6cf562bcde842d663730a1be695e1330fc61 Mon Sep 17 00:00:00 2001 From: Arthur de Jong Date: Sat, 17 Jun 2017 16:14:10 +0200 Subject: [PATCH 1/2] Implement UTF-8 string normalisation This will use the unistring library, if available, to convert all strings to UTF-8 compatibility composed form before using them in LDAP search filters. It will also perform string comparisons of returned values from LDAP ignoring unicode normalisation. It will fallback to "normal" string operations if UTF-8 handling fails or unistring is not available. --- configure.ac | 5 ++++ nslcd/Makefile.am | 3 ++- nslcd/alias.c | 4 +-- nslcd/common.h | 5 ++-- nslcd/ether.c | 2 +- nslcd/myldap.c | 26 +++++++++++++++--- nslcd/unistr.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ nslcd/unistr.h | 52 +++++++++++++++++++++++++++++++++++ tests/Makefile.am | 1 + 9 files changed, 169 insertions(+), 10 deletions(-) create mode 100644 nslcd/unistr.c create mode 100644 nslcd/unistr.h diff --git a/configure.ac b/configure.ac index 564f9c8..9f2a860 100644 --- a/configure.ac +++ b/configure.ac @@ -993,6 +993,11 @@ then fi fi + # check unicode conversion functions + AC_CHECK_HEADERS([uninorm.h unicase.h]) + AC_SEARCH_LIBS([u8_normalize], [unistring]) + AC_CHECK_FUNCS([u8_normalize u8_normcmp u8_casecmp]) + # save nslcd LIBS and CFLAGS and restore originals nslcd_CFLAGS="$CFLAGS" nslcd_LIBS="$LIBS" diff --git a/nslcd/Makefile.am b/nslcd/Makefile.am index a17b4ce..5803a9b 100644 --- a/nslcd/Makefile.am +++ b/nslcd/Makefile.am @@ -1,7 +1,7 @@ # Makefile.am - use automake to generate Makefile.in # # Copyright (C) 2006-2007 West Consulting -# Copyright (C) 2006-2014 Arthur de Jong +# Copyright (C) 2006-2017 Arthur de Jong # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public @@ -31,6 +31,7 @@ nslcd_SOURCES = nslcd.c ../nslcd.h ../common/nslcd-prot.h \ myldap.c myldap.h \ cfg.c cfg.h \ attmap.c attmap.h \ + unistr.c unistr.h \ nsswitch.c invalidator.c \ config.c alias.c ether.c group.c host.c netgroup.c network.c \ passwd.c protocol.c rpc.c service.c shadow.c pam.c usermod.c diff --git a/nslcd/alias.c b/nslcd/alias.c index 7d6b978..70222e5 100644 --- a/nslcd/alias.c +++ b/nslcd/alias.c @@ -5,7 +5,7 @@ Copyright (C) 1997-2005 Luke Howard Copyright (C) 2006 West Consulting - Copyright (C) 2006-2014 Arthur de Jong + Copyright (C) 2006-2017 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -110,7 +110,7 @@ static int write_alias(TFILE *fp, MYLDAP_ENTRY *entry, const char *reqalias) /* for each name, write an entry */ for (i = 0; names[i] != NULL; i++) { - if ((reqalias == NULL) || (strcasecmp(reqalias, names[i]) == 0)) + if ((reqalias == NULL) || (uni_strcasecmp(reqalias, names[i]) == 0)) { WRITE_INT32(fp, NSLCD_RESULT_BEGIN); WRITE_STRING(fp, names[i]); diff --git a/nslcd/common.h b/nslcd/common.h index ffa07ba..2aedc1b 100644 --- a/nslcd/common.h +++ b/nslcd/common.h @@ -3,7 +3,7 @@ This file is part of the nss-pam-ldapd library. Copyright (C) 2006 West Consulting - Copyright (C) 2006-2014 Arthur de Jong + Copyright (C) 2006-2017 Arthur de Jong This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -35,6 +35,7 @@ #include "common/nslcd-prot.h" #include "common/tio.h" #include "compat/attrs.h" +#include "unistr.h" #include "myldap.h" #include "cfg.h" @@ -310,6 +311,6 @@ int nslcd_usermod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid); determine whether or not to do a case-sensitive match */ #define STR_CMP(str1, str2) \ (nslcd_cfg->ignorecase == 1 ? \ - strcasecmp(str1, str2) : strcmp(str1, str2)) + uni_strcasecmp(str1, str2) : uni_strcmp(str1, str2)) #endif /* not NSLCD__COMMON_H */ diff --git a/nslcd/ether.c b/nslcd/ether.c index 16117a5..e0e6374 100644 --- a/nslcd/ether.c +++ b/nslcd/ether.c @@ -159,7 +159,7 @@ static int write_ether(TFILE *fp, MYLDAP_ENTRY *entry, } /* write entries for all names and addresses */ for (i = 0; names[i] != NULL; i++) - if ((reqname == NULL) || (strcasecmp(reqname, names[i]) == 0)) + if ((reqname == NULL) || (uni_strcasecmp(reqname, names[i]) == 0)) for (j = 0; ethers[j] != NULL; j++) { WRITE_INT32(fp, NSLCD_RESULT_BEGIN); diff --git a/nslcd/myldap.c b/nslcd/myldap.c index 6b98a21..8fbc8f9 100644 --- a/nslcd/myldap.c +++ b/nslcd/myldap.c @@ -2363,14 +2363,31 @@ const char ***myldap_get_deref_values(MYLDAP_ENTRY UNUSED(*entry), int myldap_escape(const char *src, char *buffer, size_t buflen) { size_t pos = 0; - /* go over all characters in source string */ - for (; *src != '\0'; src++) + char *tmpbuf, *norm; + /* normalise src value in temporary buffer */ + tmpbuf = (char *)malloc(buflen); + if (tmpbuf == NULL) + { + log_log(LOG_CRIT, "myldap_escape(): malloc() failed to allocate memory"); + exit(EXIT_FAILURE); + } + norm = uni_norm(src, tmpbuf, buflen); + if (norm == NULL) + { + free(tmpbuf); + return -1; + } + /* go over all characters in normalised string */ + for (; *norm != '\0'; norm++) { /* check if char will fit */ if ((pos + 4) >= buflen) + { + free(tmpbuf); return -1; + } /* do escaping for some characters */ - switch (*src) + switch (*norm) { case '*': strcpy(buffer + pos, "\\2a"); @@ -2390,12 +2407,13 @@ int myldap_escape(const char *src, char *buffer, size_t buflen) break; default: /* just copy character */ - buffer[pos++] = *src; + buffer[pos++] = *norm; break; } } /* terminate destination string */ buffer[pos] = '\0'; + free(tmpbuf); return 0; } diff --git a/nslcd/unistr.c b/nslcd/unistr.c new file mode 100644 index 0000000..0a17499 --- /dev/null +++ b/nslcd/unistr.c @@ -0,0 +1,81 @@ +/* + unistr.c - functions for performing string comparisons + + Copyright (C) 2017 Arthur de Jong + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +#include "config.h" + +#include +#include +#ifdef HAVE_UNINORM_H +#include +#endif /* HAVE_UNINORM_H */ +#ifdef HAVE_UNICASE_H +#include +#endif /* HAVE_UNICASE_H */ + +#include "unistr.h" + +/* Normalise the passed string and return it in the provided buffer. */ +char *uni_norm(const char *str, char *buffer, size_t buflen) +{ +#ifdef HAVE_U8_NORMALIZE + size_t l = buflen - 1; + memset(buffer, 0, buflen); + /* normalise the string to compatibility composed form */ + if (u8_normalize(UNINORM_NFKC, (const uint8_t *)str, strlen(str), + (uint8_t *)buffer, &l) != NULL) + return buffer; +#endif /* HAVE_U8_NORMALIZE */ + /* fall back to just copying the string */ + if (buflen < (strlen(str) + 1)) + return NULL; /* buffer not large enough */ + strcpy(buffer, str); + return buffer; +} + +/* String comparison, similar to strcmp() but performs normalisation. */ +int uni_strcmp(const char *s1, const char *s2) +{ +#ifdef HAVE_U8_NORMCMP + int result; + /* compare strings, ignoring differences in normalisation */ + if (u8_normcmp((const uint8_t *)s1, strlen(s1), + (const uint8_t *)s2, strlen(s2), + UNINORM_NFKD, &result) == 0) + return result; +#endif /* HAVE_U8_NORMCMP */ + /* fall back to normal strcmp() */ + return strcmp(s1, s2); +} + +/* String comparison, similar to strcasecmp() that performs normalisation. */ +int uni_strcasecmp(const char *s1, const char *s2) +{ +#ifdef HAVE_U8_CASECMP + int result; + /* compare strings, ignoring differences in case and normalisation */ + if (u8_casecmp((const uint8_t *)s1, strlen(s1), + (const uint8_t *)s2, strlen(s2), + NULL, UNINORM_NFKD, &result) == 0) + return result; +#endif /* HAVE_U8_CASECMP */ + /* fall back to normal strcasecmp() */ + return strcasecmp(s1, s2); +} diff --git a/nslcd/unistr.h b/nslcd/unistr.h new file mode 100644 index 0000000..b7a9203 --- /dev/null +++ b/nslcd/unistr.h @@ -0,0 +1,52 @@ +/* + unistr.h - functions for performing string comparisons + + Copyright (C) 2017 Arthur de Jong + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA +*/ + +/* + We provide some functions for performing string operations and Unicode + normalisation. The LDAP server is supposed to handle UTF-8 strings and + expects the strings to be in Unicode KC (compatibility composed) form. + + https://tools.ietf.org/html/rfc4517#section-3.3.6 + https://tools.ietf.org/html/rfc4518#section-2.3 + + Since the PAM and NSS modules just pass around bytestrings without dealing + with encoding we have to assume that those strings are already UTF-8 + encoded. + + What is currently not implemented but may be needed is removing certain + code points from the string. +*/ + +#ifndef NSLCD__UNISTR_H +#define NSLCD__UNISTR_H 1 + +#include "compat/attrs.h" + +/* Normalise the passed string and return it in the provided buffer. */ +MUST_USE char *uni_norm(const char *str, char *buffer, size_t buflen); + +/* String comparison, similar to strcmp() that performs normalisation. */ +MUST_USE int uni_strcmp(const char *s1, const char *s2); + +/* String comparison, similar to strcasecmp() that performs normalisation. */ +MUST_USE int uni_strcasecmp(const char *s1, const char *s2); + +#endif /* not NSLCD__UNISTR_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 0a7854e..4b6b893 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -75,6 +75,7 @@ common_nslcd_LDADD = ../nslcd/log.o ../nslcd/common.o ../nslcd/invalidator.o \ ../nslcd/host.o ../nslcd/netgroup.o ../nslcd/network.o \ ../nslcd/passwd.o ../nslcd/protocol.o ../nslcd/rpc.o \ ../nslcd/service.o ../nslcd/shadow.o ../nslcd/pam.o \ + ../nslcd/unistr.o \ ../common/libtio.a ../common/libdict.a \ ../common/libexpr.a ../compat/libcompat.a \ @nslcd_LIBS@ @PTHREAD_LIBS@ -- 2.11.0