1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "passwd_common.h" 18#include "apr_strings.h" 19#include "apr_errno.h" 20 21#if APR_HAVE_STDIO_H 22#include <stdio.h> 23#endif 24 25#include "apr_md5.h" 26#include "apr_sha1.h" 27 28#if APR_HAVE_TIME_H 29#include <time.h> 30#endif 31#if APR_HAVE_CRYPT_H 32#include <crypt.h> 33#endif 34#if APR_HAVE_STDLIB_H 35#include <stdlib.h> 36#endif 37#if APR_HAVE_STRING_H 38#include <string.h> 39#endif 40#if APR_HAVE_UNISTD_H 41#include <unistd.h> 42#endif 43#if APR_HAVE_IO_H 44#include <io.h> 45#endif 46 47#ifdef _MSC_VER 48#define write _write 49#endif 50 51apr_file_t *errfile; 52 53int abort_on_oom(int rc) 54{ 55 const char *buf = "Error: out of memory\n"; 56 int written, count = strlen(buf); 57 do { 58 written = write(STDERR_FILENO, buf, count); 59 if (written == count) 60 break; 61 if (written > 0) { 62 buf += written; 63 count -= written; 64 } 65 } while (written >= 0 || errno == EINTR); 66 abort(); 67 /* NOTREACHED */ 68 return 0; 69} 70 71static int generate_salt(char *s, size_t size, const char **errstr, 72 apr_pool_t *pool) 73{ 74 unsigned char rnd[32]; 75 static const char itoa64[] = 76 "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; 77 apr_size_t n; 78 unsigned int val = 0, bits = 0; 79 apr_status_t rv; 80 81 n = (size * 6 + 7)/8; 82 if (n > sizeof(rnd)) { 83 apr_file_printf(errfile, "generate_salt(): BUG: Buffer too small"); 84 abort(); 85 } 86 rv = apr_generate_random_bytes(rnd, n); 87 if (rv) { 88 *errstr = apr_psprintf(pool, "Unable to generate random bytes: %pm", 89 &rv); 90 return ERR_RANDOM; 91 } 92 n = 0; 93 while (size > 0) { 94 if (bits < 6) { 95 val |= (rnd[n++] << bits); 96 bits += 8; 97 } 98 *s++ = itoa64[val & 0x3f]; 99 size--; 100 val >>= 6; 101 bits -= 6; 102 } 103 *s = '\0'; 104 return 0; 105} 106 107void putline(apr_file_t *f, const char *l) 108{ 109 apr_status_t rv; 110 if (f == NULL) 111 return; 112 rv = apr_file_puts(l, f); 113 if (rv != APR_SUCCESS) { 114 apr_file_printf(errfile, "Error writing temp file: %pm", &rv); 115 apr_file_close(f); 116 exit(ERR_FILEPERM); 117 } 118} 119 120int get_password(struct passwd_ctx *ctx) 121{ 122 char buf[MAX_STRING_LEN + 1]; 123 if (ctx->passwd_src == PW_STDIN) { 124 apr_file_t *file_stdin; 125 apr_size_t nread; 126 if (apr_file_open_stdin(&file_stdin, ctx->pool) != APR_SUCCESS) { 127 ctx->errstr = "Unable to read from stdin."; 128 return ERR_GENERAL; 129 } 130 if (apr_file_read_full(file_stdin, buf, sizeof(buf) - 1, 131 &nread) != APR_EOF 132 || nread == sizeof(buf) - 1) { 133 goto err_too_long; 134 } 135 buf[nread] = '\0'; 136 if (nread >= 1 && buf[nread-1] == '\n') { 137 buf[nread-1] = '\0'; 138 if (nread >= 2 && buf[nread-2] == '\r') 139 buf[nread-2] = '\0'; 140 } 141 apr_file_close(file_stdin); 142 ctx->passwd = apr_pstrdup(ctx->pool, buf); 143 } 144 else if (ctx->passwd_src == PW_PROMPT_VERIFY) { 145 apr_size_t bufsize = sizeof(buf); 146 if (apr_password_get("Enter password: ", buf, &bufsize) != 0) 147 goto err_too_long; 148 ctx->passwd = apr_pstrdup(ctx->pool, buf); 149 } 150 else { 151 apr_size_t bufsize = sizeof(buf); 152 if (apr_password_get("New password: ", buf, &bufsize) != 0) 153 goto err_too_long; 154 ctx->passwd = apr_pstrdup(ctx->pool, buf); 155 bufsize = sizeof(buf); 156 buf[0] = '\0'; 157 apr_password_get("Re-type new password: ", buf, &bufsize); 158 if (strcmp(ctx->passwd, buf) != 0) { 159 ctx->errstr = "password verification error"; 160 memset(ctx->passwd, '\0', strlen(ctx->passwd)); 161 memset(buf, '\0', sizeof(buf)); 162 return ERR_PWMISMATCH; 163 } 164 } 165 memset(buf, '\0', sizeof(buf)); 166 return 0; 167 168err_too_long: 169 ctx->errstr = apr_psprintf(ctx->pool, 170 "password too long (>%" APR_SIZE_T_FMT ")", 171 ctx->out_len - 1); 172 return ERR_OVERFLOW; 173} 174 175/* 176 * Make a password record from the given information. A zero return 177 * indicates success; on failure, ctx->errstr points to the error message. 178 */ 179int mkhash(struct passwd_ctx *ctx) 180{ 181 char *pw; 182 char salt[16]; 183 apr_status_t rv; 184 int ret = 0; 185#if CRYPT_ALGO_SUPPORTED 186 char *cbuf; 187#endif 188 189 if (ctx->cost != 0 && ctx->alg != ALG_BCRYPT) { 190 apr_file_printf(errfile, 191 "Warning: Ignoring -C argument for this algorithm." NL); 192 } 193 194 if (ctx->passwd == NULL) { 195 if ((ret = get_password(ctx)) != 0) 196 return ret; 197 } 198 pw = ctx->passwd; 199 200 switch (ctx->alg) { 201 case ALG_APSHA: 202 /* XXX out >= 28 + strlen(sha1) chars - fixed len SHA */ 203 apr_sha1_base64(pw, strlen(pw), ctx->out); 204 break; 205 206 case ALG_APMD5: 207 ret = generate_salt(salt, 8, &ctx->errstr, ctx->pool); 208 if (ret != 0) 209 break; 210 rv = apr_md5_encode(pw, salt, ctx->out, ctx->out_len); 211 if (rv != APR_SUCCESS) { 212 ctx->errstr = apr_psprintf(ctx->pool, 213 "could not encode password: %pm", &rv); 214 ret = ERR_GENERAL; 215 } 216 break; 217 218 case ALG_PLAIN: 219 /* XXX this len limitation is not in sync with any HTTPd len. */ 220 apr_cpystrn(ctx->out, pw, ctx->out_len); 221 break; 222 223#if CRYPT_ALGO_SUPPORTED 224 case ALG_CRYPT: 225 ret = generate_salt(salt, 8, &ctx->errstr, ctx->pool); 226 if (ret != 0) 227 break; 228 cbuf = crypt(pw, salt); 229 if (cbuf == NULL) { 230 rv = APR_FROM_OS_ERROR(errno); 231 ctx->errstr = apr_psprintf(ctx->pool, "crypt() failed: %pm", &rv); 232 ret = ERR_PWMISMATCH; 233 break; 234 } 235 236 apr_cpystrn(ctx->out, cbuf, ctx->out_len - 1); 237 if (strlen(pw) > 8) { 238 char *truncpw = apr_pstrdup(ctx->pool, pw); 239 truncpw[8] = '\0'; 240 if (!strcmp(ctx->out, crypt(truncpw, salt))) { 241 apr_file_printf(errfile, "Warning: Password truncated to 8 " 242 "characters by CRYPT algorithm." NL); 243 } 244 memset(truncpw, '\0', strlen(pw)); 245 } 246 break; 247#endif /* CRYPT_ALGO_SUPPORTED */ 248 249#if BCRYPT_ALGO_SUPPORTED 250 case ALG_BCRYPT: 251 rv = apr_generate_random_bytes((unsigned char*)salt, 16); 252 if (rv != APR_SUCCESS) { 253 ctx->errstr = apr_psprintf(ctx->pool, "Unable to generate random " 254 "bytes: %pm", &rv); 255 ret = ERR_RANDOM; 256 break; 257 } 258 259 if (ctx->cost == 0) 260 ctx->cost = BCRYPT_DEFAULT_COST; 261 rv = apr_bcrypt_encode(pw, ctx->cost, (unsigned char*)salt, 16, 262 ctx->out, ctx->out_len); 263 if (rv != APR_SUCCESS) { 264 ctx->errstr = apr_psprintf(ctx->pool, "Unable to encode with " 265 "bcrypt: %pm", &rv); 266 ret = ERR_PWMISMATCH; 267 break; 268 } 269 break; 270#endif /* BCRYPT_ALGO_SUPPORTED */ 271 272 default: 273 apr_file_printf(errfile, "mkhash(): BUG: invalid algorithm %d", 274 ctx->alg); 275 abort(); 276 } 277 memset(pw, '\0', strlen(pw)); 278 return ret; 279} 280 281int parse_common_options(struct passwd_ctx *ctx, char opt, 282 const char *opt_arg) 283{ 284 switch (opt) { 285 case 'b': 286 ctx->passwd_src = PW_ARG; 287 break; 288 case 'i': 289 ctx->passwd_src = PW_STDIN; 290 break; 291 case 'm': 292 ctx->alg = ALG_APMD5; 293 break; 294 case 's': 295 ctx->alg = ALG_APSHA; 296 break; 297 case 'p': 298 ctx->alg = ALG_PLAIN; 299#if !PLAIN_ALGO_SUPPORTED 300 /* Backward compatible behavior: Just print a warning */ 301 apr_file_printf(errfile, 302 "Warning: storing passwords as plain text might just " 303 "not work on this platform." NL); 304#endif 305 break; 306 case 'd': 307#if CRYPT_ALGO_SUPPORTED 308 ctx->alg = ALG_CRYPT; 309#else 310 /* Backward compatible behavior: Use MD5. OK since MD5 is more secure */ 311 apr_file_printf(errfile, 312 "Warning: CRYPT algorithm not supported on this " 313 "platform." NL 314 "Automatically using MD5 format." NL); 315 ctx->alg = ALG_APMD5; 316#endif 317 break; 318 case 'B': 319#if BCRYPT_ALGO_SUPPORTED 320 ctx->alg = ALG_BCRYPT; 321#else 322 /* Don't fall back to something less secure */ 323 ctx->errstr = "BCRYPT algorithm not supported on this platform"; 324 return ERR_ALG_NOT_SUPP; 325#endif 326 break; 327 case 'C': { 328 char *endptr; 329 long num = strtol(opt_arg, &endptr, 10); 330 if (*endptr != '\0' || num <= 0) { 331 ctx->errstr = "argument to -C must be a positive integer"; 332 return ERR_SYNTAX; 333 } 334 ctx->cost = num; 335 break; 336 } 337 default: 338 apr_file_printf(errfile, 339 "parse_common_options(): BUG: invalid option %c", 340 opt); 341 abort(); 342 } 343 return 0; 344} 345