Corinna Vinschen
2019-Feb-20 12:41 UTC
[PATCH 0/2] Cygwin: allow user and group case-insensitive Unicode strings
Windows usernames are case-insensitive and almost any Unicode character is allowed in a username. The user should be able to login with her username given in any case and not be refused. However, this opens up a security problem in terms of the sshd_config Match rules. The match rules for user and group names have to operate case-insensitive as well, otherwise the user can override her settings by simply changing the case at login time. Corinna Vinschen (2): Revert "[auth.c] On Cygwin, refuse usernames that have differences in case" Cygwin: implement case-insensitive Unicode user and group name matching auth.c | 13 --- groupaccess.c | 4 + match.c | 4 + openbsd-compat/bsd-cygwin_util.c | 146 +++++++++++++++++++++++++++++++ servconf.c | 4 + 5 files changed, 158 insertions(+), 13 deletions(-) -- 2.20.1
Corinna Vinschen
2019-Feb-20 12:41 UTC
[PATCH 1/2] Revert "[auth.c] On Cygwin, refuse usernames that have differences in case"
This reverts commit acc9b29486dfd649dfda474e5c1a03b317449f1c. Signed-off-by: Corinna Vinschen <vinschen at redhat.com> --- auth.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/auth.c b/auth.c index 62c58e72f01e..332b6220cd21 100644 --- a/auth.c +++ b/auth.c @@ -583,19 +583,6 @@ getpwnamallow(struct ssh *ssh, const char *user) #if defined(_AIX) && defined(HAVE_SETAUTHDB) aix_restoreauthdb(); -#endif -#ifdef HAVE_CYGWIN - /* - * Windows usernames are case-insensitive. To avoid later problems - * when trying to match the username, the user is only allowed to - * login if the username is given in the same case as stored in the - * user database. - */ - if (pw != NULL && strcmp(user, pw->pw_name) != 0) { - logit("Login name %.100s does not match stored username %.100s", - user, pw->pw_name); - pw = NULL; - } #endif if (pw == NULL) { logit("Invalid user %.100s from %.100s port %d", -- 2.20.1
Corinna Vinschen
2019-Feb-20 12:41 UTC
[PATCH 2/2] Cygwin: implement case-insensitive Unicode user and group name matching
The previous revert enabled case-insensitive user names again. This patch implements the case-insensitive user and group name matching. To allow Unicode chars, implement the matcher using wchar_t chars in Cygwin-specific code. Keep the generic code changes as small as possible. Cygwin: implement case-insensitive Unicode user and group name matching Signed-off-by: Corinna Vinschen <vinschen at redhat.com> --- groupaccess.c | 4 + match.c | 4 + openbsd-compat/bsd-cygwin_util.c | 146 +++++++++++++++++++++++++++++++ servconf.c | 4 + 4 files changed, 158 insertions(+) diff --git a/groupaccess.c b/groupaccess.c index 9e4d25521647..43367990d8c3 100644 --- a/groupaccess.c +++ b/groupaccess.c @@ -103,7 +103,11 @@ ga_match_pattern_list(const char *group_pattern) int i, found = 0; for (i = 0; i < ngroups; i++) { +#ifndef HAVE_CYGWIN switch (match_pattern_list(groups_byname[i], group_pattern, 0)) { +#else + switch (match_pattern_list(groups_byname[i], group_pattern, 1)) { +#endif case -1: return 0; /* Negated match wins */ case 0: diff --git a/match.c b/match.c index bb3e95f678ca..b50ae4057391 100644 --- a/match.c +++ b/match.c @@ -111,6 +111,8 @@ match_pattern(const char *s, const char *pattern) /* NOTREACHED */ } +#ifndef HAVE_CYGWIN /* Cygwin version in openbsd-compat/bsd-cygwin_util.c */ + /* * Tries to match the string against the * comma-separated sequence of subpatterns (each possibly preceded by ! to @@ -170,6 +172,8 @@ match_pattern_list(const char *string, const char *pattern, int dolower) return got_positive; } +#endif + /* * Tries to match the host name (which must be in all lowercase) against the * comma-separated sequence of subpatterns (each possibly preceded by ! to diff --git a/openbsd-compat/bsd-cygwin_util.c b/openbsd-compat/bsd-cygwin_util.c index fb49e30f5981..f721fca9d998 100644 --- a/openbsd-compat/bsd-cygwin_util.c +++ b/openbsd-compat/bsd-cygwin_util.c @@ -37,6 +37,8 @@ #include <string.h> #include <unistd.h> #include <stdarg.h> +#include <wchar.h> +#include <wctype.h> #include "xmalloc.h" @@ -117,4 +119,148 @@ free_windows_environment(char **p) free(p); } +/* + * Returns true if the given string matches the pattern (which may contain ? + * and * as wildcards), and zero if it does not match. + * + * The Cygwin version of this function must be case-insensitive and take + * Unicode characters into account. + */ + +static int +__match_pattern (const wchar_t *s, const wchar_t *pattern, int caseinsensitive) +{ + for (;;) { + /* If at end of pattern, accept if also at end of string. */ + if (!*pattern) + return !*s; + + if (*pattern == '*') { + /* Skip the asterisk. */ + pattern++; + + /* If at end of pattern, accept immediately. */ + if (!*pattern) + return 1; + + /* If next character in pattern is known, optimize. */ + if (*pattern != '?' && *pattern != '*') { + /* + * Look instances of the next character in + * pattern, and try to match starting from + * those. + */ + for (; *s; s++) + if (*s == *pattern && + __match_pattern(s + 1, pattern + 1, + caseinsensitive)) + return 1; + /* Failed. */ + return 0; + } + /* + * Move ahead one character at a time and try to + * match at each position. + */ + for (; *s; s++) + if (__match_pattern(s, pattern, caseinsensitive)) + return 1; + /* Failed. */ + return 0; + } + /* + * There must be at least one more character in the string. + * If we are at the end, fail. + */ + if (!*s) + return 0; + + /* Check if the next character of the string is acceptable. */ + if (*pattern != '?' && (*pattern != *s && + (!caseinsensitive || towlower(*pattern) != towlower(*s)))) + return 0; + + /* Move to the next character, both in string and in pattern. */ + s++; + pattern++; + } + /* NOTREACHED */ +} + +static int +_match_pattern(const char *s, const char *pattern, int caseinsensitive) +{ + wchar_t *ws; + wchar_t *wpattern; + size_t len; + + if ((len = mbstowcs(NULL, s, 0)) < 0) + return 0; + ws = (wchar_t *) alloca((len + 1) * sizeof (wchar_t)); + mbstowcs(ws, s, len + 1); + if ((len = mbstowcs(NULL, pattern, 0)) < 0) + return 0; + wpattern = (wchar_t *) alloca((len + 1) * sizeof (wchar_t)); + mbstowcs(wpattern, pattern, len + 1); + return __match_pattern (ws, wpattern, caseinsensitive); +} + +/* + * Tries to match the string against the + * comma-separated sequence of subpatterns (each possibly preceded by ! to + * indicate negation). Returns -1 if negation matches, 1 if there is + * a positive match, 0 if there is no match at all. + */ +int +match_pattern_list(const char *string, const char *pattern, int caseinsensitive) +{ + char sub[1024]; + int negated; + int got_positive; + u_int i, subi, len = strlen(pattern); + + got_positive = 0; + for (i = 0; i < len;) { + /* Check if the subpattern is negated. */ + if (pattern[i] == '!') { + negated = 1; + i++; + } else + negated = 0; + + /* + * Extract the subpattern up to a comma or end. Convert the + * subpattern to lowercase. + */ + for (subi = 0; + i < len && subi < sizeof(sub) - 1 && pattern[i] != ','; + subi++, i++) + sub[subi] = pattern[i]; + /* If subpattern too long, return failure (no match). */ + if (subi >= sizeof(sub) - 1) + return 0; + + /* If the subpattern was terminated by a comma, then skip it. */ + if (i < len && pattern[i] == ',') + i++; + + /* Null-terminate the subpattern. */ + sub[subi] = '\0'; + + /* Try to match the subpattern against the string. */ + if (_match_pattern(string, sub, caseinsensitive)) { + if (negated) + return -1; /* Negative */ + else + got_positive = 1; /* Positive */ + } + } + + /* + * Return success if got a positive match. If there was a negative + * match, we have already returned -1 and never get here. + */ + return got_positive; +} + #endif /* HAVE_CYGWIN */ diff --git a/servconf.c b/servconf.c index d9680aba11ca..4fa896fd4576 100644 --- a/servconf.c +++ b/servconf.c @@ -1049,7 +1049,11 @@ match_cfg_line(char **condition, int line, struct connection_info *ci) } if (ci->user == NULL) match_test_missing_fatal("User", "user"); +#ifndef HAVE_CYGWIN if (match_pattern_list(ci->user, arg, 0) != 1) +#else + if (match_pattern_list(ci->user, arg, 1) != 1) +#endif result = 0; else debug("user %.100s matched 'User %.100s' at " -- 2.20.1
Darren Tucker
2019-Feb-22 04:32 UTC
[PATCH 2/2] Cygwin: implement case-insensitive Unicode user and group name matching
On Wed, 20 Feb 2019 at 23:54, Corinna Vinschen <vinschen at redhat.com> wrote:> The previous revert enabled case-insensitive user names again. This > patch implements the case-insensitive user and group name matching. > To allow Unicode chars, implement the matcher using wchar_t chars in > Cygwin-specific code. Keep the generic code changes as small as possible. > Cygwin: implement case-insensitive Unicode user and group name matchingApplied, thanks. I think it might be possible to make this less intrusive by adding a match_user_pattern_list() function that just calls match_pattern_list on Unix-alikes and the Cygwin specific function there. I'll take a look. -- Darren Tucker (dtucker at dtucker.net) GPG key 11EAA6FA / A86E 3E07 5B19 5880 E860 37F4 9357 ECEF 11EA A6FA (new) Good judgement comes with experience. Unfortunately, the experience usually comes from bad judgement.
Possibly Parallel Threads
- [PATCH 0/2] Cygwin: allow user and group case-insensitive Unicode strings
- [PATCH 2/2] Cygwin: implement case-insensitive Unicode user and group name matching
- xmalloc(foo*bar) -> xcalloc(foo, bar) for Portable
- [PATCH 2/2] Cygwin: implement case-insensitive Unicode user and group name matching
- NGROUPS_MAX