Annotation of embedaddon/lighttpd/src/http_auth.c, revision 1.1.1.3

1.1.1.3 ! misho       1: #include "first.h"
        !             2: 
1.1       misho       3: #include "server.h"
                      4: #include "log.h"
                      5: #include "http_auth.h"
                      6: #include "inet_ntop_cache.h"
                      7: #include "stream.h"
1.1.1.3 ! misho       8: #include "base64.h"
1.1       misho       9: 
                     10: #ifdef HAVE_CRYPT_H
                     11: # include <crypt.h>
                     12: #elif defined(__linux__)
                     13: /* linux needs _XOPEN_SOURCE */
                     14: # define _XOPEN_SOURCE
                     15: #endif
                     16: 
1.1.1.3 ! misho      17: #if defined(HAVE_LIBCRYPT) && !defined(HAVE_CRYPT)
        !            18: /* always assume crypt() is present if we have -lcrypt */
1.1       misho      19: # define HAVE_CRYPT
                     20: #endif
                     21: 
                     22: #include <sys/types.h>
                     23: #include <sys/stat.h>
                     24: 
                     25: #include <fcntl.h>
                     26: #include <stdlib.h>
                     27: #include <stdio.h>
                     28: #include <string.h>
                     29: #include <time.h>
                     30: #include <errno.h>
                     31: #include <unistd.h>
                     32: #include <ctype.h>
1.1.1.3 ! misho      33: #include <mysql/mysql.h>
1.1       misho      34: 
                     35: #include "md5.h"
                     36: 
                     37: #ifdef USE_OPENSSL
                     38: #include <openssl/sha.h>
                     39: #endif
                     40: 
1.1.1.3 ! misho      41: #include "safe_memclear.h"
        !            42: 
1.1       misho      43: #define HASHLEN 16
                     44: #define HASHHEXLEN 32
                     45: typedef unsigned char HASH[HASHLEN];
                     46: typedef char HASHHEX[HASHHEXLEN+1];
                     47: 
1.1.1.3 ! misho      48: static void CvtHex(const HASH Bin, char (*Hex)[33]) {
        !            49:        li_tohex(*Hex, sizeof(*Hex), (const char*) Bin, 16);
1.1       misho      50: }
                     51: 
1.1.1.3 ! misho      52: 
1.1       misho      53: /**
                     54:  * the $apr1$ handling is taken from apache 1.3.x
                     55:  */
                     56: 
                     57: /*
                     58:  * The apr_md5_encode() routine uses much code obtained from the FreeBSD 3.0
                     59:  * MD5 crypt() function, which is licenced as follows:
                     60:  * ----------------------------------------------------------------------------
                     61:  * "THE BEER-WARE LICENSE" (Revision 42):
                     62:  * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
                     63:  * can do whatever you want with this stuff. If we meet some day, and you think
                     64:  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
                     65:  * ----------------------------------------------------------------------------
                     66:  */
                     67: 
                     68: handler_t auth_ldap_init(server *srv, mod_auth_plugin_config *s);
                     69: 
                     70: static int http_auth_get_password(server *srv, mod_auth_plugin_data *p, buffer *username, buffer *realm, buffer *password) {
1.1.1.3 ! misho      71:        if (buffer_is_empty(username) || buffer_is_empty(realm)) return -1;
1.1       misho      72: 
                     73:        if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
1.1.1.3 ! misho      74:                FILE *fp;
        !            75:                char f_user[1024];
1.1       misho      76: 
1.1.1.3 ! misho      77:                if (buffer_string_is_empty(p->conf.auth_htdigest_userfile)) return -1;
1.1       misho      78: 
1.1.1.3 ! misho      79:                fp = fopen(p->conf.auth_htdigest_userfile->ptr, "r");
        !            80:                if (NULL == fp) {
1.1       misho      81:                        log_error_write(srv, __FILE__, __LINE__, "sbss", "opening digest-userfile", p->conf.auth_htdigest_userfile, "failed:", strerror(errno));
                     82: 
                     83:                        return -1;
                     84:                }
                     85: 
1.1.1.3 ! misho      86:                while (NULL != fgets(f_user, sizeof(f_user), fp)) {
        !            87:                        char *f_pwd, *f_realm;
        !            88:                        size_t u_len, r_len;
1.1       misho      89: 
1.1.1.3 ! misho      90:                        /* skip blank lines and comment lines (beginning '#') */
        !            91:                        if (f_user[0] == '#' || f_user[0] == '\n' || f_user[0] == '\0') continue;
1.1       misho      92: 
                     93:                        /*
                     94:                         * htdigest format
                     95:                         *
                     96:                         * user:realm:md5(user:realm:password)
                     97:                         */
                     98: 
1.1.1.3 ! misho      99:                        if (NULL == (f_realm = strchr(f_user, ':'))) {
1.1       misho     100:                                log_error_write(srv, __FILE__, __LINE__, "sbs",
                    101:                                                "parsed error in", p->conf.auth_htdigest_userfile,
                    102:                                                "expected 'username:realm:hashed password'");
                    103: 
1.1.1.3 ! misho     104:                                continue; /* skip bad lines */
1.1       misho     105:                        }
                    106: 
1.1.1.3 ! misho     107:                        if (NULL == (f_pwd = strchr(f_realm + 1, ':'))) {
1.1       misho     108:                                log_error_write(srv, __FILE__, __LINE__, "sbs",
                    109:                                                "parsed error in", p->conf.auth_plain_userfile,
                    110:                                                "expected 'username:realm:hashed password'");
                    111: 
1.1.1.3 ! misho     112:                                continue; /* skip bad lines */
1.1       misho     113:                        }
                    114: 
                    115:                        /* get pointers to the fields */
                    116:                        u_len = f_realm - f_user;
                    117:                        f_realm++;
                    118:                        r_len = f_pwd - f_realm;
                    119:                        f_pwd++;
                    120: 
1.1.1.3 ! misho     121:                        if (buffer_string_length(username) == u_len &&
        !           122:                            (buffer_string_length(realm) == r_len) &&
1.1       misho     123:                            (0 == strncmp(username->ptr, f_user, u_len)) &&
                    124:                            (0 == strncmp(realm->ptr, f_realm, r_len))) {
                    125:                                /* found */
                    126: 
1.1.1.3 ! misho     127:                                size_t pwd_len = strlen(f_pwd);
        !           128:                                if (f_pwd[pwd_len-1] == '\n') --pwd_len;
        !           129: 
1.1       misho     130:                                buffer_copy_string_len(password, f_pwd, pwd_len);
                    131: 
1.1.1.3 ! misho     132:                                fclose(fp);
        !           133:                                return 0;
1.1       misho     134:                        }
                    135:                }
                    136: 
1.1.1.3 ! misho     137:                fclose(fp);
1.1       misho     138:        } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD ||
                    139:                   p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
1.1.1.3 ! misho     140:                FILE *fp;
        !           141:                char f_user[1024];
1.1       misho     142:                buffer *auth_fn;
                    143: 
                    144:                auth_fn = (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) ? p->conf.auth_htpasswd_userfile : p->conf.auth_plain_userfile;
                    145: 
1.1.1.3 ! misho     146:                if (buffer_string_is_empty(auth_fn)) return -1;
1.1       misho     147: 
1.1.1.3 ! misho     148:                fp = fopen(auth_fn->ptr, "r");
        !           149:                if (NULL == fp) {
1.1       misho     150:                        log_error_write(srv, __FILE__, __LINE__, "sbss",
                    151:                                        "opening plain-userfile", auth_fn, "failed:", strerror(errno));
                    152: 
                    153:                        return -1;
                    154:                }
                    155: 
