]> eyrie.org Git - kerberos/krb5-strength.git/blob - plugin/principal.c
Declare fast forward from 3.1-2
[kerberos/krb5-strength.git] / plugin / principal.c
1 /*
2  * Password strength checks based on the principal.
3  *
4  * Performs various checks of the password against the principal for which the
5  * password is being changed, trying to detect and reject passwords based on
6  * components of the principal.
7  *
8  * Developed by Derrick Brashear and Ken Hornstein of Sine Nomine Associates,
9  *     on behalf of Stanford University
10  * Extensive modifications by Russ Allbery <eagle@eyrie.org>
11  * Copyright 2020 Russ Allbery <eagle@eyrie.org>
12  * Copyright 2006-2007, 2009, 2012-2014
13  *     The Board of Trustees of the Leland Stanford Junior University
14  *
15  * SPDX-License-Identifier: MIT
16  */
17
18 #include <config.h>
19 #include <portable/system.h>
20
21 #include <ctype.h>
22
23 #include <plugin/internal.h>
24 #include <util/macros.h>
25
26
27 /*
28  * Given a string taken from the principal, check if the password matches that
29  * string or is that string with leading or trailing digits added.  If so,
30  * sets the Kerberos error and returns a non-zero error code.  Otherwise,
31  * returns 0.
32  */
33 static krb5_error_code
34 check_component(krb5_context ctx, const char *component, const char *password)
35 {
36     char *copy;
37     size_t i, j, complength, passlength;
38     char c;
39
40     /* Check if the password is a simple match for the component. */
41     if (strcasecmp(component, password) == 0)
42         return strength_error_generic(ctx, ERROR_USERNAME);
43
44     /*
45      * If the length of the password matches the length of the component,
46      * check for a reversed match.
47      */
48     complength = strlen(component);
49     passlength = strlen(password);
50     if (complength == passlength) {
51         copy = strdup(component);
52         if (copy == NULL)
53             return strength_error_system(ctx, "cannot allocate memory");
54         for (i = 0, j = complength - 1; i < j; i++, j--) {
55             c = copy[i];
56             copy[i] = copy[j];
57             copy[j] = c;
58         }
59         if (strcasecmp(copy, password) == 0) {
60             explicit_bzero(copy, strlen(copy));
61             free(copy);
62             return strength_error_generic(ctx, ERROR_USERNAME);
63         }
64         free(copy);
65     }
66
67     /*
68      * We've checked everything we care about unless the password is longer
69      * than the component.
70      */
71     if (passlength <= complength)
72         return 0;
73
74     /*
75      * Check whether the user just added leading or trailing digits to the
76      * component of the principal to form the password.
77      */
78     for (i = 0; i <= passlength - complength; i++) {
79         if (strncasecmp(password + i, component, complength) != 0)
80             continue;
81
82         /*
83          * For this to be a match, all characters from 0 to i - 1 must be
84          * digits, and all characters from strlen(component) + i to
85          * strlen(password) - 1 must be digits.
86          */
87         for (j = 0; j < i; j++)
88             if (!isdigit((unsigned char) password[j]))
89                 return 0;
90         for (j = complength + i; j < passlength; j++)
91             if (!isdigit((unsigned char) password[j]))
92                 return 0;
93
94         /* The password was formed by adding digits to this component. */
95         return strength_error_generic(ctx, ERROR_USERNAME);
96     }
97
98     /* No similarity to component detected. */
99     return 0;
100 }
101
102
103 /*
104  * Returns true if a given character is a separator character for forming
105  * components, and false otherwise.
106  */
107 static bool
108 is_separator(unsigned char c)
109 {
110     if (c == '-' || c == '_')
111         return false;
112     if (isalnum(c))
113         return false;
114     return true;
115 }
116
117
118 /*
119  * Check whether the password is based in some way on the principal.  We do
120  * this by scanning the principal (in string form) and checking both each
121  * component of that password (defined as the alphanumeric, hyphen, and
122  * underscore bits between other characters) and the remaining principal from
123  * that point forward (to catch, for example, the entire realm).  Returns 0 if
124  * it is not and some non-zero error code if it appears to be.
125  */
126 krb5_error_code
127 strength_check_principal(krb5_context ctx, krb5_pwqual_moddata data UNUSED,
128                          const char *principal, const char *password)
129 {
130     krb5_error_code code;
131     char *copy, *start;
132     size_t i, length;
133
134     /* Sanity check. */
135     if (principal == NULL)
136         return 0;
137
138     /* Start with checking the entire principal. */
139     code = check_component(ctx, principal, password);
140     if (code != 0)
141         return code;
142
143     /*
144      * Make a copy of the principal and scan forward past any leading
145      * separators.
146      */
147     length = strlen(principal);
148     copy = strdup(principal);
149     if (copy == NULL)
150         return strength_error_system(ctx, "cannot allocate memory");
151     i = 0;
152     while (copy[i] != '\0' && is_separator(copy[i]))
153         i++;
154
155     /*
156      * Now loop for each component.  At the start of each loop, check against
157      * the component formed by the rest of the principal string.
158      */
159     do {
160         if (i != 0) {
161             code = check_component(ctx, copy + i, password);
162             if (code != 0) {
163                 explicit_bzero(copy, strlen(copy));
164                 free(copy);
165                 return code;
166             }
167         }
168
169         /* Set the component start and then scan for a separator. */
170         start = copy + i;
171         while (i < length && !is_separator(copy[i]))
172             i++;
173
174         /* At end of string or a separator.  Truncate the component. */
175         copy[i] = '\0';
176
177         /* Check the current component. */
178         code = check_component(ctx, start, password);
179         if (code != 0) {
180             explicit_bzero(copy, strlen(copy));
181             free(copy);
182             return code;
183         }
184
185         /* Scan forward past any more separators. */
186         while (i < length && is_separator(copy[i]))
187             i++;
188     } while (i < length);
189
190     /* Password does not appear to be based on the principal. */
191     explicit_bzero(copy, strlen(copy));
192     free(copy);
193     return 0;
194 }