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.
Reasonably Related 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