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

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

(third-party) upgrade shttpd from 1.39 to the latest, 1.41

File size: 9.2 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, &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
167        return (!memcmp(resp, digest->resp.ptr, 32));
168}
169
170static FILE *
171open_auth_file(struct shttpd_ctx *ctx, const char *path)
172{
173        char            name[FILENAME_MAX];
174        const char      *p, *e;
175        FILE            *fp = NULL;
176        int             fd;
177
178        if (ctx->options[OPT_AUTH_GPASSWD] != NULL) {
179                /* Use global passwords file */
180                my_snprintf(name, sizeof(name), "%s",
181                    ctx->options[OPT_AUTH_GPASSWD]);
182        } else {
183                /* Try to find .htpasswd in requested directory */
184                for (p = path, e = p + strlen(p) - 1; e > p; e--)
185                        if (IS_DIRSEP_CHAR(*e))
186                                break;
187
188                assert(IS_DIRSEP_CHAR(*e));
189                (void) my_snprintf(name, sizeof(name), "%.*s/%s",
190                    (int) (e - p), p, HTPASSWD);
191        }
192
193        if ((fd = my_open(name, O_RDONLY, 0)) == -1) {
194                DBG(("open_auth_file: open(%s)", name));
195        } else if ((fp = fdopen(fd, "r")) == NULL) {
196                DBG(("open_auth_file: fdopen(%s)", name));
197                (void) close(fd);
198        }
199
200        return (fp);
201}
202
203/*
204 * Parse the line from htpasswd file. Line should be in form of
205 * "user:domain:ha1". Fill in the vector values. Return 1 if successful.
206 */
207static int
208parse_htpasswd_line(const char *s, struct vec *user,
209                                struct vec *domain, struct vec *ha1)
210{
211        user->len = domain->len = ha1->len = 0;
212
213        for (user->ptr = s; *s != '\0' && *s != ':'; s++, user->len++);
214        if (*s++ != ':')
215                return (0);
216
217        for (domain->ptr = s; *s != '\0' && *s != ':'; s++, domain->len++);
218        if (*s++ != ':')
219                return (0);
220
221        for (ha1->ptr = s; *s != '\0' && !isspace(* (unsigned char *) s);
222            s++, ha1->len++);
223
224        DBG(("parse_htpasswd_line: [%.*s] [%.*s] [%.*s]", user->len, user->ptr,
225            domain->len, domain->ptr, ha1->len, ha1->ptr));
226
227        return (user->len > 0 && domain->len > 0 && ha1->len > 0);
228}
229
230/*
231 * Authorize against the opened passwords file. Return 1 if authorized.
232 */
233static int
234authorize(struct conn *c, FILE *fp)
235{
236        struct vec      *auth_vec = &c->ch.auth.v_vec;
237        struct vec      *user_vec = &c->ch.user.v_vec;
238        struct vec      user, domain, ha1;
239        struct digest   digest;
240        int             ok = 0;
241        char            line[256];
242
243        if (auth_vec->len > 20 &&
244            !my_strncasecmp(auth_vec->ptr, "Digest ", 7)) {
245
246                parse_authorization_header(auth_vec, &digest);
247                *user_vec = digest.user;
248
249                while (fgets(line, sizeof(line), fp) != NULL) {
250
251                        if (!parse_htpasswd_line(line, &user, &domain, &ha1))
252                                continue;
253
254                        DBG(("[%.*s] [%.*s] [%.*s]", user.len, user.ptr,
255                            domain.len, domain.ptr, ha1.len, ha1.ptr));
256
257                        if (vcmp(user_vec, &user) &&
258                            !memcmp(c->ctx->options[OPT_AUTH_REALM],
259                            domain.ptr, domain.len)) {
260                                ok = check_password(c->method, &ha1, &digest);
261                                break;
262                        }
263                }
264        }
265
266        return (ok);
267}
268
269int
270check_authorization(struct conn *c, const char *path)
271{
272        FILE            *fp = NULL;
273        int             len, n, authorized = 1;
274        const char      *p, *s = c->ctx->options[OPT_PROTECT];
275        char            protected_path[FILENAME_MAX];
276
277        FOR_EACH_WORD_IN_LIST(s, len) {
278
279                if ((p = memchr(s, '=', len)) == NULL || p >= s + len || p == s)
280                        continue;
281
282                if (!memcmp(c->uri, s, p - s)) {
283                       
284                        n = s + len - p;
285                        if (n > (int) sizeof(protected_path) - 1)
286                                n = sizeof(protected_path) - 1;
287
288                        my_strlcpy(protected_path, p + 1, n);
289
290                        if ((fp = fopen(protected_path, "r")) == NULL)
291                                elog(E_LOG, c, "check_auth: cannot open %s: %s",
292                                    protected_path, strerror(errno));
293                        break;
294                }
295        }
296
297        if (fp == NULL)
298                fp = open_auth_file(c->ctx, path);
299
300        if (fp != NULL) {
301                authorized = authorize(c, fp);
302                (void) fclose(fp);
303        }
304
305        return (authorized);
306}
307
308int
309is_authorized_for_put(struct conn *c)
310{
311        FILE    *fp;
312        int     ret = 0;
313
314        if ((fp = fopen(c->ctx->options[OPT_AUTH_PUT], "r")) != NULL) {
315                ret = authorize(c, fp);
316                (void) fclose(fp);
317        }
318
319        return (ret);
320}
321
322void
323send_authorization_request(struct conn *c)
324{
325        char    buf[512];
326
327        (void) my_snprintf(buf, sizeof(buf), "Unauthorized\r\n"
328            "WWW-Authenticate: Digest qop=\"auth\", realm=\"%s\", "
329            "nonce=\"%lu\"", c->ctx->options[OPT_AUTH_REALM],
330            (unsigned long) current_time);
331
332        send_server_error(c, 401, buf);
333}
334
335/*
336 * Edit the passwords file.
337 */
338int
339edit_passwords(const char *fname, const char *domain,
340                const char *user, const char *pass)
341{
342        int             ret = EXIT_SUCCESS, found = 0;
343        struct vec      u, d, p;
344        char            line[512], tmp[FILENAME_MAX], ha1[32];
345        FILE            *fp = NULL, *fp2 = NULL;
346
347        (void) my_snprintf(tmp, sizeof(tmp), "%s.tmp", fname);
348
349        /* Create the file if does not exist */
350        if ((fp = fopen(fname, "a+")))
351                (void) fclose(fp);
352
353        /* Open the given file and temporary file */
354        if ((fp = fopen(fname, "r")) == NULL)
355                elog(E_FATAL, 0, "Cannot open %s: %s", fname, strerror(errno));
356        else if ((fp2 = fopen(tmp, "w+")) == NULL)
357                elog(E_FATAL, 0, "Cannot open %s: %s", tmp, strerror(errno));
358
359        p.ptr = pass;
360        p.len = strlen(pass);
361
362        /* Copy the stuff to temporary file */
363        while (fgets(line, sizeof(line), fp) != NULL) {
364                u.ptr = line;
365                if ((d.ptr = strchr(line, ':')) == NULL)
366                        continue;
367                u.len = d.ptr - u.ptr;
368                d.ptr++;
369                if (strchr(d.ptr, ':') == NULL)
370                        continue;
371                d.len = strchr(d.ptr, ':') - d.ptr;
372
373                if ((int) strlen(user) == u.len &&
374                    !memcmp(user, u.ptr, u.len) &&
375                    (int) strlen(domain) == d.len &&
376                    !memcmp(domain, d.ptr, d.len)) {
377                        found++;
378                        md5(ha1, &u, &d, &p, NULL);
379                        (void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1);
380                } else {
381                        (void) fprintf(fp2, "%s", line);
382                }
383        }
384
385        /* If new user, just add it */
386        if (found == 0) {
387                u.ptr = user; u.len = strlen(user);
388                d.ptr = domain; d.len = strlen(domain);
389                md5(ha1, &u, &d, &p, NULL);
390                (void) fprintf(fp2, "%s:%s:%.32s\n", user, domain, ha1);
391        }
392
393        /* Close files */
394        (void) fclose(fp);
395        (void) fclose(fp2);
396
397        /* Put the temp file in place of real file */
398        (void) my_remove(fname);
399        (void) my_rename(tmp, fname);
400
401        return (ret);
402}
403#endif /* NO_AUTH */
Note: See TracBrowser for help on using the repository browser.