]> eyrie.org Git - kerberos/krb5-strength.git/blob - plugin/principal.c
Don't use sysbail to report libdl errors in test suite
[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 2006, 2007, 2009, 2012, 2013
12  *     The Board of Trustees of the Leland Stanford Junior University
13  *
14  * See LICENSE for licensing terms.
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             free(copy);
60             return strength_error_generic(ctx, ERROR_USERNAME);
61         }
62         free(copy);
63     }
64
65     /*
66      * We've checked everything we care about unless the password is longer
67      * than the component.
68      */
69     if (passlength <= complength)
70         return 0;
71
72     /*
73      * Check whether the user just added leading or trailing digits to the
74      * component of the principal to form the password.
75      */
76     for (i = 0; i <= passlength - complength; i++) {
77         if (strncasecmp(password + i, component, complength) != 0)
78             continue;
79
80         /*
81          * For this to be a match, all characters from 0 to i - 1 must be
82          * digits, and all characters from strlen(component) + i to
83          * strlen(password) - 1 must be digits.
84          */
85         for (j = 0; j < i; j++)
86             if (!isdigit((unsigned char) password[j]))
87                 return 0;
88         for (j = complength + i; j < passlength; j++)
89             if (!isdigit((unsigned char) password[j]))
90                 return 0;
91
92         /* The password was formed by adding digits to this component. */
93         return strength_error_generic(ctx, ERROR_USERNAME);
94     }
95
96     /* No similarity to component detected. */
97     return 0;
98 }
99
100
101 /*
102  * Returns true if a given character is a separator character for forming
103  * components, and false otherwise.
104  */
105 static bool
106 is_separator(unsigned char c)
107 {
108     if (c == '-' || c == '_')
109         return false;
110     if (isalnum(c))
111         return false;
112     return true;
113 }
114
115
116 /*
117  * Check whether the password is based in some way on the principal.  We do
118  * this by scanning the principal (in string form) and checking both each
119  * component of that password (defined as the alphanumeric, hyphen, and
120  * underscore bits between other characters) and the remaining principal from
121  * that point forward (to catch, for example, the entire realm).  Returns 0 if
122  * it is not and some non-zero error code if it appears to be.
123  */
124 krb5_error_code
125 strength_check_principal(krb5_context ctx, krb5_pwqual_moddata data UNUSED,
126                          const char *principal, const char *password)
127 {
128     krb5_error_code code;
129     char *copy, *start;
130     size_t i, length;
131
132     /* Sanity check. */
133     if (principal == NULL)
134         return 0;
135
136     /* Start with checking the entire principal. */
137     code = check_component(ctx, principal, password);
138     if (code != 0)
139         return code;
140
141     /*
142      * Make a copy of the principal and scan forward past any leading
143      * separators.
144      */
145     length = strlen(principal);
146     copy = strdup(principal);
147     if (copy == NULL)
148         return strength_error_system(ctx, "cannot allocate memory");
149     i = 0;
150     while (copy[i] != '\0' && is_separator(copy[i]))
151         i++;
152
153     /*
154      * Now loop for each component.  At the start of each loop, check against
155      * the component formed by the rest of the principal string.
156      */
157     do {
158         if (i != 0) {
159             code = check_component(ctx, copy + i, password);
160             if (code != 0) {
161                 free(copy);
162                 return code;
163             }
164         }
165
166         /* Set the component start and then scan for a separator. */
167         start = copy + i;
168         while (i < length && !is_separator(copy[i]))
169             i++;
170
171         /* At end of string or a separator.  Truncate the component. */
172         copy[i] = '\0';
173
174         /* Check the current component. */
175         code = check_component(ctx, start, password);
176         if (code != 0) {
177             free(copy);
178             return code;
179         }
180
181         /* Scan forward past any more separators. */
182         while (i < length && is_separator(copy[i]))
183             i++;
184     } while (i < length);
185
186     /* Password does not appear to be based on the principal. */
187     free(copy);
188     return 0;
189 }