1.1.1.3 ! misho     156:                while (NULL != fgets(f_user, sizeof(f_user), fp)) {
        !           157:                        char *f_pwd;
        !           158:                        size_t u_len;
1.1       misho     159: 
1.1.1.3 ! misho     160:                        /* skip blank lines and comment lines (beginning '#') */
        !           161:                        if (f_user[0] == '#' || f_user[0] == '\n' || f_user[0] == '\0') continue;
1.1       misho     162: 
                    163:                        /*
                    164:                         * htpasswd format
                    165:                         *
                    166:                         * user:crypted passwd
                    167:                         */
                    168: 
1.1.1.3 ! misho     169:                        if (NULL == (f_pwd = strchr(f_user, ':'))) {
1.1       misho     170:                                log_error_write(srv, __FILE__, __LINE__, "sbs",
                    171:                                                "parsed error in", auth_fn,
                    172:                                                "expected 'username:hashed password'");
                    173: 
1.1.1.3 ! misho     174:                                continue; /* skip bad lines */
1.1       misho     175:                        }
                    176: 
                    177:                        /* get pointers to the fields */
                    178:                        u_len = f_pwd - f_user;
                    179:                        f_pwd++;
                    180: 
1.1.1.3 ! misho     181:                        if (buffer_string_length(username) == u_len &&
1.1       misho     182:                            (0 == strncmp(username->ptr, f_user, u_len))) {
                    183:                                /* found */
                    184: 
1.1.1.3 ! misho     185:                                size_t pwd_len = strlen(f_pwd);
        !           186:                                if (f_pwd[pwd_len-1] == '\n') --pwd_len;
        !           187: 
1.1       misho     188:                                buffer_copy_string_len(password, f_pwd, pwd_len);
                    189: 
1.1.1.3 ! misho     190:                                fclose(fp);
        !           191:                                return 0;
1.1       misho     192:                        }
                    193:                }
                    194: 
1.1.1.3 ! misho     195:                fclose(fp);
1.1       misho     196:        } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
1.1.1.3 ! misho     197:                return 0;
        !           198:        } else if (p->conf.auth_backend == AUTH_BACKEND_MYSQL) {
        !           199:                MYSQL_RES *result;
        !           200:                MYSQL_ROW row;
        !           201:                int port = atoi(p->conf.auth_mysql_port->ptr);
        !           202:                char q[255];
        !           203: 
        !           204:                if (p->conf.auth_mysql_socket->ptr != NULL)
        !           205:                        if (0 == strcmp(p->conf.auth_mysql_socket->ptr, "")) p->conf.auth_mysql_socket->ptr = NULL;
        !           206: 
        !           207:                p->conf.mysql_conn = mysql_init(NULL);
        !           208: 
        !           209:                if (mysql_real_connect(p->conf.mysql_conn, p->conf.auth_mysql_host->ptr, p->conf.auth_mysql_user->ptr, p->conf.auth_mysql_pass->ptr, p->conf.auth_mysql_db->ptr, port, p->conf.auth_mysql_socket->ptr, 0))
        !           210:                {
        !           211: //#define MY_HOSTING
        !           212: 
        !           213: #ifdef MY_HOSTING
        !           214:                        char my_full_realm[255];
        !           215:                        char *my_realm = NULL;
        !           216:                        char *my_domain = NULL;
        !           217: 
        !           218:                        char *uname;
        !           219:                        size_t unamelen;
        !           220: 
        !           221:                        unamelen = strlen(username->ptr);
        !           222:                        uname = malloc(unamelen*2+1);
        !           223: 
        !           224:                        mysql_real_escape_string(p->conf.mysql_conn,
        !           225:                                        uname, username->ptr,
        !           226:                                        (unsigned long)unamelen);
        !           227: 
        !           228:                        strcpy(my_full_realm, realm->ptr);
        !           229:                        my_realm = strtok(my_full_realm, "@");
        !           230: 
        !           231:                        if (my_realm != NULL)
        !           232:                                my_domain = strtok(NULL, "@");
        !           233: 
        !           234:                        sprintf(q, "SELECT %s FROM %s, %s WHERE %s='%s' AND %s='%s' AND %s='%s' AND %s=%s",
        !           235:                                p->conf.auth_mysql_col_pass->ptr,
        !           236: 
        !           237:                                p->conf.auth_mysql_users_table->ptr,
        !           238:                                p->conf.auth_mysql_domains_table->ptr,
        !           239: 
        !           240:                                p->conf.auth_mysql_col_user->ptr,
        !           241:                                uname,
        !           242: 
        !           243:                                p->conf.auth_mysql_col_realm->ptr,
        !           244:                                my_realm,
        !           245: 
        !           246:                                p->conf.auth_mysql_col_domain->ptr,
        !           247:                                my_domain,
        !           248: 
        !           249:                                p->conf.auth_mysql_domains_table_col_domain_id->ptr,
        !           250:                                p->conf.auth_mysql_users_table_col_domain_id->ptr
        !           251:                                );
        !           252: 
        !           253:                        free(uname);
        !           254: #else
        !           255:                        // sanitize username & realm by taguchi@ff.iij4u.or.jp
        !           256:                        char *uname, *urealm;
        !           257:                        size_t unamelen, urealmlen;
        !           258: 
        !           259:                        unamelen = strlen(username->ptr);
        !           260:                        urealmlen = strlen(realm->ptr);
        !           261:                        uname = malloc(unamelen*2+1);
        !           262:                        urealm = malloc(urealmlen*2+1);
        !           263: 
        !           264:                        mysql_real_escape_string(p->conf.mysql_conn,
        !           265:                                uname, username->ptr,
        !           266:                                (unsigned long)unamelen);
        !           267: 
        !           268:                        mysql_real_escape_string(p->conf.mysql_conn,
        !           269:                                urealm, realm->ptr,
        !           270:                                (unsigned long)unamelen);
        !           271: 
        !           272:                        mysql_real_escape_string(p->conf.mysql_conn,
        !           273:                                urealm, realm->ptr,
        !           274:                                (unsigned long)urealmlen);
        !           275: 
        !           276:                        sprintf(q, "SELECT %s FROM %s WHERE %s='%s' AND %s='%s'",
        !           277:                                p->conf.auth_mysql_col_pass->ptr,
        !           278:                                p->conf.auth_mysql_users_table->ptr,
        !           279:                                p->conf.auth_mysql_col_user->ptr,
        !           280:                                uname,
        !           281:                                p->conf.auth_mysql_col_realm->ptr,
        !           282:                                urealm
        !           283:                        );
        !           284: 
        !           285:                        free(uname);
        !           286:                        free(urealm);
        !           287: #endif
1.1       misho     288: 
1.1.1.3 ! misho     289:                        mysql_query(p->conf.mysql_conn, q);
        !           290:                        result = mysql_store_result(p->conf.mysql_conn);
        !           291:                        if (mysql_num_rows(result) == 1)
        !           292:                        {
        !           293:                                /* found */
        !           294:                                row = mysql_fetch_row(result);
        !           295:                                buffer_copy_string_len(password, row[0], strlen(row[0]));
        !           296: 
        !           297:                                return 0;
        !           298:                        } else
        !           299:                        {
        !           300:                                /* not found */
        !           301:                                return -1;
        !           302:                        }
        !           303: 
        !           304:                        mysql_free_result(result);
        !           305:                        mysql_close(p->conf.mysql_conn);
        !           306: 
        !           307:                        p->conf.mysql_conn = NULL;
        !           308:                } else
        !           309:                        return -1;
        !           310:        }
