/* Package Name : twhttpd * File Name : read_cfg.c * Author : Sam NG * * This package is an secure HTTP application proxy writen by Sam Ng. * Copyright (C) 2001 SAM NG * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, * MA 02111-1307, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "y.tab.h" #include "twhttpd.h" #include "cfg_functions.h" #include "base64.h" void init_gen_cfg(gen_cfg *gc) { int i; gc->enable_chroot = 1; gc->uid = (uid_t)0; gc->gid = (gid_t)0; gc->work_dir = CURRENT_DIR; gc->cache_dir = DEFAULT_CACHE_DIR; gc->cookie_dir = DEFAULT_COOKIE_DIR; gc->cache_tout = DEFAULT_CACHE_TIMEOUT; gc->cache_fast_tout = DEFAULT_CACHE_FAST_TIMEOUT; gc->mime_type = NULL; for ( i = 0; i < MAX_SC; i++ ) { gc->sclist[i] = NULL; } } void init_srv_cfg(srv_cfg *sc) { sc->fd = -1; memset( &(sc->ip), 0, sizeof(struct sockaddr_in) ); memset( &(sc->forward), 0, sizeof(struct sockaddr_in) ); sc->forward_proxy = 0; sc->local = 0; sc->alog = NULL; sc->elog = NULL; sc->enable_cache = 1; sc->enable_cookie_chk = 1; sc->safe_url = 1; sc->enable_ftp = 0; sc->enable_https = 0; sc->srv_hd_chk = 0; sc->clt_hd_chk = 1; sc->srv_ver = NULL; sc->browser_ver = NULL; sc->dt = NULL; } void init_dt(struct decision_tree_st *dt) { dt->inverted = 0; dt->f = NULL; dt->value = NULL; dt->value2 = NULL; dt->property = -1; dt->and = NULL; dt->or = NULL; dt->return_code = 0; } // OR append dt2 to dt1 (i.e. dt1 or dt2) // return newly formed dt if OK // return NULL if error decision_tree *or_dt(decision_tree *dt1, decision_tree *dt2) { decision_tree *ptr; if ( NULL == dt1 || NULL == dt2 ) return NULL; ptr = dt1; // point ptr to last dt while ( NULL != ptr->or ) { ptr = ptr->or; } // now point the last dt.or to dt2 ptr->or = dt2; return dt1; } // form new decision tree dt1 and dt2 // if dt1 or dt2 is all the way AND (ie. w/o OR branch) then simple AND append dt2 to dt1 // else create new dt3 -> dt1 and then AND append dt2 to dt3 // return newly formed dt if OK // return NULL if error decision_tree *and_dt(decision_tree *dt1, decision_tree *dt2) { decision_tree *ptr1, *ptr2, *result; if ( NULL == dt1 || NULL == dt2 ) return NULL; if ( and_only_dt(dt1) ) { ptr1 = dt1; ptr2 = dt2; } else if ( and_only_dt(dt2) ) { ptr1 = dt2; ptr2 = dt1; } else { ptr1 = (decision_tree *)malloc(sizeof(decision_tree)); if ( NULL == ptr1 ) return NULL; init_dt(ptr1); ptr1->f = &eval_dt; ptr1->value = dt1; ptr2 = dt2; } result = ptr1; // now point ptr to last dt while( NULL != ptr1->and ) { ptr1 = ptr1->and; } ptr1->and = ptr2; return result; } // same as and_dt except always out dt1 and dt2 // i.e., the sequence is retained decision_tree *seq_and_dt(decision_tree *dt1, decision_tree *dt2) { decision_tree *ptr1, *ptr2, *result; if ( NULL == dt1 || NULL == dt2 ) return NULL; if ( and_only_dt(dt1) ) { ptr1 = dt1; ptr2 = dt2; } else { ptr1 = (decision_tree *)malloc(sizeof(decision_tree)); if ( NULL == ptr1 ) return NULL; init_dt(ptr1); ptr1->f = &eval_dt; ptr1->value = dt1; ptr2 = dt2; } result = ptr1; // now point ptr to last dt while( NULL != ptr1->and ) { ptr1 = ptr1->and; } ptr1->and = ptr2; return result; } // check if input dt only has AND decision // return 1 if TRUE // return 0 if false int and_only_dt(decision_tree *dt) { while( NULL != dt ) { if ( NULL != dt->or ) return 0; dt = dt->and; } return 1; } // print out the whole decision tree void print_dt(decision_tree *dt) { while ( 1 ) { if ( dt->f == &eval_dt ) { if ( dt->inverted ) printf("!"); printf("( "); print_dt(dt->value); printf(") "); } else { if ( dt->inverted ) printf("!"); if ( dt->f == &cfg_str_fnmatch ) { printf("(%s ? %s)", "str_fnmatch", dt->value); } else if ( dt->f == &cfg_ip_range ) { printf("(%s ? %ul-%ul)", "ip_range", ((ip_range_t *)dt->value)->start_addr, ((ip_range_t *)dt->value)->end_addr); } else if ( dt->f == &cfg_is_cgi ) { printf("(%s ? %s)", "is_cgi", dt->value); } else if ( dt->f == &cfg_return ) { printf("return %d", *((int *)dt->value)); } else { printf("(%s ? %s)", "unknown function", dt->value); } } if ( NULL != dt->and ) { printf(" AND ( "); print_dt(dt->and); printf(" )"); } if ( NULL != dt->or ) { printf(" OR\n"); dt = dt->or; } else { return; } } } // evaluate the result of a decision tree int eval_dt(http_header *hd, decision_tree *init_dt) { int result; decision_tree *dt; /* return FALSE if dt is NULL */ if ( NULL == init_dt ) return 0; else dt = init_dt; while (1) { /* sub-branch?? */ if ( &eval_dt == dt->f ) { result = dt->inverted ^ eval_dt(hd, (decision_tree *)dt->value); } /* normal dt block */ else { result = dt->inverted ^ dt->f(hd, dt->value, dt->value2, dt->property); } /* goto the OR path */ if ( 0 == result ) { if ( NULL != dt->or ) { /* point dt to next OR block */ dt = dt->or; } else { /* no more OR branch, return FALSE */ return 0; } } /* goto the AND path */ else if ( 1 == result ) { if ( NULL != dt->and ) { /* re-currsive call next AND block */ if ( eval_dt(hd, dt->and) ) { return 1; } else if ( NULL != dt->or ) dt = dt->or; else return 0; } else { /* we already reach the ground, retrun TRUE */ return 1; } } /* impossible */ else { return 0; } } } // test if the value match some header field // the field is selected by property // value2 is not used int cfg_str_fnmatch(http_header *hd, void *value, void *value2, int property) { char *hd_ptr; /* point to the header field to be compare */ int cs; /* is the compare case sensitive? */ char data[MAX_LINE]; /* for case insensitive fnmatch */ char pattern[MAX_LINE]; int i; switch (property) { case METHOD: hd_ptr = hd->method; cs = 1; break; case PATH: hd_ptr = hd->path; cs = 0; break; case HOST: hd_ptr = hd->host; cs = 0; break; case EXT: hd_ptr = hd->ext; cs = 0; break; case USER_AGENT: hd_ptr = hd->user_agent; cs = 0; break; case REFERER: hd_ptr = hd->referer; cs = 0; break; case QUERY: hd_ptr = hd->query; cs = 0; break; case COOKIE: hd_ptr = hd->cookie; cs = 0; break; /* don't know how to handle, return error */ default: return -1; } /* check if the string is null, if yes, return NOT MATCH anyway */ if ( NULL == hd_ptr ) return 0; if ( cs ) { /* case sensitive */ return !fnmatch((char *)value, hd_ptr, 0); } else { /* case insensitive */ #ifdef FNM_CASEFOLD return !fnmatch((char *)value, hd_ptr, FNM_CASEFOLD); #else /* system don't have GNU fnmatch() extension, have to do our own */ if ( sizeof(data) < strlen(hd_ptr) ) return -1; // almost impossible strcpy(data, hd_ptr); if ( sizeof(pattern) < strlen((char *)value) ) return -1; // almost impossible strcpy(pattern, (char *)value); /* change the string to small letter */ for ( i=0; imethod; break; case PATH: hd_ptr = hd->path; break; case HOST: hd_ptr = hd->host; break; case EXT: hd_ptr = hd->ext; break; case USER_AGENT: hd_ptr = hd->user_agent; break; case REFERER: hd_ptr = hd->referer; break; case QUERY: hd_ptr = hd->query; break; case COOKIE: hd_ptr = hd->cookie; break; /* don't know how to handle, return error */ default: return -1; } /* check if the string is null, if yes, return NOT MATCH anyway */ if ( NULL == hd_ptr ) return 0; mismatch = regexec((regex_t *)value, hd_ptr, 0, NULL, 0); if ( !mismatch ) { return 1; } else if ( REG_NOMATCH == mismatch ) { return 0; } else /* regex internal error */ { syslog(LOG_ERR, "regexec() internal error\n"); return -1; } } // numeric test // left hand side value is indirectly determined from *value // right hand side value is *value2 // operator is property int cfg_numeric_test(http_header *hd, void *value, void *value2, int property) { char *hd_ptr=NULL; /* point to the header field to be compare */ int v1=-1; switch ( ((cfg_variable *)value)->type ) { /* int type */ case PORT: v1 = hd->port; break; case POST_LEN: v1 = hd->post_len; break; /* char* type */ case METHOD: hd_ptr = hd->method; break; case PATH: hd_ptr = hd->path; break; case HOST: hd_ptr = hd->host; break; case EXT: hd_ptr = hd->ext; break; case USER_AGENT: hd_ptr = hd->user_agent; break; case REFERER: hd_ptr = hd->referer; break; case QUERY: hd_ptr = hd->query; break; case COOKIE: hd_ptr = hd->cookie; break; /* don't know how to handle, return error */ default: return -1; } if ( NULL != ((cfg_variable *)value)->f ) { if ( NULL == hd_ptr ) v1 = 0; else v1 = ((cfg_variable *)value)->f(hd_ptr); } switch (property) { case LT: return (v1 < *(int *)value2) ? 1:0; case LE: return (v1 <= *(int *)value2) ? 1:0; case GT: return (v1 > *(int *)value2) ? 1:0; case GE: return (v1 >= *(int *)value2) ? 1:0; case EQ: return (v1 == *(int *)value2) ? 1:0; case NE: return (v1 != *(int *)value2) ? 1:0; /* don't know how to handle, return error */ default: return -1; } } // test if the $local or $remote is in // IP range denoted by value and value2 // the range is inclusive, ie. including two end points // property indicate the test is on $local or $remote // comparison base on host number (not network number) int cfg_ip_range(http_header *hd, void *value, void *value2, int property) { unsigned long l; if ( LOCAL_IP == property )l = ntohl(hd->srv_addr.sin_addr.s_addr); else if ( CLIENT_IP == property )l = ntohl(hd->clt_addr.sin_addr.s_addr); else return 0; // printf("Input IP: %ul\n", l); // printf("Start IP: %ul\n", ((ip_range_t *)value)->start_addr); // printf("End IP : %ul\n", ((ip_range_t *)value)->start_addr); if ( l >= ((ip_range_t *)value)->start_addr && l <= ((ip_range_t *)value)->end_addr ) { return 1; } else { return 0; } } // test if ($method is "POST") OR ($query_len > 0) int cfg_is_cgi(http_header *hd, void *value, void *value2, int property) { if ( ( NULL != hd->query ) || !strcasecmp(hd->method, "POST") ) return 1; else return 0; } // test www-authentication // value is char point to the passwor file name int cfg_htpasswd(http_header *hd, void *value, void *value2, int property) { FILE *fp; char *line; char *fname; char decode[MAX_AUTH]; char format_string[64]; char *login, *passwd; char field1[MAX_AUTH], field2[MAX_AUTH]; char *salt, *des_passwd; if ( AUTH == property ) line = hd->auth; else if ( PROXY_AUTH == property ) line = hd->proxy_auth; else return -1; if ( NULL == line ) return 0; if ( strlen(line) >= MAX_AUTH ) return 0; if ( sscanf(line, "%s %s", field1, field2) != 2 ) return 0; if ( strcasecmp(field1, "basic") ) return 0; if ( b64_decode(field2, decode, MAX_AUTH) < 0 ) { syslog(LOG_ERR, "b64_decode() error\n"); return -1; } /* extract name and passwd */ /* input should be name=passwd */ login = decode; if ( NULL == (passwd=(char *)strchr(decode, ':')) ) return 0; *passwd = '\0'; passwd++; /* OK read from file to check for passwd */ fname = (char *)value; if ( NULL == (fp=fopen(fname, "r")) ) { syslog(LOG_ERR, "fopen(%s): %s\n", fname, strerror(errno)); return -1; } /* setup format string, used in fscanf() */ if ( snprintf(format_string, sizeof(format_string)-1, "%%%d[^:]:%%%ds\n", MAX_AUTH, MAX_AUTH) < 0 ) { return -1; } format_string[sizeof(format_string)-1] = '\0'; while ( 2 == fscanf(fp, format_string, field1, field2) ) { if ( !strcmp(login, field1) ) { salt = field2; des_passwd = (char *)crypt(passwd, salt); if ( !strcmp(des_passwd, field2 ) ) { return 1; } else { return 0; } } } return 0; } // change forward destination int cfg_set_forward(http_header *hd, void *value, void *value2, int property) { hd->forward = (struct sockaddr_in *)value; return 1; } // change web or proxy mode int cfg_set_forward_proxy(http_header *hd, void *value, void *value2, int property) { hd->forward_proxy = (int *)value; return 1; } // setup hd->location, only useful if return 301 or 302 int cfg_set_location(http_header *hd, void *value, void *value2, int property) { if ( strlen((char *)value) >= sizeof(hd->location) ) { syslog(LOG_ERR, "$location too long %s", (char *)value); return -1; } else { strcpy(hd->location, (char *)value); return 1; } } // return statment // usually the end of a config script int cfg_return(http_header *hd, void *value, void *value2, int property) { hd->return_code = *((int *)value); return 1; } // little helper functions int cfg_strlen(char *s) { int i; if ( NULL != s ) i = strlen(s); else i = 0; return i; } void *cf_malloc(size_t s) { void *p; p = (void *)malloc(s); if ( NULL == p ) { fprintf(stderr, "malloc(): %s\n", strerror(errno)); exit(-1); } return p; }