%{ /* Package Name : twhttpd * File Name : parser.y * 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 "config.h" #include "cfg_functions.h" struct in_addr yyaddr; int yyint; int *yyintp; struct passwd *yypasswd; FILE *yyf; char yystring[MAX_LINE]; regex_t *yyregex; extern int yylineno; extern char* yytext; extern int return_code; %} %token DEBUG CHROOT UID GID WORK_DIR CACHE_DIR COOKIE_DIR CACHE_TIMEOUT CACHE_FAST_TIMEOUT MIME %token SERVER SERVER_VERSION BROWSER_VERSION LISTEN CACHE COOKIE_CHECK SAFE_URL HTTPS ACCESS_LOG HEADER_CHECK %token FORWARD FORWARD_PROXY LOCATION %token IF ELSIF ELSE %token IP_ADDR STRING REGEX_STR %token INCLUDE HTPASSWD STRLEN CGI_DECODE IS_CGI IS_VALID_COOKIE CLEAR_COOKIE RETURN %token EQ NE GE GT LE LT IS AND OR %token NUMBER %token LOCAL_IP CLIENT_IP %token METHOD HOST PATH EXT USER_AGENT REFERER QUERY COOKIE %token PORT POST_LEN %token AUTH PROXY_AUTH %type eq_test str_test num_test logic %type
if_then_else if_stat elsif_stat_list elsif_stat else_stat %type
stat_list statment %type
condition_list condition %type
expression_list expression return_statment %type ip_range %type ip_var str_var auth_var %type num_var %type config_file general_stat general_list %type server_cfg server_opt server_opt_list %union { int INT; char *STR; struct decision_tree_st *DT; struct ip_range_struct *IP; struct srv_cfg_struct *SC; struct cfg_variable_struct *CV; } %start config_file %% config_file: general_list | config_file server_cfg { for( yyint = 0; yyint < MAX_SC ; yyint++ ) { if ( NULL == yycfg.sclist[yyint] ) break; } if ( MAX_SC == yyint ) { fprintf(stderr, "Config Error: Too Many Server Config Sections\n"); exit(-1); } else { yycfg.sclist[yyint] = $2; } } ; general_list: general_stat | general_list general_stat ; general_stat: DEBUG '=' STRING ';' { $$ = NULL; } | CHROOT '=' STRING ';' { if ( !strcasecmp($3, "enable") ) yyint = 1; else if ( !strcasecmp($3, "disable") ) yyint = 0; else { fprintf(stderr, "Config Error: Invalid $chroot\n"); exit(-1); } yycfg.enable_chroot = yyint; } | UID '=' STRING ';' { yypasswd = getpwnam($3); if ( NULL == yypasswd ) { fprintf(stderr, "Config Error: Invalid $uid %s\n", $3); exit(-1); } yycfg.uid = yypasswd->pw_uid; } | GID '=' STRING ';' { yypasswd = getpwnam($3); if ( NULL == yypasswd ) { fprintf(stderr, "Config Error: Invalid $uid %s\n", $3); exit(-1); } yycfg.gid = yypasswd->pw_gid; } | WORK_DIR '=' STRING ';' { yycfg.work_dir = $3; } | CACHE_DIR '=' STRING ';' { yycfg.cache_dir = $3; } | COOKIE_DIR '=' STRING ';' { yycfg.cookie_dir = $3; } | CACHE_TIMEOUT '=' NUMBER ';' { yycfg.cache_tout = $3; } | CACHE_FAST_TIMEOUT '=' NUMBER ';' { yycfg.cache_fast_tout = $3; } | MIME '=' STRING ';' { yycfg.mime_type = $3; } ; server_cfg: SERVER '(' server_opt_list ')' '{' stat_list '}' { $$ = (srv_cfg *)cf_malloc(sizeof(srv_cfg)); memcpy($$, &yysco, sizeof(srv_cfg)); $$->dt = $6; /* clear out yysco after use */ init_srv_cfg(&yysco); } ; server_opt_list: server_opt | server_opt_list ',' server_opt ; server_opt: LISTEN '=' IP_ADDR ':' NUMBER { if ( 0 == $5 ) { fprintf(stderr, "Config Error: Invalid $listen port\n"); exit(-1); } (yysco.ip).sin_family = AF_INET; if ( 1 != str2addr($3, &((yysco.ip).sin_addr)) ) { fprintf(stderr, "Config Error: Invalid Server IP Address\n"); exit(-1); } (yysco.ip).sin_port = htons($5); } | FORWARD '=' IP_ADDR ':' NUMBER { if ( 0 == $5 ) { fprintf(stderr, "Config Error: Invalid forward port\n"); exit(-1); } (yysco.forward).sin_family = AF_INET; if ( 1 != str2addr($3, &((yysco.forward).sin_addr)) ) { fprintf(stderr, "Config Error: Invalid Server IP Address\n"); exit(-1); } (yysco.forward).sin_port = htons($5); } | FORWARD '=' IP_ADDR { (yysco.forward).sin_family = AF_INET; if ( 1 != str2addr($3, &((yysco.forward).sin_addr)) ) { fprintf(stderr, "Config Error: Invalid Server IP Address\n"); exit(-1); } (yysco.forward).sin_port = htons(80); } | FORWARD_PROXY '=' STRING { if ( !strcasecmp($3, "enable") ) yyint = 1; else if ( !strcasecmp($3, "disable") ) yyint = 0; else { fprintf(stderr, "Config Error: Invalid $forward_proxy option\n"); exit(-1); } yysco.forward_proxy = yyint; } | CACHE '=' STRING { if ( !strcasecmp($3, "enable") ) yyint = 1; else if ( !strcasecmp($3, "disable") ) yyint = 0; else { fprintf(stderr, "Config Error: Invalid $cache option\n"); exit(-1); } yysco.enable_cache = yyint; } | COOKIE_CHECK '=' STRING { if ( !strcasecmp($3, "enable") ) yyint = 1; else if ( !strcasecmp($3, "disable") ) yyint = 0; else { fprintf(stderr, "Config Error: Invalid $cookie_check option\n"); exit(-1); } yysco.enable_cookie_chk = yyint; } | SAFE_URL '=' STRING { if ( !strcasecmp($3, "enable") ) yyint = 1; else if ( !strcasecmp($3, "disable") ) yyint = 0; else { fprintf(stderr, "Config Error: Invalid $safe_url option\n"); exit(-1); } yysco.safe_url = yyint; } | HTTPS '=' STRING { if ( !strcasecmp($3, "enable") ) yyint = 1; else if ( !strcasecmp($3, "disable") ) yyint = 0; else { fprintf(stderr, "Config Error: Invalid $https option\n"); exit(-1); } yysco.enable_https = yyint; } | ACCESS_LOG '=' STRING { if ( strlen(yycfg.work_dir) + strlen($3) >= MAX_LINE - 2 ) { fprintf(stderr, "Config Error: $work_dir+$access_log file name too long\n"); exit(-1); } strcpy(yystring, yycfg.work_dir); /* well, double "/" does not hurt */ strcat(yystring, "/"); strcat(yystring, $3); yyf = fopen(yystring, "a"); if ( NULL == yyf ) { fprintf(stderr, "Config Error: Error open access_log, %s\n", strerror(errno)); exit(-1); } yysco.alog = yyf; } | HEADER_CHECK '=' STRING { if ( !strcasecmp($3, "request") ) { yysco.srv_hd_chk = 0; yysco.clt_hd_chk = 1; } else if ( !strcasecmp($3, "response") ) { yysco.srv_hd_chk = 1; yysco.clt_hd_chk = 0; } else if ( !strcasecmp($3, "both") ) { yysco.srv_hd_chk = 1; yysco.clt_hd_chk = 1; } else if ( !strcasecmp($3, "disable") ) { yysco.srv_hd_chk = 0; yysco.clt_hd_chk = 0; } else { fprintf(stderr, "Config Error: Invalid $header_check option\n"); exit(-1); } } | SERVER_VERSION '=' STRING { yysco.srv_ver = $3; } | BROWSER_VERSION '=' STRING { yysco.browser_ver = $3; } ; stat_list: statment { $$ = $1; } | stat_list statment { $$ = or_dt($1, $2); } ; statment: if_then_else { $$ = $1; } | expression_list { $$ = $1; } ; if_then_else: if_stat { $$ = $1; } | if_stat else_stat { $$ = or_dt($1, $2); } | if_stat elsif_stat_list else_stat { $$ = or_dt(or_dt($1, $2), $3); } ; if_stat: IF '(' condition_list ')' '{' stat_list '}' { $$ = seq_and_dt($3, $6); } ; elsif_stat_list: elsif_stat { $$ = $1; } | elsif_stat_list elsif_stat { $$ = or_dt($1, $2); } ; elsif_stat: ELSIF '(' condition_list ')' '{' stat_list '}' { $$ = seq_and_dt($3, $6); } ; else_stat: ELSE '{' stat_list '}' { $$ = $3; } ; condition_list: condition { $$ = $1; } | condition_list logic condition_list { if ( AND == $2 ) $$ = and_dt($1, $3); else if ( OR == $2 ) $$ = or_dt($1, $3); else $$ = NULL; } | '!' condition_list { $$ = $2; $$->inverted = 1; } | '(' condition_list ')' { $$ = $2; } ; condition: ip_var eq_test ip_range { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_ip_range; $$->value = $3; $$->property = $1; if ( NE == $2 ) $$->inverted = 1; } | str_var str_test STRING { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_str_fnmatch; $$->property = $1; if ( NE == $2 ) $$->inverted = 1; $$->value = $3; } | str_var IS REGEX_STR { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_str_regex; $$->property = $1; $$->inverted = 0; /* if the first char is "s", that is a case sensitive search */ if ( 's' == *($3) ) { yyint = REG_EXTENDED|REG_NOSUB; } else { yyint = REG_EXTENDED|REG_NOSUB|REG_ICASE; } yyregex = (regex_t *)cf_malloc(sizeof(regex_t)); if ( regcomp(yyregex, $3+1, yyint) ) { fprintf(stderr, "regcomp(): %s\n", strerror(errno)); exit(-1); } $$->value = yyregex; } | num_var num_test NUMBER { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_numeric_test; $$->value = $1; $$->value2 = cf_malloc(sizeof(int)); *(int *)$$->value2 = $3; $$->property = $2; } | HTPASSWD '(' auth_var ',' STRING ')' { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_htpasswd; $$->value = $5; $$->property = $3; } | CGI_DECODE '(' STRING ',' STRING ')' { $$ = NULL; } | IS_CGI '(' ')' { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_is_cgi; $$->value = "(null)"; } | IS_VALID_COOKIE '(' ')' { $$ = NULL; } ; expression_list: return_statment { $$ = $1; } | expression expression_list { $$ = seq_and_dt($1, $2); } ; expression: FORWARD '=' IP_ADDR ':' NUMBER ';' { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_set_forward; $$->value = (struct sockaddr_in *)cf_malloc(sizeof(struct sockaddr_in)); /* malloc OK, now check for validity */ if ( 0 == $5 ) { fprintf(stderr, "Config Error: Invalid forward port\n"); exit(-1); } if ( 1 != str2addr($3, &(((struct sockaddr_in *)$$->value)->sin_addr)) ) { fprintf(stderr, "Config Error: Invalid Server IP Address\n"); exit(-1); } ((struct sockaddr_in *)$$->value)->sin_family = AF_INET; ((struct sockaddr_in *)$$->value)->sin_port = htons($5); } | FORWARD '=' IP_ADDR ';' { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_set_forward; $$->value = (struct sockaddr_in *)cf_malloc(sizeof(struct sockaddr_in)); /* malloc OK, now check for validity */ if ( 1 != str2addr($3, &(((struct sockaddr_in *)$$->value)->sin_addr)) ) { fprintf(stderr, "Config Error: Invalid Server IP Address\n"); exit(-1); } ((struct sockaddr_in *)$$->value)->sin_family = AF_INET; /* no port defined, use standard port 80 */ ((struct sockaddr_in *)$$->value)->sin_port = htons(80); } | FORWARD_PROXY '=' STRING ';' { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_set_forward_proxy; if ( !strcasecmp($3, "enable") ) yyint = 1; else if ( !strcasecmp($3, "disable") ) yyint = 0; else { fprintf(stderr, "Config Error: Invalid $forward_proxy value\n"); exit(-1); } $$->value = (int *)cf_malloc(sizeof(int)); *(int *)($$->value) = yyint; } | LOCATION '=' STRING ';' { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_set_location; $$->value = strdup($3); } ; return_statment: RETURN NUMBER ';' { $$ = (decision_tree *)cf_malloc(sizeof(decision_tree)); init_dt($$); $$->f = &cfg_return; $$->value = (int *)cf_malloc(sizeof(int)); *((int *)$$->value) = $2; } ; ip_var: LOCAL_IP { $$ = LOCAL_IP; } | CLIENT_IP { $$ = CLIENT_IP; } ; str_var: METHOD { $$ = METHOD; } | HOST { $$ = HOST; } | PATH { $$ = PATH; } | USER_AGENT { $$ = USER_AGENT; } | REFERER { $$ = REFERER; } | EXT { $$ = EXT; } | QUERY { $$ = QUERY; } | COOKIE { $$ = COOKIE; } ; num_var: PORT { $$ = (cfg_variable *)cf_malloc(sizeof(cfg_variable)); $$->type = PORT; $$->f = NULL; } | POST_LEN { $$ = (cfg_variable *)cf_malloc(sizeof(cfg_variable)); $$->type = POST_LEN; $$->f = NULL; } | STRLEN '(' str_var ')' { $$ = (cfg_variable *)cf_malloc(sizeof(cfg_variable)); $$->type = $3; $$->f = &cfg_strlen; } ; auth_var: AUTH { $$ = AUTH; } | PROXY_AUTH { $$ = PROXY_AUTH; } ; ip_range: IP_ADDR '/' NUMBER { $$ = (ip_range_t *)cf_malloc(sizeof(ip_range_t)); if ( 1 == str2addr($1, &yyaddr) ) { $$->start_addr = ntohl(yyaddr.s_addr); } else { printf("Invalid IP address: %s\n", $1); } yyint = $3; if ( yyint <= 0 || yyint >= 32 ) { printf("Invalid Network Mask: %d\n", yyint); } $$->end_addr = $$->start_addr | (0xFFFFFFFF >> yyint); } | IP_ADDR '-' IP_ADDR { $$ = (ip_range_t *)cf_malloc(sizeof(ip_range_t)); if ( 1 == str2addr($1, &yyaddr) ) { $$->start_addr = ntohl(yyaddr.s_addr); } else { printf("Invalid IP address: %s\n", $1); } if ( 1 == str2addr($3, &yyaddr) ) { $$->end_addr = ntohl(yyaddr.s_addr); } else { printf("Invalid IP address: %s\n", $1); } } | IP_ADDR { $$ = (ip_range_t *)cf_malloc(sizeof(ip_range_t)); if ( 1 == str2addr($1, &yyaddr) ) { $$->start_addr = ntohl(yyaddr.s_addr); } else { printf("Invalid IP address: %s\n", $1); } $$->end_addr = $$->start_addr; } ; logic: AND { $$ = AND; } | OR { $$ = OR; } ; /* test for equal or not equal */ eq_test: EQ { $$ = EQ; } | NE { $$ = NE; } ; /* test for equal, not equal, or ~=, ie. regex */ str_test: eq_test ; /* numeric comparison, == != > >= < <= */ num_test: eq_test | GE { $$ = GE; } | GT { $$ = GT; } | LE { $$ = LE; } | LT { $$ = LT; } ; %% yyerror(char *err_msg) { printf("Config Error in Line %d: %s near \"%s\"\n", yylineno, err_msg, yytext); exit(-1); }