1 /* 2 * Copyright (c) 2000-2003,2005,2016,2020,2021 by Solar Designer 3 * Copyright (c) 2008,2009 by Dmitry V. Levin 4 * See LICENSE 5 */ 6 7 #ifdef _MSC_VER 8 #define _CRT_NONSTDC_NO_WARNINGS /* we use POSIX function names */ 9 #endif 10 11 #include <stdio.h> 12 #include <stdlib.h> 13 #include <string.h> 14 #include <limits.h> 15 16 #include "passwdqc.h" 17 #include "concat.h" 18 19 static const char *skip_prefix(const char *sample, const char *prefix) 20 { 21 size_t len = strlen(prefix); 22 23 if (strncmp(sample, prefix, len)) 24 return NULL; 25 return sample + len; 26 } 27 28 static int 29 parse_option(passwdqc_params_t *params, char **reason, const char *option) 30 { 31 const char *err = "Invalid parameter value"; 32 const char * const err_oom = "Out of memory"; 33 const char *p; 34 char *e; 35 int i, rc = 0; 36 unsigned long v; 37 38 *reason = NULL; 39 if ((p = skip_prefix(option, "min="))) { 40 for (i = 0; i < 5; i++) { 41 if (!strncmp(p, "disabled", 8)) { 42 v = INT_MAX; 43 p += 8; 44 } else { 45 v = strtoul(p, &e, 10); 46 p = e; 47 } 48 if (i < 4 && *p++ != ',') 49 goto parse_error; 50 if (v > INT_MAX) 51 goto parse_error; 52 if (i && (int)v > params->qc.min[i - 1]) 53 goto parse_error; 54 params->qc.min[i] = v; 55 } 56 if (*p) 57 goto parse_error; 58 } else if ((p = skip_prefix(option, "max="))) { 59 v = strtoul(p, &e, 10); 60 if (*e || v < 8 || v > INT_MAX) 61 goto parse_error; 62 if (v > 10000) 63 v = 10000; 64 params->qc.max = v; 65 } else if ((p = skip_prefix(option, "passphrase="))) { 66 v = strtoul(p, &e, 10); 67 if (*e || v > INT_MAX) 68 goto parse_error; 69 params->qc.passphrase_words = v; 70 } else if ((p = skip_prefix(option, "match="))) { 71 v = strtoul(p, &e, 10); 72 if (*e || v > INT_MAX) 73 goto parse_error; 74 params->qc.match_length = v; 75 } else if ((p = skip_prefix(option, "similar="))) { 76 if (!strcmp(p, "permit")) 77 params->qc.similar_deny = 0; 78 else if (!strcmp(p, "deny")) 79 params->qc.similar_deny = 1; 80 else 81 goto parse_error; 82 } else if ((p = skip_prefix(option, "random="))) { 83 v = strtoul(p, &e, 10); 84 if (!strcmp(e, ",only")) { 85 e += 5; 86 params->qc.min[4] = INT_MAX; 87 } 88 if (*e || (v && v < 24) || v > 136) 89 goto parse_error; 90 params->qc.random_bits = v; 91 } else if ((p = skip_prefix(option, "wordlist="))) { 92 free(params->qc.wordlist); 93 params->qc.wordlist = NULL; 94 if (*p && !(params->qc.wordlist = strdup(p))) { 95 err = err_oom; 96 goto parse_error; 97 } 98 } else if ((p = skip_prefix(option, "denylist="))) { 99 free(params->qc.denylist); 100 params->qc.denylist = NULL; 101 if (*p && !(params->qc.denylist = strdup(p))) { 102 err = err_oom; 103 goto parse_error; 104 } 105 } else if ((p = skip_prefix(option, "filter="))) { 106 free(params->qc.filter); 107 params->qc.filter = NULL; 108 if (*p && !(params->qc.filter = strdup(p))) { 109 err = err_oom; 110 goto parse_error; 111 } 112 } else if ((p = skip_prefix(option, "enforce="))) { 113 params->pam.flags &= ~F_ENFORCE_MASK; 114 if (!strcmp(p, "users")) 115 params->pam.flags |= F_ENFORCE_USERS; 116 else if (!strcmp(p, "everyone")) 117 params->pam.flags |= F_ENFORCE_EVERYONE; 118 else if (strcmp(p, "none")) 119 goto parse_error; 120 } else if (!strcmp(option, "non-unix")) { 121 if (params->pam.flags & F_CHECK_OLDAUTHTOK) 122 goto parse_error; 123 params->pam.flags |= F_NON_UNIX; 124 } else if ((p = skip_prefix(option, "retry="))) { 125 v = strtoul(p, &e, 10); 126 if (*e || v > INT_MAX) 127 goto parse_error; 128 params->pam.retry = v; 129 } else if ((p = skip_prefix(option, "ask_oldauthtok"))) { 130 params->pam.flags &= ~F_ASK_OLDAUTHTOK_MASK; 131 if (params->pam.flags & F_USE_FIRST_PASS) 132 goto parse_error; 133 if (!p[0]) 134 params->pam.flags |= F_ASK_OLDAUTHTOK_PRELIM; 135 else if (!strcmp(p, "=update")) 136 params->pam.flags |= F_ASK_OLDAUTHTOK_UPDATE; 137 else 138 goto parse_error; 139 } else if (!strcmp(option, "check_oldauthtok")) { 140 if (params->pam.flags & F_NON_UNIX) 141 goto parse_error; 142 params->pam.flags |= F_CHECK_OLDAUTHTOK; 143 } else if (!strcmp(option, "use_first_pass")) { 144 if (params->pam.flags & F_ASK_OLDAUTHTOK_MASK) 145 goto parse_error; 146 params->pam.flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK; 147 } else if (!strcmp(option, "use_authtok")) { 148 params->pam.flags |= F_USE_AUTHTOK; 149 } else if (!strcmp(option, "noaudit")) { 150 params->pam.flags |= F_NO_AUDIT; 151 } else if ((p = skip_prefix(option, "config="))) { 152 if ((rc = passwdqc_params_load(params, reason, p))) 153 goto parse_error; 154 } else { 155 err = "Invalid parameter"; 156 goto parse_error; 157 } 158 159 return 0; 160 161 parse_error: 162 passwdqc_params_free(params); 163 e = concat("Error parsing parameter \"", option, "\": ", 164 (rc ? (*reason ? *reason : err_oom) : err), NULL); 165 free(*reason); 166 *reason = e; 167 return rc ? rc : -1; 168 } 169 170 int 171 passwdqc_params_parse(passwdqc_params_t *params, char **reason, 172 int argc, const char *const *argv) 173 { 174 int i; 175 176 *reason = NULL; 177 for (i = 0; i < argc; ++i) { 178 int rc; 179 180 if ((rc = parse_option(params, reason, argv[i]))) 181 return rc; 182 } 183 return 0; 184 } 185 186 static const passwdqc_params_t defaults = { 187 { 188 {INT_MAX, 24, 11, 8, 7}, /* min */ 189 72, /* max */ 190 3, /* passphrase_words */ 191 4, /* match_length */ 192 1, /* similar_deny */ 193 47, /* random_bits */ 194 NULL, /* wordlist */ 195 NULL, /* denylist */ 196 NULL /* filter */ 197 }, 198 { 199 F_ENFORCE_EVERYONE, /* flags */ 200 3 /* retry */ 201 } 202 }; 203 204 void passwdqc_params_reset(passwdqc_params_t *params) 205 { 206 *params = defaults; 207 } 208 209 void passwdqc_params_free(passwdqc_params_t *params) 210 { 211 free(params->qc.wordlist); 212 free(params->qc.denylist); 213 free(params->qc.filter); 214 passwdqc_params_reset(params); 215 } 216