1.1       misho     311: }
                    312: 
                    313: int http_auth_match_rules(server *srv, array *req, const char *username, const char *group, const char *host) {
                    314:        const char *r = NULL, *rules = NULL;
                    315:        int username_len;
                    316:        data_string *require;
                    317: 
                    318:        UNUSED(group);
                    319:        UNUSED(host);
                    320: 
                    321:        require = (data_string *)array_get_element(req, "require");
1.1.1.3 ! misho     322:        if (!require) return -1; /*(should not happen; config is validated at startup)*/
1.1       misho     323: 
                    324:        /* if we get here, the user we got a authed user */
                    325:        if (0 == strcmp(require->value->ptr, "valid-user")) {
                    326:                return 0;
                    327:        }
                    328: 
                    329:        /* user=name1|group=name3|host=name4 */
                    330: 
                    331:        /* seperate the string by | */
                    332: #if 0
                    333:        log_error_write(srv, __FILE__, __LINE__, "sb", "rules", require->value);
                    334: #endif
                    335: 
                    336:        username_len = username ? strlen(username) : 0;
                    337: 
                    338:        r = rules = require->value->ptr;
                    339: 
                    340:        while (1) {
                    341:                const char *eq;
                    342:                const char *k, *v, *e;
                    343:                int k_len, v_len, r_len;
                    344: 
                    345:                e = strchr(r, '|');
                    346: 
                    347:                if (e) {
                    348:                        r_len = e - r;
                    349:                } else {
                    350:                        r_len = strlen(rules) - (r - rules);
                    351:                }
                    352: 
                    353:                /* from r to r + r_len is a rule */
                    354: 
                    355:                if (0 == strncmp(r, "valid-user", r_len)) {
                    356:                        log_error_write(srv, __FILE__, __LINE__, "sb",
                    357:                                        "parsing the 'require' section in 'auth.require' failed: valid-user cannot be combined with other require rules",
                    358:                                        require->value);
                    359:                        return -1;
                    360:                }
                    361: 
                    362:                /* search for = in the rules */
                    363:                if (NULL == (eq = strchr(r, '='))) {
                    364:                        log_error_write(srv, __FILE__, __LINE__, "sb",
                    365:                                        "parsing the 'require' section in 'auth.require' failed: a = is missing",
                    366:                                        require->value);
                    367:                        return -1;
                    368:                }
                    369: 
                    370:                /* = out of range */
                    371:                if (eq > r + r_len) {
                    372:                        log_error_write(srv, __FILE__, __LINE__, "sb",
                    373:                                        "parsing the 'require' section in 'auth.require' failed: = out of range",
                    374:                                        require->value);
                    375: 
                    376:                        return -1;
                    377:                }
                    378: 
                    379:                /* the part before the = is user|group|host */
                    380: 
                    381:                k = r;
                    382:                k_len = eq - r;
                    383:                v = eq + 1;
                    384:                v_len = r_len - k_len - 1;
                    385: 
                    386:                if (k_len == 4) {
                    387:                        if (0 == strncmp(k, "user", k_len)) {
                    388:                                if (username &&
                    389:                                    username_len == v_len &&
                    390:                                    0 == strncmp(username, v, v_len)) {
                    391:                                        return 0;
                    392:                                }
                    393:                        } else if (0 == strncmp(k, "host", k_len)) {
                    394:                                log_error_write(srv, __FILE__, __LINE__, "s", "host ... (not implemented)");
                    395:                        } else {
                    396:                                log_error_write(srv, __FILE__, __LINE__, "s", "unknown key");
                    397:                                return -1;
                    398:                        }
                    399:                } else if (k_len == 5) {
                    400:                        if (0 == strncmp(k, "group", k_len)) {
                    401:                                log_error_write(srv, __FILE__, __LINE__, "s", "group ... (not implemented)");
                    402:                        } else {
                    403:                                log_error_write(srv, __FILE__, __LINE__, "ss", "unknown key", k);
                    404:                                return -1;
                    405:                        }
                    406:                } else {
                    407:                        log_error_write(srv, __FILE__, __LINE__, "s", "unknown  key");
                    408:                        return -1;
                    409:                }
                    410: 
                    411:                if (!e) break;
                    412:                r = e + 1;
                    413:        }
                    414: 
                    415:        log_error_write(srv, __FILE__, __LINE__, "s", "nothing matched");
                    416: 
                    417:        return -1;
                    418: }
                    419: 
                    420: #define APR_MD5_DIGESTSIZE 16
                    421: #define APR1_ID "$apr1$"
                    422: 
                    423: /*
                    424:  * The following MD5 password encryption code was largely borrowed from
                    425:  * the FreeBSD 3.0 /usr/src/lib/libcrypt/crypt.c file, which is
                    426:  * licenced as stated at the top of this file.
                    427:  */
                    428: 
                    429: static void to64(char *s, unsigned long v, int n)
                    430: {
1.1.1.3 ! misho     431:        static const unsigned char itoa64[] =         /* 0 ... 63 => ASCII - 64 */
        !           432:                "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
1.1       misho     433: 
1.1.1.3 ! misho     434:        while (--n >= 0) {
        !           435:                *s++ = itoa64[v&0x3f];
        !           436:                v >>= 6;
        !           437:        }
1.1       misho     438: }
                    439: 
                    440: static void apr_md5_encode(const char *pw, const char *salt, char *result, size_t nbytes) {
1.1.1.3 ! misho     441:        /*
        !           442:         * Minimum size is 8 bytes for salt, plus 1 for the trailing NUL,
        !           443:         * plus 4 for the '$' separators, plus the password hash itself.
        !           444:         * Let's leave a goodly amount of leeway.
        !           445:         */
        !           446: 
        !           447:        char passwd[120], *p;
        !           448:        const char *sp, *ep;
        !           449:        unsigned char final[APR_MD5_DIGESTSIZE];
        !           450:        ssize_t sl, pl, i;
        !           451:        li_MD5_CTX ctx, ctx1;
        !           452:        unsigned long l;
        !           453: 
        !           454:        /*
        !           455:         * Refine the salt first.  It's possible we were given an already-hashed
        !           456:         * string as the salt argument, so extract the actual salt value from it
        !           457:         * if so.  Otherwise just use the string up to the first '$' as the salt.
        !           458:         */
        !           459:        sp = salt;
        !           460: 
        !           461:        /*
        !           462:         * If it starts with the magic string, then skip that.
        !           463:         */
        !           464:        if (!strncmp(sp, APR1_ID, strlen(APR1_ID))) {
        !           465:                sp += strlen(APR1_ID);
        !           466:        }
        !           467: 
        !           468:        /*
        !           469:         * It stops at the first '$' or 8 chars, whichever comes first
        !           470:         */
        !           471:        for (ep = sp; (*ep != '\0') && (*ep != '$') && (ep < (sp + 8)); ep++) {
        !           472:                continue;
        !           473:        }
        !           474: 
        !           475:        /*
        !           476:         * Get the length of the true salt
        !           477:         */
        !           478:        sl = ep - sp;
        !           479: 
        !           480:        /*
        !           481:         * 'Time to make the doughnuts..'
        !           482:         */
        !           483:        li_MD5_Init(&ctx);
        !           484: 
        !           485:        /*
        !           486:         * The password first, since that is what is most unknown
        !           487:         */
        !           488:        li_MD5_Update(&ctx, pw, strlen(pw));
        !           489: 
        !           490:        /*
        !           491:         * Then our magic string
        !           492:         */
        !           493:        li_MD5_Update(&ctx, APR1_ID, strlen(APR1_ID));
        !           494: 
        !           495:        /*
        !           496:         * Then the raw salt
        !           497:         */
        !           498:        li_MD5_Update(&ctx, sp, sl);
        !           499: 
        !           500:        /*
        !           501:         * Then just as many characters of the MD5(pw, salt, pw)
        !           502:         */
        !           503:        li_MD5_Init(&ctx1);
        !           504:        li_MD5_Update(&ctx1, pw, strlen(pw));
        !           505:        li_MD5_Update(&ctx1, sp, sl);
        !           506:        li_MD5_Update(&ctx1, pw, strlen(pw));
        !           507:        li_MD5_Final(final, &ctx1);
        !           508:        for (pl = strlen(pw); pl > 0; pl -= APR_MD5_DIGESTSIZE) {
        !           509:                li_MD5_Update(
        !           510:                        &ctx, final,
        !           511:                        (pl > APR_MD5_DIGESTSIZE) ? APR_MD5_DIGESTSIZE : pl);
        !           512:        }
        !           513: 
        !           514:        /*
        !           515:         * Don't leave anything around in vm they could use.
        !           516:         */
        !           517:        memset(final, 0, sizeof(final));
        !           518: 
        !           519:        /*
        !           520:         * Then something really weird...
        !           521:         */
        !           522:        for (i = strlen(pw); i != 0; i >>= 1) {
        !           523:                if (i & 1) {
        !           524:                        li_MD5_Update(&ctx, final, 1);
        !           525:                }
        !           526:                else {
        !           527:                        li_MD5_Update(&ctx, pw, 1);
        !           528:                }
        !           529:        }
        !           530: 
        !           531:        /*
        !           532:         * Now make the output string.  We know our limitations, so we
        !           533:         * can use the string routines without bounds checking.
        !           534:         */
        !           535:        strcpy(passwd, APR1_ID);
        !           536:        strncat(passwd, sp, sl);
        !           537:        strcat(passwd, "$");
        !           538: 
        !           539:        li_MD5_Final(final, &ctx);
        !           540: 
        !           541:        /*
        !           542:         * And now, just to make sure things don't run too fast..
        !           543:         * On a 60 Mhz Pentium this takes 34 msec, so you would
        !           544:         * need 30 seconds to build a 1000 entry dictionary...
        !           545:         */
        !           546:        for (i = 0; i < 1000; i++) {
        !           547:                li_MD5_Init(&ctx1);
        !           548:                if (i & 1) {
        !           549:                        li_MD5_Update(&ctx1, pw, strlen(pw));
        !           550:                }
        !           551:                else {
        !           552:                        li_MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
        !           553:                }
        !           554:                if (i % 3) {
        !           555:                        li_MD5_Update(&ctx1, sp, sl);
        !           556:                }
        !           557: 
        !           558:                if (i % 7) {
        !           559:                        li_MD5_Update(&ctx1, pw, strlen(pw));
        !           560:                }
        !           561: 
        !           562:                if (i & 1) {
        !           563:                        li_MD5_Update(&ctx1, final, APR_MD5_DIGESTSIZE);
        !           564:                }
        !           565:                else {
        !           566:                        li_MD5_Update(&ctx1, pw, strlen(pw));
        !           567:                }
        !           568:                li_MD5_Final(final,&ctx1);
        !           569:        }
        !           570: 
        !           571:        p = passwd + strlen(passwd);
        !           572: 
        !           573:        l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p, l, 4); p += 4;
        !           574:        l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p, l, 4); p += 4;
        !           575:        l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p, l, 4); p += 4;
        !           576:        l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p, l, 4); p += 4;
        !           577:        l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p, l, 4); p += 4;
        !           578:        l =                    final[11]                ; to64(p, l, 2); p += 2;
        !           579:        *p = '\0';
        !           580: 
        !           581:        /*
        !           582:         * Don't leave anything around in vm they could use.
        !           583:         */
        !           584:        safe_memclear(final, sizeof(final));
