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