1 /* 2 * Copyright (c) 2000-2002 by Solar Designer. See LICENSE. 3 */ 4 5 #include <stdlib.h> 6 #include <string.h> 7 #include <ctype.h> 8 #include <pwd.h> 9 10 #include "passwdqc.h" 11 12 #define REASON_ERROR \ 13 "check failed" 14 15 #define REASON_SAME \ 16 "is the same as the old one" 17 #define REASON_SIMILAR \ 18 "is based on the old one" 19 20 #define REASON_SHORT \ 21 "too short" 22 #define REASON_LONG \ 23 "too long" 24 25 #define REASON_SIMPLESHORT \ 26 "not enough different characters or classes for this length" 27 #define REASON_SIMPLE \ 28 "not enough different characters or classes" 29 30 #define REASON_PERSONAL \ 31 "based on personal login information" 32 33 #define REASON_WORD \ 34 "based on a dictionary word and not a passphrase" 35 36 #define FIXED_BITS 15 37 38 typedef unsigned long fixed; 39 40 /* 41 * Calculates the expected number of different characters for a random 42 * password of a given length. The result is rounded down. We use this 43 * with the _requested_ minimum length (so longer passwords don't have 44 * to meet this strict requirement for their length). 45 */ 46 static int expected_different(int charset, int length) 47 { 48 fixed x, y, z; 49 50 x = ((fixed)(charset - 1) << FIXED_BITS) / charset; 51 y = x; 52 while (--length > 0) y = (y * x) >> FIXED_BITS; 53 z = (fixed)charset * (((fixed)1 << FIXED_BITS) - y); 54 55 return (int)(z >> FIXED_BITS); 56 } 57 58 /* 59 * A password is too simple if it is too short for its class, or doesn't 60 * contain enough different characters for its class, or doesn't contain 61 * enough words for a passphrase. 62 */ 63 static int is_simple(passwdqc_params_t *params, const char *newpass) 64 { 65 int length, classes, words, chars; 66 int digits, lowers, uppers, others, unknowns; 67 int c, p; 68 69 length = classes = words = chars = 0; 70 digits = lowers = uppers = others = unknowns = 0; 71 p = ' '; 72 while ((c = (unsigned char)newpass[length])) { 73 length++; 74 75 if (!isascii(c)) unknowns++; else 76 if (isdigit(c)) digits++; else 77 if (islower(c)) lowers++; else 78 if (isupper(c)) uppers++; else 79 others++; 80 81 if (isascii(c) && isalpha(c) && isascii(p) && !isalpha(p)) 82 words++; 83 p = c; 84 85 if (!strchr(&newpass[length], c)) 86 chars++; 87 } 88 89 if (!length) return 1; 90 91 /* Upper case characters and digits used in common ways don't increase the 92 * strength of a password */ 93 c = (unsigned char)newpass[0]; 94 if (uppers && isascii(c) && isupper(c)) uppers--; 95 c = (unsigned char)newpass[length - 1]; 96 if (digits && isascii(c) && isdigit(c)) digits--; 97 98 /* Count the number of different character classes we've seen. We assume 99 * that there're no non-ASCII characters for digits. */ 100 classes = 0; 101 if (digits) classes++; 102 if (lowers) classes++; 103 if (uppers) classes++; 104 if (others) classes++; 105 if (unknowns && (!classes || (digits && classes == 1))) classes++; 106 107 for (; classes > 0; classes--) 108 switch (classes) { 109 case 1: 110 if (length >= params->min[0] && 111 chars >= expected_different(10, params->min[0]) - 1) 112 return 0; 113 return 1; 114 115 case 2: 116 if (length >= params->min[1] && 117 chars >= expected_different(36, params->min[1]) - 1) 118 return 0; 119 if (!params->passphrase_words || 120 words < params->passphrase_words) 121 continue; 122 if (length >= params->min[2] && 123 chars >= expected_different(27, params->min[2]) - 1) 124 return 0; 125 continue; 126 127 case 3: 128 if (length >= params->min[3] && 129 chars >= expected_different(62, params->min[3]) - 1) 130 return 0; 131 continue; 132 133 case 4: 134 if (length >= params->min[4] && 135 chars >= expected_different(95, params->min[4]) - 1) 136 return 0; 137 continue; 138 } 139 140 return 1; 141 } 142 143 static char *unify(const char *src) 144 { 145 const char *sptr; 146 char *dst, *dptr; 147 int c; 148 149 if (!(dst = malloc(strlen(src) + 1))) 150 return NULL; 151 152 sptr = src; 153 dptr = dst; 154 do { 155 c = (unsigned char)*sptr; 156 if (isascii(c) && isupper(c)) 157 *dptr++ = tolower(c); 158 else 159 *dptr++ = *sptr; 160 } while (*sptr++); 161 162 return dst; 163 } 164 165 static char *reverse(const char *src) 166 { 167 const char *sptr; 168 char *dst, *dptr; 169 170 if (!(dst = malloc(strlen(src) + 1))) 171 return NULL; 172 173 sptr = &src[strlen(src)]; 174 dptr = dst; 175 while (sptr > src) 176 *dptr++ = *--sptr; 177 *dptr = '\0'; 178 179 return dst; 180 } 181 182 static void clean(char *dst) 183 { 184 if (dst) { 185 memset(dst, 0, strlen(dst)); 186 free(dst); 187 } 188 } 189 190 /* 191 * Needle is based on haystack if both contain a long enough common 192 * substring and needle would be too simple for a password with the 193 * substring removed. 194 */ 195 static int is_based(passwdqc_params_t *params, 196 const char *haystack, const char *needle, const char *original) 197 { 198 char *scratch; 199 int length; 200 int i, j; 201 const char *p; 202 int match; 203 204 if (!params->match_length) /* disabled */ 205 return 0; 206 207 if (params->match_length < 0) /* misconfigured */ 208 return 1; 209 210 if (strstr(haystack, needle)) /* based on haystack entirely */ 211 return 1; 212 213 scratch = NULL; 214 215 length = strlen(needle); 216 for (i = 0; i <= length - params->match_length; i++) 217 for (j = params->match_length; i + j <= length; j++) { 218 match = 0; 219 for (p = haystack; *p; p++) 220 if (*p == needle[i] && !strncmp(p, &needle[i], j)) { 221 match = 1; 222 if (!scratch) { 223 if (!(scratch = malloc(length + 1))) 224 return 1; 225 } 226 memcpy(scratch, original, i); 227 memcpy(&scratch[i], &original[i + j], 228 length + 1 - (i + j)); 229 if (is_simple(params, scratch)) { 230 clean(scratch); 231 return 1; 232 } 233 } 234 if (!match) break; 235 } 236 237 clean(scratch); 238 239 return 0; 240 } 241 242 /* 243 * This wordlist check is now the least important given the checks above 244 * and the support for passphrases (which are based on dictionary words, 245 * and checked by other means). It is still useful to trap simple short 246 * passwords (if short passwords are allowed) that are word-based, but 247 * passed the other checks due to uncommon capitalization, digits, and 248 * special characters. We (mis)use the same set of words that are used 249 * to generate random passwords. This list is much smaller than those 250 * used for password crackers, and it doesn't contain common passwords 251 * that aren't short English words. Perhaps support for large wordlists 252 * should still be added, even though this is now of little importance. 253 */ 254 static int is_word_based(passwdqc_params_t *params, 255 const char *needle, const char *original) 256 { 257 char word[7]; 258 char *unified; 259 int i; 260 261 word[6] = '\0'; 262 for (i = 0; i < 0x1000; i++) { 263 memcpy(word, _passwdqc_wordset_4k[i], 6); 264 if ((int)strlen(word) < params->match_length) continue; 265 unified = unify(word); 266 if (is_based(params, unified, needle, original)) { 267 clean(unified); 268 return 1; 269 } 270 clean(unified); 271 } 272 273 return 0; 274 } 275 276 const char *_passwdqc_check(passwdqc_params_t *params, 277 const char *newpass, const char *oldpass, struct passwd *pw) 278 { 279 char truncated[9], *reversed; 280 char *u_newpass, *u_reversed; 281 char *u_oldpass; 282 char *u_name, *u_gecos; 283 const char *reason; 284 int length; 285 286 reversed = NULL; 287 u_newpass = u_reversed = NULL; 288 u_oldpass = NULL; 289 u_name = u_gecos = NULL; 290 291 reason = NULL; 292 293 if (oldpass && !strcmp(oldpass, newpass)) 294 reason = REASON_SAME; 295 296 length = strlen(newpass); 297 298 if (!reason && length < params->min[4]) 299 reason = REASON_SHORT; 300 301 if (!reason && length > params->max) { 302 if (params->max == 8) { 303 truncated[0] = '\0'; 304 strncat(truncated, newpass, 8); 305 newpass = truncated; 306 if (oldpass && !strncmp(oldpass, newpass, 8)) 307 reason = REASON_SAME; 308 } else 309 reason = REASON_LONG; 310 } 311 312 if (!reason && is_simple(params, newpass)) { 313 if (length < params->min[1] && params->min[1] <= params->max) 314 reason = REASON_SIMPLESHORT; 315 else 316 reason = REASON_SIMPLE; 317 } 318 319 if (!reason) { 320 if ((reversed = reverse(newpass))) { 321 u_newpass = unify(newpass); 322 u_reversed = unify(reversed); 323 if (oldpass) 324 u_oldpass = unify(oldpass); 325 if (pw) { 326 u_name = unify(pw->pw_name); 327 u_gecos = unify(pw->pw_gecos); 328 } 329 } 330 if (!reversed || 331 !u_newpass || !u_reversed || 332 (oldpass && !u_oldpass) || 333 (pw && (!u_name || !u_gecos))) 334 reason = REASON_ERROR; 335 } 336 337 if (!reason && oldpass && params->similar_deny && 338 (is_based(params, u_oldpass, u_newpass, newpass) || 339 is_based(params, u_oldpass, u_reversed, reversed))) 340 reason = REASON_SIMILAR; 341 342 if (!reason && pw && 343 (is_based(params, u_name, u_newpass, newpass) || 344 is_based(params, u_name, u_reversed, reversed) || 345 is_based(params, u_gecos, u_newpass, newpass) || 346 is_based(params, u_gecos, u_reversed, reversed))) 347 reason = REASON_PERSONAL; 348 349 if (!reason && (int)strlen(newpass) < params->min[2] && 350 (is_word_based(params, u_newpass, newpass) || 351 is_word_based(params, u_reversed, reversed))) 352 reason = REASON_WORD; 353 354 memset(truncated, 0, sizeof(truncated)); 355 clean(reversed); 356 clean(u_newpass); clean(u_reversed); 357 clean(u_oldpass); 358 clean(u_name); clean(u_gecos); 359 360 return reason; 361 } 362