1.1       misho     585: 
                    586:        /* FIXME
                    587:         */
                    588: #define apr_cpystrn strncpy
1.1.1.3 ! misho     589:        apr_cpystrn(result, passwd, nbytes - 1);
1.1       misho     590: }
                    591: 
                    592: #ifdef USE_OPENSSL
                    593: static void apr_sha_encode(const char *pw, char *result, size_t nbytes) {
1.1.1.3 ! misho     594:        unsigned char digest[20];
        !           595:        size_t base64_written;
        !           596: 
        !           597:        SHA1((const unsigned char*) pw, strlen(pw), digest);
1.1       misho     598: 
                    599:        memset(result, 0, nbytes);
                    600: 
                    601:        /* need 5 bytes for "{SHA}", 28 for base64 (3 bytes -> 4 bytes) of SHA1 (20 bytes), 1 terminating */
                    602:        if (nbytes < 5 + 28 + 1) return;
                    603: 
1.1.1.3 ! misho     604:        memcpy(result, "{SHA}", 5);
        !           605:        base64_written = li_to_base64(result + 5, nbytes - 5, digest, 20, BASE64_STANDARD);
        !           606:        force_assert(base64_written == 28);
        !           607:        result[5 + base64_written] = '\0'; /* terminate string */
1.1       misho     608: }
                    609: #endif
                    610: 
                    611: /**
                    612:  *
                    613:  *
                    614:  * @param password password-string from the auth-backend
                    615:  * @param pw       password-string from the client
                    616:  */
                    617: 
                    618: static int http_auth_basic_password_compare(server *srv, mod_auth_plugin_data *p, array *req, buffer *username, buffer *realm, buffer *password, const char *pw) {
                    619:        UNUSED(srv);
                    620:        UNUSED(req);
                    621: 
                    622:        if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
                    623:                /*
                    624:                 * htdigest format
                    625:                 *
                    626:                 * user:realm:md5(user:realm:password)
                    627:                 */
                    628: 
                    629:                li_MD5_CTX Md5Ctx;
                    630:                HASH HA1;
1.1.1.3 ! misho     631:                char a1[33];
1.1       misho     632: 
                    633:                li_MD5_Init(&Md5Ctx);
1.1.1.3 ! misho     634:                li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(username));
        !           635:                li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
        !           636:                li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(realm));
        !           637:                li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho     638:                li_MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw));
                    639:                li_MD5_Final(HA1, &Md5Ctx);
                    640: 
