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