/*
 * http_access: Security options etc.
 *
 * All code contained herein is covered by the Copyright as distributed
 * in the README file in the main directory of the distribution of 
 * NCSA HTTPD.
 * 
 * Based on NCSA HTTPd 1.3 by Rob McCool
 *
 *  03-12-95 blong
 *     Added patch to fix ALLOW_THEN_DENY
 * 
 */


#include "httpd.h"
#include "new.h"

int in_domain(char *domain, char *what) {
    int dl=strlen(domain);
    int wl=strlen(what);

    if((wl-dl) >= 0)
        return(!strcmp(domain,&what[wl-dl]));
    else
        return 0;
}

int in_ip(char *domain, char *what) {
    return(!strncmp(domain,what,strlen(domain)));
}

int find_allow(int x, int m) {
    register int y;

    if(sec[x].num_allow[m] < 0)
        return 1;

    for(y=0;y<sec[x].num_allow[m];y++) {
        if(!strcmp("all",sec[x].allow[m][y])) 
            return 1;
        if(remote_host && isalpha(remote_host[0]))
            if(in_domain(sec[x].allow[m][y],remote_host))
                return 1;
        if(in_ip(sec[x].allow[m][y],remote_ip))
            return 1;
    }
    return 0;
}

int find_deny(int x, int m) {
    register int y;

    if(sec[x].num_deny[m] < 0)
        return 1;

    for(y=0;y<sec[x].num_deny[m];y++) {
        if(!strcmp("all",sec[x].deny[m][y])) 
            return 1;
        if(remote_host && isalpha(remote_host[0]))
            if(in_domain(sec[x].deny[m][y],remote_host))
                return 1;
        if(in_ip(sec[x].deny[m][y],remote_ip))
            return 1;
    }
    return 0;
}

void check_dir_access(int x, int m, int *w, int *n) {
    if(sec[x].auth_type)
        auth_type = sec[x].auth_type;
    if(sec[x].auth_name)
        auth_name = sec[x].auth_name;
    if(sec[x].auth_pwfile)
        auth_pwfile = sec[x].auth_pwfile;
    if(sec[x].auth_grpfile)
        auth_grpfile = sec[x].auth_grpfile;

    if(sec[x].order[m] == ALLOW_THEN_DENY) {
	*w=0;
        if(find_allow(x,m))
            *w=1;
        if(find_deny(x,m))
            *w=0;
    } else if(sec[x].order[m] == DENY_THEN_ALLOW) {
        if(find_deny(x,m))
            *w=0;
        if(find_allow(x,m))
            *w=1;
    }
    else
        *w = find_allow(x,m) && (!find_deny(x,m));

    if(sec[x].num_auth[m])
        *n=x;
}