1.1.1.3 ! misho     641:                CvtHex(HA1, &a1);
1.1       misho     642: 
                    643:                if (0 == strcmp(password->ptr, a1)) {
                    644:                        return 0;
                    645:                }
                    646:        } else if (p->conf.auth_backend == AUTH_BACKEND_HTPASSWD) {
                    647:                char sample[120];
                    648:                if (!strncmp(password->ptr, APR1_ID, strlen(APR1_ID))) {
                    649:                        /*
                    650:                         * The hash was created using $apr1$ custom algorithm.
                    651:                         */
                    652:                        apr_md5_encode(pw, password->ptr, sample, sizeof(sample));
                    653:                        return (strcmp(sample, password->ptr) == 0) ? 0 : 1;
                    654: #ifdef USE_OPENSSL
                    655:                } else if (0 == strncmp(password->ptr, "{SHA}", 5)) {
                    656:                        apr_sha_encode(pw, sample, sizeof(sample));
                    657:                        return (strcmp(sample, password->ptr) == 0) ? 0 : 1;
                    658: #endif
                    659:                } else {
1.1.1.3 ! misho     660: #if defined(HAVE_CRYPT_R) || defined(HAVE_CRYPT)
1.1       misho     661:                        char *crypted;
1.1.1.3 ! misho     662: #if defined(HAVE_CRYPT_R)
        !           663:                        struct crypt_data crypt_tmp_data;
        !           664:                        crypt_tmp_data.initialized = 0;
        !           665: #endif
1.1       misho     666: 
                    667:                        /* a simple DES password is 2 + 11 characters. everything else should be longer. */
1.1.1.3 ! misho     668:                        if (buffer_string_length(password) < 13) {
1.1       misho     669:                                return -1;
                    670:                        }
                    671: 
1.1.1.3 ! misho     672: #if defined(HAVE_CRYPT_R)
        !           673:                        if (0 == (crypted = crypt_r(pw, password->ptr, &crypt_tmp_data))) {
        !           674: #else
1.1       misho     675:                        if (0 == (crypted = crypt(pw, password->ptr))) {
1.1.1.3 ! misho     676: #endif
1.1       misho     677:                                /* crypt failed. */
                    678:                                return -1;
                    679:                        }
                    680: 
                    681:                        if (0 == strcmp(password->ptr, crypted)) {
                    682:                                return 0;
                    683:                        }
                    684: #endif
                    685:                }
                    686:        } else if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
                    687:                if (0 == strcmp(password->ptr, pw)) {
                    688:                        return 0;
                    689:                }
                    690:        } else if (p->conf.auth_backend == AUTH_BACKEND_LDAP) {
                    691: #ifdef USE_LDAP
                    692:                LDAP *ldap;
                    693:                LDAPMessage *lm, *first;
                    694:                char *dn;
                    695:                int ret;
                    696:                char *attrs[] = { LDAP_NO_ATTRS, NULL };
1.1.1.3 ! misho     697:                size_t i, len;
1.1       misho     698: 
                    699:                /* for now we stay synchronous */
                    700: 
                    701:                /*
                    702:                 * 1. connect anonymously (done in plugin init)
                    703:                 * 2. get DN for uid = username
                    704:                 * 3. auth against ldap server
                    705:                 * 4. (optional) check a field
                    706:                 * 5. disconnect
                    707:                 *
                    708:                 */
                    709: 
                    710:                /* check username
                    711:                 *
                    712:                 * we have to protect us againt username which modifies out filter in
                    713:                 * a unpleasant way
                    714:                 */
                    715: 
1.1.1.3 ! misho     716:                len = buffer_string_length(username);
        !           717:                for (i = 0; i < len; i++) {
1.1       misho     718:                        char c = username->ptr[i];
                    719: 
                    720:                        if (!isalpha(c) &&
                    721:                            !isdigit(c) &&
                    722:                            (c != ' ') &&
                    723:                            (c != '@') &&
                    724:                            (c != '-') &&
                    725:                            (c != '_') &&
                    726:                            (c != '.') ) {
                    727: 
                    728:                                log_error_write(srv, __FILE__, __LINE__, "sbd",
                    729:                                        "ldap: invalid character (- _.@a-zA-Z0-9 allowed) in username:", username, i);
                    730: 
                    731:                                return -1;
                    732:                        }
                    733:                }
                    734: 
                    735:                if (p->conf.auth_ldap_allow_empty_pw != 1 && pw[0] == '\0')
                    736:                        return -1;
                    737: 
                    738:                /* build filter */
1.1.1.3 ! misho     739:                buffer_copy_buffer(p->ldap_filter, p->conf.ldap_filter_pre);
1.1       misho     740:                buffer_append_string_buffer(p->ldap_filter, username);
                    741:                buffer_append_string_buffer(p->ldap_filter, p->conf.ldap_filter_post);
                    742: 
                    743: 
                    744:                /* 2. */
                    745:                if (p->anon_conf->ldap == NULL ||
                    746:                    LDAP_SUCCESS != (ret = ldap_search_s(p->anon_conf->ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) {
                    747: 
                    748:                        /* try again; the ldap library sometimes fails for the first call but reconnects */
                    749:                        if (p->anon_conf->ldap == NULL || ret != LDAP_SERVER_DOWN ||
                    750:                            LDAP_SUCCESS != (ret = ldap_search_s(p->anon_conf->ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) {
                    751: 
                    752:                                if (auth_ldap_init(srv, p->anon_conf) != HANDLER_GO_ON)
                    753:                                        return -1;
                    754: 
1.1.1.2   misho     755:                                if (NULL == p->anon_conf->ldap) return -1;
                    756: 
                    757:                                if (LDAP_SUCCESS != (ret = ldap_search_s(p->anon_conf->ldap, p->conf.auth_ldap_basedn->ptr, LDAP_SCOPE_SUBTREE, p->ldap_filter->ptr, attrs, 0, &lm))) {
1.1       misho     758:                                        log_error_write(srv, __FILE__, __LINE__, "sssb",
                    759:                                                        "ldap:", ldap_err2string(ret), "filter:", p->ldap_filter);
                    760:                                        return -1;
                    761:                                }
                    762:                        }
                    763:                }
                    764: 
                    765:                if (NULL == (first = ldap_first_entry(p->anon_conf->ldap, lm))) {
                    766:                        log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
                    767: 
                    768:                        ldap_msgfree(lm);
                    769: 
                    770:                        return -1;
                    771:                }
                    772: 
                    773:                if (NULL == (dn = ldap_get_dn(p->anon_conf->ldap, first))) {
                    774:                        log_error_write(srv, __FILE__, __LINE__, "s", "ldap ...");
                    775: 
                    776:                        ldap_msgfree(lm);
                    777: 
                    778:                        return -1;
                    779:                }
                    780: 
                    781:                ldap_msgfree(lm);
                    782: 
                    783: 
                    784:                /* 3. */
                    785:                if (NULL == (ldap = ldap_init(p->conf.auth_ldap_hostname->ptr, LDAP_PORT))) {
                    786:                        log_error_write(srv, __FILE__, __LINE__, "ss", "ldap ...", strerror(errno));
                    787:                        return -1;
                    788:                }
                    789: 
                    790:                ret = LDAP_VERSION3;
                    791:                if (LDAP_OPT_SUCCESS != (ret = ldap_set_option(ldap, LDAP_OPT_PROTOCOL_VERSION, &ret))) {
                    792:                        log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
                    793: 
                    794:                        ldap_unbind_s(ldap);
                    795: 
                    796:                        return -1;
                    797:                }
                    798: 
                    799:                if (p->conf.auth_ldap_starttls == 1) {
                    800:                        if (LDAP_OPT_SUCCESS != (ret = ldap_start_tls_s(ldap, NULL,  NULL))) {
                    801:                                log_error_write(srv, __FILE__, __LINE__, "ss", "ldap startTLS failed:", ldap_err2string(ret));
                    802: 
                    803:                                ldap_unbind_s(ldap);
                    804: 
                    805:                                return -1;
                    806:                        }
                    807:                }
                    808: 
                    809: 
                    810:                if (LDAP_SUCCESS != (ret = ldap_simple_bind_s(ldap, dn, pw))) {
                    811:                        log_error_write(srv, __FILE__, __LINE__, "ss", "ldap:", ldap_err2string(ret));
                    812: 
                    813:                        ldap_unbind_s(ldap);
                    814: 
                    815:                        return -1;
                    816:                }
                    817: 
                    818:                /* 5. */
                    819:                ldap_unbind_s(ldap);
                    820: 
                    821:                /* everything worked, good, access granted */
                    822: 
                    823:                return 0;
                    824: #endif
1.1.1.3 ! misho     825:        } else if (p->conf.auth_backend == AUTH_BACKEND_MYSQL) {
        !           826:                /*
        !           827:                        we check for md5 crypt() now
        !           828:                        request by Nicola Tiling <nti@w4w.net>
        !           829:                */
        !           830:                if (password->ptr[0] == '$' && password->ptr[2] == '$')
        !           831:                {
        !           832:                        char salt[32];
        !           833:                        char *crypted;
        !           834:                        size_t salt_len = 0;
        !           835:                        char *dollar = NULL;
        !           836: 
        !           837:                        if (NULL == (dollar = strchr(password->ptr + 3, '$'))) {
        !           838:                                fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
        !           839:                                return -1;
        !           840:                        }
        !           841: 
        !           842:                        salt_len = dollar - password->ptr;
        !           843: 
        !           844:                        if (salt_len > sizeof(salt) - 1)
        !           845:                        {
        !           846:                                fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
        !           847:                                return -1;
        !           848:                        }
        !           849: 
        !           850:                        strncpy(salt, password->ptr, salt_len);
        !           851: 
        !           852:                        salt[salt_len] = '\0';
        !           853: 
        !           854:                        crypted = crypt(pw, salt);
        !           855: 
        !           856:                        if (0 == strcmp(password->ptr, crypted))
        !           857:                        {
        !           858:                                return 0;
        !           859:                        } else {
        !           860:                                fprintf(stderr, "%s.%d\n", __FILE__, __LINE__);
        !           861:                        }
        !           862:                } else
        !           863:                /* plain md5 check now */
        !           864:                {
        !           865:                        li_MD5_CTX Md5Ctx;
        !           866:                        HASH HA1;
        !           867:                        char a1[256];
        !           868: 
        !           869:                        li_MD5_Init(&Md5Ctx);
        !           870:                        li_MD5_Update(&Md5Ctx, (unsigned char *)pw, strlen(pw));
        !           871:                        li_MD5_Final(HA1, &Md5Ctx);
        !           872: 
        !           873:                        CvtHex(HA1, a1);
        !           874: 
        !           875:                        if (0 == strcmp(password->ptr, a1)) {
        !           876:                                return 0;
        !           877:                        }
        !           878:                }
1.1       misho     879:        }
                    880:        return -1;
                    881: }
                    882: 
                    883: int http_auth_basic_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, const char *realm_str) {
                    884:        buffer *username, *password;
                    885:        char *pw;
                    886: 
                    887:        data_string *realm;
                    888: 
                    889:        realm = (data_string *)array_get_element(req, "realm");
1.1.1.3 ! misho     890:        if (!realm) return 0; /*(should not happen; config is validated at startup)*/
1.1       misho     891: 
                    892:        username = buffer_init();
                    893: 
1.1.1.3 ! misho     894:        if (!buffer_append_base64_decode(username, realm_str, strlen(realm_str), BASE64_STANDARD)) {
1.1       misho     895:                log_error_write(srv, __FILE__, __LINE__, "sb", "decodeing base64-string failed", username);
                    896: 
                    897:                buffer_free(username);
                    898:                return 0;
                    899:        }
                    900: 
                    901:        /* r2 == user:password */
                    902:        if (NULL == (pw = strchr(username->ptr, ':'))) {
                    903:                log_error_write(srv, __FILE__, __LINE__, "sb", ": is missing in", username);
                    904: 
                    905:                buffer_free(username);
                    906:                return 0;
                    907:        }
                    908: 
1.1.1.3 ! misho     909:        buffer_string_set_length(username, pw - username->ptr);
        !           910:        pw++;
1.1       misho     911: 
                    912:        password = buffer_init();
                    913:        /* copy password to r1 */
                    914:        if (http_auth_get_password(srv, p, username, realm->value, password)) {
                    915:                buffer_free(username);
                    916:                buffer_free(password);
                    917: 
                    918:                if (AUTH_BACKEND_UNSET == p->conf.auth_backend) {
                    919:                        log_error_write(srv, __FILE__, __LINE__, "s", "auth.backend is not set");
                    920:                } else {
                    921:                        log_error_write(srv, __FILE__, __LINE__, "ss", "get_password failed, IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
                    922:                }
                    923: 
                    924:                return 0;
                    925:        }
                    926: 
                    927:        /* password doesn't match */
                    928:        if (http_auth_basic_password_compare(srv, p, req, username, realm->value, password, pw)) {
                    929:                log_error_write(srv, __FILE__, __LINE__, "sbsBss", "password doesn't match for", con->uri.path, "username:", username, ", IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
                    930: 
                    931:                buffer_free(username);
                    932:                buffer_free(password);
                    933: 
                    934:                return 0;
                    935:        }
                    936: 
                    937:        /* value is our allow-rules */
                    938:        if (http_auth_match_rules(srv, req, username->ptr, NULL, NULL)) {
                    939:                buffer_free(username);
                    940:                buffer_free(password);
                    941: 
                    942:                log_error_write(srv, __FILE__, __LINE__, "s", "rules didn't match");
                    943: 
                    944:                return 0;
                    945:        }
                    946: 
                    947:        /* remember the username */
1.1.1.3 ! misho     948:        buffer_copy_buffer(p->auth_user, username);
1.1       misho     949: 
                    950:        buffer_free(username);
                    951:        buffer_free(password);
                    952: 
                    953:        return 1;
                    954: }
                    955: 
                    956: typedef struct {
                    957:        const char *key;
                    958:        int key_len;
                    959:        char **ptr;
                    960: } digest_kv;
                    961: 
                    962: /* return values: -1: error/bad request, 0: failed, 1: success */
                    963: int http_auth_digest_check(server *srv, connection *con, mod_auth_plugin_data *p, array *req, const char *realm_str) {
1.1.1.3 ! misho     964:        char a1[33];
        !           965:        char a2[33];
1.1       misho     966: 
                    967:        char *username = NULL;
                    968:        char *realm = NULL;
                    969:        char *nonce = NULL;
                    970:        char *uri = NULL;
                    971:        char *algorithm = NULL;
                    972:        char *qop = NULL;
                    973:        char *cnonce = NULL;
                    974:        char *nc = NULL;
                    975:        char *respons = NULL;
                    976: 
                    977:        char *e, *c;
                    978:        const char *m = NULL;
                    979:        int i;
                    980:        buffer *password, *b, *username_buf, *realm_buf;
                    981: 
                    982:        li_MD5_CTX Md5Ctx;
                    983:        HASH HA1;
                    984:        HASH HA2;
                    985:        HASH RespHash;
                    986:        HASHHEX HA2Hex;
                    987: 
                    988: 
                    989:        /* init pointers */
                    990: #define S(x) \
                    991:        x, sizeof(x)-1, NULL
                    992:        digest_kv dkv[10] = {
                    993:                { S("username=") },
                    994:                { S("realm=") },
                    995:                { S("nonce=") },
                    996:                { S("uri=") },
                    997:                { S("algorithm=") },
                    998:                { S("qop=") },
                    999:                { S("cnonce=") },
                   1000:                { S("nc=") },
                   1001:                { S("response=") },
                   1002: 
                   1003:                { NULL, 0, NULL }
                   1004:        };
                   1005: #undef S
                   1006: 
                   1007:        dkv[0].ptr = &username;
                   1008:        dkv[1].ptr = &realm;
                   1009:        dkv[2].ptr = &nonce;
                   1010:        dkv[3].ptr = &uri;
                   1011:        dkv[4].ptr = &algorithm;
                   1012:        dkv[5].ptr = &qop;
                   1013:        dkv[6].ptr = &cnonce;
                   1014:        dkv[7].ptr = &nc;
                   1015:        dkv[8].ptr = &respons;
                   1016: 
                   1017:        UNUSED(req);
                   1018: 
                   1019:        if (p->conf.auth_backend != AUTH_BACKEND_HTDIGEST &&
                   1020:            p->conf.auth_backend != AUTH_BACKEND_PLAIN) {
                   1021:                log_error_write(srv, __FILE__, __LINE__, "s",
                   1022:                                "digest: unsupported backend (only htdigest or plain)");
                   1023: 
                   1024:                return -1;
                   1025:        }
                   1026: 
                   1027:        b = buffer_init_string(realm_str);
                   1028: 
                   1029:        /* parse credentials from client */
                   1030:        for (c = b->ptr; *c; c++) {
                   1031:                /* skip whitespaces */
                   1032:                while (*c == ' ' || *c == '\t') c++;
                   1033:                if (!*c) break;
                   1034: 
                   1035:                for (i = 0; dkv[i].key; i++) {
                   1036:                        if ((0 == strncmp(c, dkv[i].key, dkv[i].key_len))) {
                   1037:                                if ((c[dkv[i].key_len] == '"') &&
                   1038:                                    (NULL != (e = strchr(c + dkv[i].key_len + 1, '"')))) {
                   1039:                                        /* value with "..." */
                   1040:                                        *(dkv[i].ptr) = c + dkv[i].key_len + 1;
                   1041:                                        c = e;
                   1042: 
                   1043:                                        *e = '\0';
                   1044:                                } else if (NULL != (e = strchr(c + dkv[i].key_len, ','))) {
                   1045:                                        /* value without "...", terminated by ',' */
                   1046:                                        *(dkv[i].ptr) = c + dkv[i].key_len;
                   1047:                                        c = e;
                   1048: 
                   1049:                                        *e = '\0';
                   1050:                                } else {
                   1051:                                        /* value without "...", terminated by EOL */
                   1052:                                        *(dkv[i].ptr) = c + dkv[i].key_len;
                   1053:                                        c += strlen(c) - 1;
                   1054:                                }
1.1.1.3 ! misho    1055:                                break;
1.1       misho    1056:                        }
                   1057:                }
                   1058:        }
                   1059: 
                   1060:        if (p->conf.auth_debug > 1) {
                   1061:                log_error_write(srv, __FILE__, __LINE__, "ss", "username", username);
                   1062:                log_error_write(srv, __FILE__, __LINE__, "ss", "realm", realm);
                   1063:                log_error_write(srv, __FILE__, __LINE__, "ss", "nonce", nonce);
                   1064:                log_error_write(srv, __FILE__, __LINE__, "ss", "uri", uri);
                   1065:                log_error_write(srv, __FILE__, __LINE__, "ss", "algorithm", algorithm);
                   1066:                log_error_write(srv, __FILE__, __LINE__, "ss", "qop", qop);
                   1067:                log_error_write(srv, __FILE__, __LINE__, "ss", "cnonce", cnonce);
                   1068:                log_error_write(srv, __FILE__, __LINE__, "ss", "nc", nc);
                   1069:                log_error_write(srv, __FILE__, __LINE__, "ss", "response", respons);
                   1070:        }
                   1071: 
                   1072:        /* check if everything is transmitted */
                   1073:        if (!username ||
                   1074:            !realm ||
                   1075:            !nonce ||
                   1076:            !uri ||
                   1077:            (qop && (!nc || !cnonce)) ||
                   1078:            !respons ) {
                   1079:                /* missing field */
                   1080: 
                   1081:                log_error_write(srv, __FILE__, __LINE__, "s",
                   1082:                                "digest: missing field");
                   1083: 
                   1084:                buffer_free(b);
                   1085:                return -1;
                   1086:        }
                   1087: 
                   1088:        /**
                   1089:         * protect the md5-sess against missing cnonce and nonce
                   1090:         */
                   1091:        if (algorithm &&
                   1092:            0 == strcasecmp(algorithm, "md5-sess") &&
                   1093:            (!nonce || !cnonce)) {
                   1094:                log_error_write(srv, __FILE__, __LINE__, "s",
                   1095:                                "digest: (md5-sess: missing field");
                   1096: 
                   1097:                buffer_free(b);
                   1098:                return -1;
                   1099:        }
                   1100: 
                   1101:        if (qop && strcasecmp(qop, "auth-int") == 0) {
                   1102:                log_error_write(srv, __FILE__, __LINE__, "s",
                   1103:                                "digest: qop=auth-int not supported");
                   1104: 
                   1105:                buffer_free(b);
                   1106:                return -1;
                   1107:        }
                   1108: 
                   1109:        m = get_http_method_name(con->request.http_method);
1.1.1.3 ! misho    1110:        force_assert(m);
1.1       misho    1111: 
                   1112:        /* password-string == HA1 */
                   1113:        password = buffer_init();
                   1114:        username_buf = buffer_init_string(username);
                   1115:        realm_buf = buffer_init_string(realm);
                   1116:        if (http_auth_get_password(srv, p, username_buf, realm_buf, password)) {
                   1117:                buffer_free(password);
                   1118:                buffer_free(b);
                   1119:                buffer_free(username_buf);
                   1120:                buffer_free(realm_buf);
                   1121:                return 0;
                   1122:        }
                   1123: 
                   1124:        buffer_free(username_buf);
                   1125:        buffer_free(realm_buf);
                   1126: 
                   1127:        if (p->conf.auth_backend == AUTH_BACKEND_PLAIN) {
                   1128:                /* generate password from plain-text */
                   1129:                li_MD5_Init(&Md5Ctx);
                   1130:                li_MD5_Update(&Md5Ctx, (unsigned char *)username, strlen(username));
1.1.1.3 ! misho    1131:                li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho    1132:                li_MD5_Update(&Md5Ctx, (unsigned char *)realm, strlen(realm));
1.1.1.3 ! misho    1133:                li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
        !          1134:                li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(password));
1.1       misho    1135:                li_MD5_Final(HA1, &Md5Ctx);
                   1136:        } else if (p->conf.auth_backend == AUTH_BACKEND_HTDIGEST) {
                   1137:                /* HA1 */
                   1138:                /* transform the 32-byte-hex-md5 to a 16-byte-md5 */
                   1139:                for (i = 0; i < HASHLEN; i++) {
                   1140:                        HA1[i] = hex2int(password->ptr[i*2]) << 4;
                   1141:                        HA1[i] |= hex2int(password->ptr[i*2+1]);
                   1142:                }
                   1143:        } else {
                   1144:                /* we already check that above */
                   1145:                SEGFAULT();
                   1146:        }
                   1147: 
                   1148:        buffer_free(password);
                   1149: 
1.1.1.3 ! misho    1150:        /* detect if attacker is attempting to reuse valid digest for one uri
        !          1151:         * on a different request uri.  Might also happen if intermediate proxy
        !          1152:         * altered client request line.  (Altered request would not result in
        !          1153:         * the same digest as that calculated by the client.) */
        !          1154:        {
        !          1155:                const size_t ulen = strlen(uri);
        !          1156:                const size_t rlen = buffer_string_length(con->request.uri);
        !          1157:                if (!buffer_is_equal_string(con->request.uri, uri, ulen)
        !          1158:                    && !(rlen < ulen && 0 == memcmp(con->request.uri->ptr, uri, rlen) && uri[rlen] == '?')) {
        !          1159:                        log_error_write(srv, __FILE__, __LINE__, "sbssss",
        !          1160:                                        "digest: auth failed: uri mismatch (", con->request.uri, "!=", uri, "), IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
        !          1161:                        buffer_free(b);
        !          1162:                        return -1;
        !          1163:                }
        !          1164:        }
        !          1165: 
1.1       misho    1166:        if (algorithm &&
                   1167:            strcasecmp(algorithm, "md5-sess") == 0) {
                   1168:                li_MD5_Init(&Md5Ctx);
                   1169:                /* Errata ID 1649: http://www.rfc-editor.org/errata_search.php?rfc=2617 */
1.1.1.3 ! misho    1170:                CvtHex(HA1, &a1);
        !          1171:                li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
        !          1172:                li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho    1173:                li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
1.1.1.3 ! misho    1174:                li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho    1175:                li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
                   1176:                li_MD5_Final(HA1, &Md5Ctx);
                   1177:        }
                   1178: 
1.1.1.3 ! misho    1179:        CvtHex(HA1, &a1);
1.1       misho    1180: 
                   1181:        /* calculate H(A2) */
                   1182:        li_MD5_Init(&Md5Ctx);
                   1183:        li_MD5_Update(&Md5Ctx, (unsigned char *)m, strlen(m));
1.1.1.3 ! misho    1184:        li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho    1185:        li_MD5_Update(&Md5Ctx, (unsigned char *)uri, strlen(uri));
                   1186:        /* qop=auth-int not supported, already checked above */
                   1187: /*
                   1188:        if (qop && strcasecmp(qop, "auth-int") == 0) {
1.1.1.3 ! misho    1189:                li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho    1190:                li_MD5_Update(&Md5Ctx, (unsigned char *) [body checksum], HASHHEXLEN);
                   1191:        }
                   1192: */
                   1193:        li_MD5_Final(HA2, &Md5Ctx);
1.1.1.3 ! misho    1194:        CvtHex(HA2, &HA2Hex);
1.1       misho    1195: 
                   1196:        /* calculate response */
                   1197:        li_MD5_Init(&Md5Ctx);
                   1198:        li_MD5_Update(&Md5Ctx, (unsigned char *)a1, HASHHEXLEN);
1.1.1.3 ! misho    1199:        li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho    1200:        li_MD5_Update(&Md5Ctx, (unsigned char *)nonce, strlen(nonce));
1.1.1.3 ! misho    1201:        li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho    1202:        if (qop && *qop) {
                   1203:                li_MD5_Update(&Md5Ctx, (unsigned char *)nc, strlen(nc));
1.1.1.3 ! misho    1204:                li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho    1205:                li_MD5_Update(&Md5Ctx, (unsigned char *)cnonce, strlen(cnonce));
1.1.1.3 ! misho    1206:                li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho    1207:                li_MD5_Update(&Md5Ctx, (unsigned char *)qop, strlen(qop));
1.1.1.3 ! misho    1208:                li_MD5_Update(&Md5Ctx, CONST_STR_LEN(":"));
1.1       misho    1209:        };
                   1210:        li_MD5_Update(&Md5Ctx, (unsigned char *)HA2Hex, HASHHEXLEN);
                   1211:        li_MD5_Final(RespHash, &Md5Ctx);
1.1.1.3 ! misho    1212:        CvtHex(RespHash, &a2);
1.1       misho    1213: 
                   1214:        if (0 != strcmp(a2, respons)) {
                   1215:                /* digest not ok */
                   1216: 
                   1217:                if (p->conf.auth_debug) {
                   1218:                        log_error_write(srv, __FILE__, __LINE__, "sss",
                   1219:                                "digest: digest mismatch", a2, respons);
                   1220:                }
                   1221: 
                   1222:                log_error_write(srv, __FILE__, __LINE__, "ssss",
                   1223:                                "digest: auth failed for ", username, ": wrong password, IP:", inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
                   1224: 
                   1225:                buffer_free(b);
                   1226:                return 0;
                   1227:        }
                   1228: 
                   1229:        /* value is our allow-rules */
                   1230:        if (http_auth_match_rules(srv, req, username, NULL, NULL)) {
                   1231:                buffer_free(b);
                   1232: 
                   1233:                log_error_write(srv, __FILE__, __LINE__, "s",
                   1234:                                "digest: rules did match");
                   1235: 
                   1236:                return 0;
                   1237:        }
                   1238: 
1.1.1.3 ! misho    1239:        /* check age of nonce.  Note that rand() is used in nonce generation
        !          1240:         * in http_auth_digest_generate_nonce().  If that were replaced
        !          1241:         * with nanosecond time, then nonce secret would remain unique enough
        !          1242:         * for the purposes of Digest auth, and would be reproducible (and
        !          1243:         * verifiable) if nanoseconds were inclued with seconds as part of the
        !          1244:         * nonce "timestamp:secret".  Since that is not done, timestamp in
        !          1245:         * nonce could theoretically be modified and still produce same md5sum,
        !          1246:         * but that is highly unlikely within a 10 min (moving) window of valid
        !          1247:         * time relative to current time (now) */
        !          1248:        {
        !          1249:                time_t ts = 0;
        !          1250:                const unsigned char * const nonce_uns = (unsigned char *)nonce;
        !          1251:                for (i = 0; i < 8 && light_isxdigit(nonce_uns[i]); ++i) {
        !          1252:                        ts = (ts << 4) + hex2int(nonce_uns[i]);
        !          1253:                }
        !          1254:                if (i != 8 || nonce[8] != ':'
        !          1255:                    || ts > srv->cur_ts || srv->cur_ts - ts > 600) { /*(10 mins)*/
        !          1256:                        buffer_free(b);
        !          1257:                        return -2; /* nonce is stale; have client regenerate digest */
        !          1258:                } /*(future: might send nextnonce when expiration is imminent)*/
        !          1259:        }
        !          1260: 
1.1       misho    1261:        /* remember the username */
                   1262:        buffer_copy_string(p->auth_user, username);
                   1263: 
                   1264:        buffer_free(b);
                   1265: 
                   1266:        if (p->conf.auth_debug) {
                   1267:                log_error_write(srv, __FILE__, __LINE__, "s",
                   1268:                                "digest: auth ok");
                   1269:        }
                   1270:        return 1;
                   1271: }
                   1272: 
                   1273: 
1.1.1.3 ! misho    1274: int http_auth_digest_generate_nonce(server *srv, mod_auth_plugin_data *p, buffer *fn, char (*out)[33]) {
1.1       misho    1275:        HASH h;
                   1276:        li_MD5_CTX Md5Ctx;
1.1.1.3 ! misho    1277:        char hh[LI_ITOSTRING_LENGTH];
1.1       misho    1278: 
                   1279:        UNUSED(p);
                   1280: 
                   1281:        /* generate shared-secret */
                   1282:        li_MD5_Init(&Md5Ctx);
1.1.1.3 ! misho    1283:        li_MD5_Update(&Md5Ctx, CONST_BUF_LEN(fn));
        !          1284:        li_MD5_Update(&Md5Ctx, CONST_STR_LEN("+"));
1.1       misho    1285: 
                   1286:        /* we assume sizeof(time_t) == 4 here, but if not it ain't a problem at all */
1.1.1.3 ! misho    1287:        li_itostrn(hh, sizeof(hh), srv->cur_ts);
1.1       misho    1288:        li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
                   1289:        li_MD5_Update(&Md5Ctx, (unsigned char *)srv->entropy, sizeof(srv->entropy));
1.1.1.3 ! misho    1290:        li_itostrn(hh, sizeof(hh), rand());
1.1       misho    1291:        li_MD5_Update(&Md5Ctx, (unsigned char *)hh, strlen(hh));
                   1292: 
                   1293:        li_MD5_Final(h, &Md5Ctx);
                   1294: 
                   1295:        CvtHex(h, out);
                   1296: 
                   1297:        return 0;
                   1298: }

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>