source: trunk/third-party/shttpd/auth.c @ 6644

Last change on this file since 6644 was 6644, checked in by charles, 13 years ago

upgrade to shttpd 1.42

File size: 9.8 KB
Line 
1/*
2 * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
3 * All rights reserved
4 *
5 * "THE BEER-WARE LICENSE" (Revision 42):
6 * Sergey Lyubka wrote this file.  As long as you retain this notice you
7 * can do whatever you want with this stuff. If we meet some day, and you think
8 * this stuff is worth it, you can buy me a beer in return.
9 */
10
11#include "defs.h"
12
13#if !defined(NO_AUTH)
14/*
15 * Stringify binary data. Output buffer must be twice as big as input,
16 * because each byte takes 2 bytes in string representation
17 */
18static void
19bin2str(char *to, const unsigned char *p, size_t len)
20{
21        const char      *hex = "0123456789abcdef";
22
23        for (;len--; p++) {
24                *to++ = hex[p[0] >> 4];
25                *to++ = hex[p[0] & 0x0f];
26        }
27}
28
29/*
30 * Return stringified MD5 hash for list of vectors.
31 * buf must point to at least 32-bytes long buffer
32 */
33static void
34md5(char *buf, ...)
35{
36        unsigned char   hash[16];
37        const struct vec *v;
38        va_list         ap;
39        MD5_CTX ctx;
40        int             i;
41
42        MD5Init(&ctx);
43
44        va_start(ap, buf);
45        for (i = 0; (v = va_arg(ap, const struct vec *)) != NULL; i++) {
46                assert(v->len >= 0);
47                if (v->len == 0)
48                        continue;
49                if (i > 0)
50                        MD5Update(&ctx, (unsigned char *) ":", 1);
51                MD5Update(&ctx,(unsigned char *)v->ptr,(unsigned int)v->len);
52        }
53        va_end(ap);
54
55        MD5Final(hash, &ctx);
56        bin2str(buf, hash, sizeof(hash));
57}
58
59/*
60 * Compare to vectors. Return 1 if they are equal
61 */
62static int
63vcmp(const struct vec *v1, const struct vec *v2)
64{
65        return (v1->len == v2->len && !memcmp(v1->ptr, v2->ptr, v1->len));
66}
67
68struct digest {
69        struct vec      user;
70        struct vec      uri;
71        struct vec      nonce;
72        struct vec      cnonce;
73        struct vec      resp;
74        struct vec      qop;
75        struct vec      nc;
76};
77
78static const struct auth_keyword {
79        size_t          offset;
80        struct vec      vec;
81} known_auth_keywords[] = {
82        {offsetof(struct digest, user),         {"username=",   9}},
83        {offsetof(struct digest, cnonce),       {"cnonce=",     7}},
84        {offsetof(struct digest, resp),         {"response=",   9}},
85        {offsetof(struct digest, uri),          {"uri=",        4}},
86        {offsetof(struct digest, qop),          {"qop=",        4}},
87        {offsetof(struct digest, nc),           {"nc=",         3}},
88        {offsetof(struct digest, nonce),        {"nonce=",      6}},
89        {0,                                     {NULL,          0}}
90};
91
92static void
93parse_authorization_header(const struct vec *h, struct digest *dig)
94{
95        const unsigned char     *p, *e, *s;
96        struct vec              *v, vec;
97        const struct auth_keyword *kw;
98
99        (void) memset(dig, 0, sizeof(*dig));
100        p = (unsigned char *) h->ptr + 7;
101        e = (unsigned char *) h->ptr + h->len;
102
103        while (p < e) {
104
105                /* Skip spaces */
106                while (p < e && (*p == ' ' || *p == ','))
107                        p++;
108
109                /* Skip to "=" */
110                for (s = p; s < e && *s != '='; )
111                        s++;
112                s++;
113
114                /* Is it known keyword ? */
115                for (kw = known_auth_keywords; kw->vec.len > 0; kw++)
116                        if (kw->vec.len <= s - p &&
117                            !memcmp(p, kw->vec.ptr, kw->vec.len))
118                                break;
119
120                if (kw->vec.len == 0)
121                        v = &vec;               /* Dummy placeholder    */
122                else
123                        v = (struct vec *) ((char *) dig + kw->offset);
124
125                if (*s == '"') {
126                        p = ++s;
127                        while (p < e && *p != '"')
128                                p++;
129                } else {
130                        p = s;
131                        while (p < e && *p != ' ' && *p != ',')
132                                p++;
133                }
134
135                v->ptr = (char *) s;
136                v->len = p - s;
137
138                if (*p == '"')
139                        p++;
140
141                DBG(("auth field [%.*s]", v->len, v->ptr));
142        }
143}
144
145/*
146 * Check the user's password, return 1 if OK
147 */
148static int
149check_password(int method, const struct vec *ha1, const struct digest *digest)
150{
151        char            a2[32], resp[32];
152        struct vec      vec_a2;
153
154        /* XXX  Due to a bug in MSIE, we do not compare the URI  */
155        /* Also, we do not check for authentication timeout */
156        if (/*strcmp(dig->uri, c->ouri) != 0 || */
157            digest->resp.len != 32 /*||
158            now - strtoul(dig->nonce, NULL, 10) > 3600 */)
159                return (0);
160
161        md5(a2, &_shttpd_known_http_methods[method], &digest->uri, NULL);
162        vec_a2.ptr = a2;
163        vec_a2.len = sizeof(a2);
164        md5(resp, ha1, &digest->nonce, &digest->nc,
165            &digest->cnonce, &digest->qop, &vec_a2, NULL);
166        DBG(("%s: uri [%.*s] expected_resp [%.*s] resp [%.*s]",
167            "check_password", digest->uri.len, digest->uri.ptr,
168            32, resp, digest->resp.len, digest->resp.ptr));
169
170        return (!memcmp(resp, digest->resp.ptr, 32));
171}
172
173static FILE *
174open_auth_file(struct shttpd_ctx *ctx, const char *path)
175{
176        char            name[FILENAME_MAX];
177        const char      *p, *e;
178        FILE            *fp = NULL;
179        int             fd;
180
181        if (ctx->options[OPT_AUTH_GPASSWD] != NULL) {
182                /* Use global passwords file */
183                _shttpd_snprintf(name, sizeof(name), "%s",
184                    ctx->options[OPT_AUTH_GPASSWD]);
185        } else {
186                /*
187                 * Try to find .htpasswd in requested directory.
188                 * Given the path, create the path to .htpasswd file
189                 * in the same directory. Find the right-most
190                 * directory separator character first. That would be the
191                 * directory name. If directory separator character is not
192                 * found, 'e' will point to 'p'.
193                 */
194                for (p = path, e = p + strlen(p) - 1; e > p; e--)
195                        if (IS_DIRSEP_CHAR(*e))
196                                break;
197
198                /*
199                 * Make up the path by concatenating directory name and
200                 * .htpasswd file name.
201                 */
202                (void) _shttpd_snprintf(name, sizeof(name), "%.*s/%s",
203                    (int) (e - p), p, HTPASSWD);
204        }
205
206        if ((fd = _shttpd_open(name, O_RDONLY, 0)) == -1) {
207                DBG(("open_auth_file: open(%s)", name));
208        } else if ((fp = fdopen(fd, "r")) == NULL) {
209                DBG(("open_auth_file: fdopen(%s)", name));
210                (void) close(fd);
211        }
212
213        return (fp);
214}
215
216/*
217 * Parse the line from htpasswd file. Line should be in form of
218 * "user:domain:ha1". Fill in the vector values. Return 1 if successful.
219 */
220static int
221parse_htpasswd_line(const char *s, struct vec *user,
222                                struct vec *domain, struct vec *ha1)
223{
224        user->len = domain->len = ha1->len = 0;
225
226        for (user->ptr = s; *s != '\0' && *s != ':'; s++, user->len++);
227        if (*s++ != ':')
228                return (0);
229
230        for (domain->ptr = s; *s != '\0' && *s != ':'; s++, domain->len++);
231        if (*s++ != ':')
232                return (0);
233
234        for (ha1->ptr = s; *s != '\0' && !isspace(* (unsigned char *) s);
235            s++, ha1->len++);
236
237        DBG(("parse_htpasswd_line: [%.*s] [%.*s] [%.*s]", user->len, user->ptr,
238            domain->len, domain->ptr, ha1->len, ha1->ptr));
239
240        return (user->len > 0 && domain->len > 0 && ha1->len > 0);
241}
242
243/*
244 * Authorize against the opened passwords file. Return 1 if authorized.
245 */
246static int
247authorize(struct conn *c, FILE *fp)
248{
249        struct vec      *auth_vec = &c->ch.auth.v_vec;
250        struct vec      *user_vec = &c->ch.user.v_vec;
251        struct vec      user, domain, ha1;
252        struct digest   digest;
253        int             ok = 0;
254        char            line[256];
255
256        if (auth_vec->len > 20 &&
257            !_shttpd_strncasecmp(auth_vec->ptr, "Digest ", 7)) {
258
259                parse_authorization_header(auth_vec, &digest);
260                *user_vec = digest.user;
261
262                while (fgets(line, sizeof(line), fp) != NULL) {
263
264                        if (!parse_htpasswd_line(line, &user, &domain, &ha1))
265                                continue;
266
267                        DBG(("[%.*s] [%.*s] [%.*s]", user.len, user.ptr,
268                            domain.len, domain.ptr, ha1.len, ha1.ptr));
269
270                        if (vcmp(user_vec, &user) &&
271                            !memcmp(c->ctx->options[OPT_AUTH_REALM],
272                            domain.ptr, domain.len)) {
273                                ok = check_password(c->method, &ha1, &digest);
274                                break;
275                        }
276                }
277        }
278
279        return (ok);
280}
281
282int
283_shttpd_check_authorization(struct conn *c, const char *path)
284{
285        FILE            *fp = NULL;
286        int             len, n, authorized = 1;
287        const char      *p, *s = c->ctx->options[OPT_PROTECT];
288        char            protected_path[FILENAME_MAX];
289
290        FOR_EACH_WORD_IN_LIST(s, len) {
291
292                if ((p = memchr(s, '=', len)) == NULL || p >= s + len || p == s)
293                        continue;
294
295                if (!memcmp(c->uri, s, p - s)) {
296                       
297                        n = s + len - p;
298                        if (n > (int) sizeof(protected_path) - 1)
299                                n = sizeof(protected_path) - 1;
300
301                        _shttpd_strlcpy(protected_path, p + 1, n);
302
303                        if ((fp = fopen(protected_path, "r")) == NULL)
304                                _shttpd_elog(E_LOG, c,
305                                    "check_auth: cannot open %s: %s",
306                                    protected_path, strerror(errno));
307                        break;
308                }
309        }
310
311        if (fp == NULL)
312                fp = open_auth_file(c->ctx, path);
313
314        if (fp != NULL) {
315                authorized = authorize(c, fp);
316                (void) fclose(fp);
317        }
318
319        return (authorized);
320}
321
322int
323_shttpd_is_authorized_for_put(struct conn *c)
324{
325        FILE    *fp;
326        int     ret = 0;
327
328        if ((fp = fopen(c->ctx->options[OPT_AUTH_PUT], "r")) != NULL) {
329                ret = authorize(c, fp);
330                (void) fclose(fp);
331        }
332
333        return (ret);
334}
335
336void
337_shttpd_send_authorization_request(struct conn *c)
338{
339        char    buf[512];
340
341        (void) _shttpd_snprintf(buf, sizeof(buf), "Unauthorized\r\n"
342            "WWW-Authenticate: Digest qop=\"auth\", realm=\"%s\", "
343            "nonce=\"%lu\"", c->ctx->options[OPT_AUTH_REALM],
344            (unsigned long) _shttpd_current_time);
345
346        _shttpd_send_server_error(c, 401, buf);
347}
348
349/*
350 * Edit the passwords file.
351 */
352int
353_shttpd_edit_passwords(const char *fname, const char *domain,
354                const char *user, const char *pass)
355{
356        int             ret = EXIT_SUCCESS, found = 0;
357        struct vec      u, d, p;
358        char            line[512], tmp[FILENAME_MAX], ha1[32];
359        FILE            *fp = NULL, *fp2 = NULL;
360
361        (void) _shttpd_snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
362
363        /* Create the file if does not exist */
364        if ((fp = fopen(fname, "a+")))
365                (void) fclose(fp);
366
367        /* Open the given file and temporary file */
368        if ((fp = fopen(fname, "r")) == NULL)
369                _shttpd_elog(E_FATAL, NULL,
370                    "Cannot open %s: %s", fname, strerror(errno));
371        else if ((fp2 = fopen(tmp, "w+")) == NULL)
372                _shttpd_elog(E_FATAL, NULL,
373                    "Cannot open %s: %s", tmp, strerror(errno));
374
375        p.ptr = pass;
376        p.len = strlen(pass);
377
378        /* Copy the stuff to temporary file */
379        while (fgets(line, sizeof(line), fp) != NULL) {
380                u.ptr = line;
381                if ((d.ptr = strchr(line, ':')) == NULL)
382                        continue;
383                u.len = d.ptr - u.ptr;
384                d.ptr++;
385                if (strchr(d.ptr, ':') == NULL)
386                        continue;
387                d.len = strchr(d.ptr, ':') - d.ptr;
388
389                if ((int) strlen(user) == u.len &&
390                    !memcmp(user, u.ptr, u.len) &&
391                    (int) strlen(domain) == d.len &&
392                    !memcmp(domain, d.ptr, d.len)) {
393                        found++;
394                        md5(ha1, &u, &d, &p, NULL);
395                        (void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1);
396                } else {
397                        (void) fprintf(fp2, "%s", line);
398                }
399        }
400
401        /* If new user, just add it */
402        if (found == 0) {
403                u.ptr = user; u.len = strlen(user);
404                d.ptr = domain; d.len = strlen(domain);
405                md5(ha1, &u, &d, &p, NULL);
406                (void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1);
407        }
408
409        /* Close files */
410        (void) fclose(fp);
411        (void) fclose(fp2);
412
413        /* Put the temp file in place of real file */
414        (void) _shttpd_remove(fname);
415        (void) _shttpd_rename(tmp, fname);
416
417        return (ret);
418}
419#endif /* NO_AUTH */
Note: See TracBrowser for help on using the repository browser.