void evaluate_access(char *p, struct stat *finfo, int m, int *allow, 
                     char *allow_options,FILE *out)
{
    int will_allow, need_auth, num_dirs;
    char opts[MAX_STRING_LEN], override[MAX_STRING_LEN];
    char path[MAX_STRING_LEN], d[MAX_STRING_LEN];
    char errstr[MAX_STRING_LEN];
    register int x,y,z,n;

    if(S_ISDIR(finfo->st_mode)) strcpy_dir(path,p);
    else lim_strcpy(path,p, MAX_STRING_LEN);

    no2slash(path);

    num_dirs = count_dirs(path);
    will_allow = 1; 
    need_auth = -1;
    auth_type = NULL; 
    auth_name = NULL;
    auth_pwfile = NULL;
    auth_grpfile = NULL;
    user[0] = '\0';
    groupname[0] = '\0';
    reset_mime_vars();

    for(x=0;x<num_dirs;x++) {
        opts[x] = OPT_ALL;
        override[x] = OR_ALL;
    }

    n=num_dirs-1;
    for(x=0;x<num_sec;x++) {
        if(is_matchexp(sec[x].d)) {
            if(!strcmp_match(path,sec[x].d)) {
                for(y=0;y<num_dirs;y++) {
                    if(!(sec[x].opts & OPT_UNSET))
                        opts[y] = sec[x].opts;
                    override[y] = sec[x].override;
                }
            }
            check_dir_access(x,m,&will_allow,&need_auth);
        }
        else if(!strncmp(path,sec[x].d,strlen(sec[x].d))) {
            for(y=count_dirs(sec[x].d) - 1;y<num_dirs;y++) {
                if(!(sec[x].opts & OPT_UNSET))
                    opts[y] = sec[x].opts;
                override[y] = sec[x].override;
            }
            check_dir_access(x,m,&will_allow,&need_auth);
        }
    }
    if((override[n]) || (!(opts[n] & OPT_SYM_LINKS)) || 
       (opts[n] & OPT_SYM_OWNER))
        {
            for(x=0;x<num_dirs;x++) {
                y = num_sec;
                make_dirstr(path,x+1,d);
                if((!(opts[x] & OPT_SYM_LINKS)) || (opts[x] & OPT_SYM_OWNER)) {
                    struct stat lfi,fi;

                    lstat(d,&lfi);
                    if(!(S_ISDIR(lfi.st_mode))) {
                        if(opts[x] & OPT_SYM_OWNER) {
                            char realpath[512];
                            int bsz;

                            if((bsz = readlink(d,realpath,256)) == -1)
                                goto bong;
                            realpath[bsz] = '\0';
                            if(realpath[0] != '/') {
                                char t[260];
                                strcpy(t,"../");
                                strcpy(&t[3],realpath);
                                make_full_path(d,t,realpath);
                                getparents(realpath);
                            }
                            lstat(realpath,&fi);
                            if(fi.st_uid != lfi.st_uid)
                                goto bong;
                        }
                        else {
                          bong:
                            sprintf(errstr,"httpd: will not follow link %s",d);
                            log_error(errstr);
                            *allow=0;
                            *allow_options = OPT_NONE;
                            return;
                        }
                    }
                }
                if(override[x]) {
                    parse_htaccess(d,override[x],out);
                    if(num_sec != y) {
                        for(z=count_dirs(sec[y].d) - 1;z<num_dirs;z++) {
                            if(!(sec[y].opts & OPT_UNSET))
                                opts[z] = sec[y].opts;
                            override[z] = sec[y].override;
                        }
                        check_dir_access(y,m,&will_allow,&need_auth);
                    }
                }
            }
        }
    if((!(S_ISDIR(finfo->st_mode))) && 
       ((!(opts[n] & OPT_SYM_LINKS)) || (opts[n] & OPT_SYM_OWNER))) {
        struct stat fi,lfi;
        lstat(path,&fi);
        if(!(S_ISREG(fi.st_mode))) {
            if(opts[n] & OPT_SYM_OWNER) {
                char realpath[512];
                int bsz;
                
                if((bsz = readlink(path,realpath,256)) == -1)
                    goto gong;
                realpath[bsz] = '\0';
                if(realpath[0] != '/') {
                    char t[260];
                    strcpy(t,"../");
                    strcpy(&t[3],realpath);
                    make_full_path(path,t,realpath);
                    getparents(realpath);
                }
                lstat(realpath,&lfi);
                if(fi.st_uid != lfi.st_uid)
                    goto gong;
            }
            else {
              gong:
                sprintf(errstr,"httpd: will not follow link %s",path);
                log_error(errstr);
                *allow=0;
                *allow_options = OPT_NONE;
                return;
            }
        }
    }
    *allow = will_allow;
    if(will_allow) {
        *allow_options = opts[num_dirs-1];
        if(need_auth >= 0)
            check_auth(&sec[need_auth],m,out);
    }
    else *allow_options = 0;
}

void kill_security() {
    register int x,y,m;

    for(x=0;x<num_sec;x++) {
        free(sec[x].d);
        for(m=0;m<METHODS;m++) {
            for(y=0;y<sec[x].num_allow[m];y++)
                free(sec[x].allow[m][y]);
            for(y=0;y<sec[x].num_deny[m];y++)
                free(sec[x].deny[m][y]);
            for(y=0;y<sec[x].num_auth[m];y++)
                free(sec[x].auth[m][y]);
        }
        if(sec[x].auth_type)
            free(sec[x].auth_type);
        if(sec[x].auth_name)
            free(sec[x].auth_name);
        if(sec[x].auth_pwfile)
            free(sec[x].auth_pwfile);
        if(sec[x].auth_grpfile)
            free(sec[x].auth_grpfile);
    }
}

/* This function should reset the security data structure to contain only
   the information given in the access configuration file.  It should be 
   called after any transactions */

void reset_security() {
    register int x,y,m;

    for(x=num_sec_config;x<num_sec;x++) {
        free(sec[x].d);
        for(m=0;m<METHODS;m++) {
            for(y=0;y<sec[x].num_allow[m];y++)
                free(sec[x].allow[m][y]);
            for(y=0;y<sec[x].num_deny[m];y++)
                free(sec[x].deny[m][y]);
            for(y=0;y<sec[x].num_auth[m];y++)
                free(sec[x].auth[m][y]);
        }
        if(sec[x].auth_type)
            free(sec[x].auth_type);
        if(sec[x].auth_name)
            free(sec[x].auth_name);
        if(sec[x].auth_pwfile)
            free(sec[x].auth_pwfile);
        if(sec[x].auth_grpfile)
            free(sec[x].auth_grpfile);
    }

   num_sec = num_sec_config;
}

