diff --git a/modules/libcom/src/as/asLib.h b/modules/libcom/src/as/asLib.h index 415d98d1a..b2361c531 100644 --- a/modules/libcom/src/as/asLib.h +++ b/modules/libcom/src/as/asLib.h @@ -202,6 +202,7 @@ typedef struct{ ELLLIST uagList; /*List of ASGUAG*/ ELLLIST hagList; /*List of ASGHAG*/ int trapMask; + int ignore; // 1 if rule to be ignored because of unknown elements } ASGRULE; typedef struct{ ELLNODE node; diff --git a/modules/libcom/src/as/asLib.y b/modules/libcom/src/as/asLib.y index 1e7397123..b947223b4 100644 --- a/modules/libcom/src/as/asLib.y +++ b/modules/libcom/src/as/asLib.y @@ -12,6 +12,7 @@ static int yyerror(char *); static int yy_start; #include "asLibRoutines.c" static int yyFailed = FALSE; +static int yyWarned = FALSE; static int line_num=1; static UAG *yyUag=NULL; static HAG *yyHag=NULL; @@ -21,17 +22,24 @@ static ASGRULE *yyAsgRule=NULL; %start asconfig -%token tokenUAG tokenHAG tokenASG tokenRULE tokenCALC -%token tokenINP -%token tokenINTEGER -%token tokenSTRING +%token tokenUAG tokenHAG tokenASG tokenRULE tokenCALC tokenINP tokenSTRING +%token tokenINT64 +%token tokenFLOAT64 %union { - int Int; + epicsInt64 Int64; + epicsFloat64 Float64; char *Str; } +%type non_rule_keyword +%type generic_block_elem_name +%type generic_block_elem +%type rule_generic_block_elem +%type rule_generic_block_elem_name +%type keyword + %% asconfig: asconfig asconfig_item @@ -43,13 +51,121 @@ asconfig_item: tokenUAG uag_head uag_body | tokenHAG hag_head | tokenASG asg_head asg_body | tokenASG asg_head + | generic_item + ; + +keyword: tokenUAG + | tokenHAG + | tokenCALC + | non_rule_keyword + ; + +non_rule_keyword: tokenASG + | tokenRULE + | tokenINP + ; + +generic_item: tokenSTRING generic_head generic_list_block + { + yywarn("Ignoring unsupported TOP LEVEL Access Control Definition", $1); + free($1); + } + | tokenSTRING generic_head generic_block + { + yywarn("Ignoring unsupported TOP LEVEL Access Control Definition", $1); + free($1); + } + | tokenSTRING generic_head + { + yywarn("Ignoring unsupported TOP LEVEL Access Control Definition", $1); + free($1); + } + ; + +generic_head: '(' ')' + | '(' generic_element ')' + | '(' generic_list ')' + ; + +generic_list_block: '{' generic_element '}' + '{' generic_list '}' + ; + +generic_list: generic_list ',' generic_element + | generic_element ',' generic_element + ; + +generic_element: keyword + | tokenSTRING + { + free($1); + } + | tokenINT64 + | tokenFLOAT64 + ; + +generic_block: '{' generic_element '}' + | '{' generic_list '}' + | '{' generic_block_list '}' + ; + +generic_block_list: generic_block_list generic_block_elem + { + free($2); + } + | generic_block_elem + { + free($1); + } + ; + +generic_block_elem: generic_block_elem_name generic_head generic_block + { + $$ = $1; + } + | generic_block_elem_name generic_head + { + $$ = $1; + } + ; + +generic_block_elem_name: keyword + { + $$ = strdup($1); + if (!$$) yyerror("Out of memory"); + } + | tokenSTRING + { + $$ = $1; + } + ; + +rule_generic_block_elem: rule_generic_block_elem_name generic_head generic_block + { + $$ = $1; + } + | rule_generic_block_elem_name generic_head + { + $$ = $1; + } + ; + +rule_generic_block_elem_name: non_rule_keyword + { + $$ = strdup($1); + if (!$$) yyerror("Out of memory"); + } + | tokenSTRING + { + $$ = $1; + } ; uag_head: '(' tokenSTRING ')' { yyUag = asUagAdd($2); if(!yyUag) yyerror(""); - free((void *)$2); + free($2); } ; @@ -67,7 +183,7 @@ uag_user_list_name: tokenSTRING { if (asUagAddUser(yyUag,$1)) yyerror(""); - free((void *)$1); + free($1); } ; @@ -75,7 +191,7 @@ hag_head: '(' tokenSTRING ')' { yyHag = asHagAdd($2); if(!yyHag) yyerror(""); - free((void *)$2); + free($2); } ; @@ -90,7 +206,7 @@ hag_host_list_name: tokenSTRING { if (asHagAddHost(yyHag,$1)) yyerror(""); - free((void *)$1); + free($1); } ; @@ -98,7 +214,7 @@ asg_head: '(' tokenSTRING ')' { yyAsg = asAsgAdd($2); if(!yyAsg) yyerror(""); - free((void *)$2); + free($2); } ; @@ -114,49 +230,49 @@ asg_body_item: inp_config | rule_config inp_config: tokenINP '(' tokenSTRING ')' { - if (asAsgAddInp(yyAsg,$3,$1)) + if (asAsgAddInp(yyAsg,$3,(int)$1)) yyerror(""); - free((void *)$3); + free($3); } ; rule_config: tokenRULE rule_head rule_body | tokenRULE rule_head -rule_head: rule_head_manditory rule_head_options +rule_head: '(' rule_head_mandatory ',' rule_log_option ')' + | '(' rule_head_mandatory ')' + ; -rule_head_manditory: '(' tokenINTEGER ',' tokenSTRING + +rule_head_mandatory: tokenINT64 ',' tokenSTRING { - asAccessRights rights; - - if((strcmp($4,"NONE")==0)) { - rights=asNOACCESS; - } else if((strcmp($4,"READ")==0)) { - rights=asREAD; - } else if((strcmp($4,"WRITE")==0)) { - rights=asWRITE; + if ($1 < 0) { + char message[40]; + sprintf(message, "RULE: LEVEL must be positive: %lld", $1); + yyerror(message); + } else if((strcmp($3,"NONE")==0)) { + yyAsgRule = asAsgAddRule(yyAsg,asNOACCESS,(int)$1); + } else if((strcmp($3,"READ")==0)) { + yyAsgRule = asAsgAddRule(yyAsg,asREAD,(int)$1); + } else if((strcmp($3,"WRITE")==0)) { + yyAsgRule = asAsgAddRule(yyAsg,asWRITE,(int)$1); } else { - yyerror("Access rights must be NONE, READ or WRITE"); - rights = asNOACCESS; + yywarn("Ignoring RULE that contains an unsupported keyword", $3); } - yyAsgRule = asAsgAddRule(yyAsg,rights,$2); - free((void *)$4); + free($3); } ; -rule_head_options: ')' - | rule_log_options - -rule_log_options: ',' tokenSTRING ')' +rule_log_option: tokenSTRING { - if((strcmp($2,"TRAPWRITE")==0)) { + if((strcmp($1,"TRAPWRITE")==0)) { long status; status = asAsgAddRuleOptions(yyAsgRule,AS_TRAP_WRITE); if(status) yyerror(""); - } else if((strcmp($2,"NOTRAPWRITE")!=0)) { + } else if((strcmp($1,"NOTRAPWRITE")!=0)) { yyerror("Log options must be TRAPWRITE or NOTRAPWRITE"); } - free((void *)$2); + free($1); } ; @@ -173,7 +289,14 @@ rule_list_item: tokenUAG '(' rule_uag_list ')' { if (asAsgRuleCalc(yyAsgRule,$3)) yyerror(""); - free((void *)$3); + free($3); + } + | rule_generic_block_elem + { + yywarn("Ignoring RULE that contains an unsupported keyword", $1); + free($1); + if (asAsgRuleDisable(yyAsgRule)) + yyerror(""); } ; @@ -185,7 +308,7 @@ rule_uag_list_name: tokenSTRING { if (asAsgRuleUagAdd(yyAsgRule,$1)) yyerror(""); - free((void *)$1); + free($1); } ; @@ -197,7 +320,7 @@ rule_hag_list_name: tokenSTRING { if (asAsgRuleHagAdd(yyAsgRule,$1)) yyerror(""); - free((void *)$1); + free($1); } ; %% @@ -207,12 +330,19 @@ rule_hag_list_name: tokenSTRING static int yyerror(char *str) { if (strlen(str)) - errlogPrintf("%s at line %d\n", str, line_num); + fprintf(stderr, ERL_ERROR " %s at line %d\n", str, line_num); else - errlogPrintf(ERL_ERROR " at line %d\n", line_num); + fprintf(stderr, ERL_ERROR " at line %d\n", line_num); yyFailed = TRUE; return 0; } +static int yywarn(char *str, char *token) +{ + if (!yyWarned && strlen(str) && strlen(token)) + fprintf(stderr, ERL_WARNING " %s at line %d: %s\n", str, line_num, token); + yyWarned = TRUE; + return 0; +} static int myParse(ASINPUTFUNCPTR inputfunction) { static int FirstFlag = 1; @@ -222,6 +352,7 @@ static int myParse(ASINPUTFUNCPTR inputfunction) if (!FirstFlag) { line_num=1; yyFailed = FALSE; + yyWarned = FALSE; yyreset(); yyrestart(NULL); } diff --git a/modules/libcom/src/as/asLibRoutines.c b/modules/libcom/src/as/asLibRoutines.c index 571471e2a..f86f985e5 100644 --- a/modules/libcom/src/as/asLibRoutines.c +++ b/modules/libcom/src/as/asLibRoutines.c @@ -66,6 +66,7 @@ static long asAsgAddRuleOptions(ASGRULE *pasgrule,int trapMask); static long asAsgRuleUagAdd(ASGRULE *pasgrule,const char *name); static long asAsgRuleHagAdd(ASGRULE *pasgrule,const char *name); static long asAsgRuleCalc(ASGRULE *pasgrule,const char *calc); +static long asAsgRuleDisable(ASGRULE *pasgrule); /* asInitialize can be called while access security is already active. @@ -585,8 +586,8 @@ int epicsStdCall asDumpFP( pasginp = (ASGINP *)ellNext(&pasginp->node); } while(pasgrule) { - int print_end_brace; - + int print_rule_end_brace = FALSE; + if (pasgrule->ignore) goto next_rule; fprintf(fp,"\tRULE(%d,%s,%s)", pasgrule->level,asAccessName[pasgrule->access], asTrapOption[pasgrule->trapMask]); @@ -594,10 +595,10 @@ int epicsStdCall asDumpFP( pasghag = (ASGHAG *)ellFirst(&pasgrule->hagList); if(pasguag || pasghag || pasgrule->calc) { fprintf(fp," {\n"); - print_end_brace = TRUE; + print_rule_end_brace = TRUE; } else { fprintf(fp,"\n"); - print_end_brace = FALSE; + print_rule_end_brace = FALSE; } if(pasguag) fprintf(fp,"\t\tUAG("); while(pasguag) { @@ -605,7 +606,6 @@ int epicsStdCall asDumpFP( pasguag = (ASGUAG *)ellNext(&pasguag->node); if(pasguag) fprintf(fp,","); else fprintf(fp,")\n"); } - pasghag = (ASGHAG *)ellFirst(&pasgrule->hagList); if(pasghag) fprintf(fp,"\t\tHAG("); while(pasghag) { fprintf(fp,"%s",pasghag->phag->name); @@ -618,7 +618,8 @@ int epicsStdCall asDumpFP( fprintf(fp," result=%s",(pasgrule->result==1 ? "TRUE" : "FALSE")); fprintf(fp,"\n"); } - if(print_end_brace) fprintf(fp,"\t}\n"); +next_rule: + if(print_rule_end_brace) fprintf(fp,"\t}\n"); pasgrule = (ASGRULE *)ellNext(&pasgrule->node); } pasgmember = (ASGMEMBER *)ellFirst(&pasg->memberList); @@ -735,7 +736,7 @@ int epicsStdCall asDumpRulesFP(FILE *fp,const char *asgname) pasg = (ASG *)ellFirst(&pasbase->asgList); if(!pasg) fprintf(fp,"No ASGs\n"); while(pasg) { - int print_end_brace; + int print_end_brace = FALSE; if(asgname && strcmp(asgname,pasg->name)!=0) { pasg = (ASG *)ellNext(&pasg->node); @@ -761,8 +762,8 @@ int epicsStdCall asDumpRulesFP(FILE *fp,const char *asgname) pasginp = (ASGINP *)ellNext(&pasginp->node); } while(pasgrule) { - int print_end_brace; - + int print_rule_end_brace = FALSE; + if ( pasgrule->ignore) goto next_rule; fprintf(fp,"\tRULE(%d,%s,%s)", pasgrule->level,asAccessName[pasgrule->access], asTrapOption[pasgrule->trapMask]); @@ -770,10 +771,10 @@ int epicsStdCall asDumpRulesFP(FILE *fp,const char *asgname) pasghag = (ASGHAG *)ellFirst(&pasgrule->hagList); if(pasguag || pasghag || pasgrule->calc) { fprintf(fp," {\n"); - print_end_brace = TRUE; + print_rule_end_brace = TRUE; } else { fprintf(fp,"\n"); - print_end_brace = FALSE; + print_rule_end_brace = FALSE; } if(pasguag) fprintf(fp,"\t\tUAG("); while(pasguag) { @@ -793,7 +794,8 @@ int epicsStdCall asDumpRulesFP(FILE *fp,const char *asgname) fprintf(fp," result=%s",(pasgrule->result==1 ? "TRUE" : "FALSE")); fprintf(fp,"\n"); } - if(print_end_brace) fprintf(fp,"\t}\n"); +next_rule: + if(print_rule_end_brace) fprintf(fp,"\t}\n"); pasgrule = (ASGRULE *)ellNext(&pasgrule->node); } if(print_end_brace) fprintf(fp,"}\n"); @@ -948,6 +950,7 @@ static long asComputeAsgPvt(ASG *pasg) if(!asActive) return(S_asLib_asNotActive); pasgrule = (ASGRULE *)ellFirst(&pasg->ruleList); while(pasgrule) { + if ( pasgrule->ignore) goto next_rule; double result = pasgrule->result; /* set for VAL */ long status; @@ -960,6 +963,8 @@ static long asComputeAsgPvt(ASG *pasg) pasgrule->result = ((result>.99) && (result<1.01)) ? 1 : 0; } } + +next_rule: pasgrule = (ASGRULE *)ellNext(&pasgrule->node); } pasg->inpChanged = FALSE; @@ -995,6 +1000,7 @@ static long asComputePvt(ASCLIENTPVT asClientPvt) oldaccess=pasgclient->access; pasgrule = (ASGRULE *)ellFirst(&pasg->ruleList); while(pasgrule) { + if (pasgrule->ignore) goto next_rule; if(access == asWRITE) break; if(access>=pasgrule->access) goto next_rule; if(pasgclient->level > pasgrule->level) goto next_rule; @@ -1408,3 +1414,14 @@ static long asAsgRuleCalc(ASGRULE *pasgrule,const char *calc) } return(status); } + +/** + * @brief Disable a rule if it contains unsupported elements + * @param pasgrule the rule to disable + * @return Non-zero if rule was not disabled + */ +static long asAsgRuleDisable(ASGRULE *pasgrule) { + if (!pasgrule) return 1; + pasgrule->ignore = 1; + return 0; +} diff --git a/modules/libcom/src/as/asLib_lex.l b/modules/libcom/src/as/asLib_lex.l index b7f01f5c5..7ba1c8da7 100644 --- a/modules/libcom/src/as/asLib_lex.l +++ b/modules/libcom/src/as/asLib_lex.l @@ -21,6 +21,8 @@ punctuation [(){},] link [A-U] %{ +#include "epicsStdlib.h" + static ASINPUTFUNCPTR *my_yyinput; #undef YY_INPUT #define YY_INPUT(b,r,ms) (r=(*my_yyinput)((char *)b,ms)) @@ -43,14 +45,31 @@ RULE { return(tokenRULE); } CALC { return(tokenCALC); } INP{link} { - yylval.Int = (unsigned char)yytext[3]; - yylval.Int -= 'A'; + yylval.Int64 = (unsigned char)yytext[3]; + yylval.Int64 -= 'A'; return(tokenINP); } -{digit}+ { /*integer*/ - yylval.Int = atoi((char *)yytext); - return(tokenINTEGER); +[-+]?{digit}*\.{digit}+([eE][-+]?{digit}+)? { + char *end; + if (epicsParseDouble((char *)yytext, &yylval.Float64, &end) ) { + char message[40]; + sprintf(message, "Error parsing Float64: %s", (char *)yytext); + yyerror(message); + } else { + return(tokenFLOAT64); + } +} + +[-+]?{digit}+ { /*integer 64*/ + char *end; + if (epicsParseInt64((char *)yytext, &yylval.Int64, 10, &end) ) { + char message[40]; + sprintf(message, "Error parsing Int64: %s", (char *)yytext); + yyerror(message); + } else { + return(tokenINT64); + } } {name}+ { /*unquoted string*/ diff --git a/modules/libcom/test/aslibtest.c b/modules/libcom/test/aslibtest.c index eefad5700..705823de5 100644 --- a/modules/libcom/test/aslibtest.c +++ b/modules/libcom/test/aslibtest.c @@ -22,18 +22,538 @@ static char *asUser, *asHost; static int asAsl; +/** + * @brief Test data with Host Access Groups (HAG) + * + * This includes a host access group (HAG) for localhost and a default Access Security Group (ASG) + * with rules for read and write access to the HAG. + */ +static const char hostname_config[] = "" + "HAG(foo) {localhost}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n" + + "ASG(rw) {\n" + " RULE(1, WRITE) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported element should be silently ignored, but the rest of the config is processed. + * + * top-unknown-keyword(WELL,FORMED,LIST) + * - valid top level keyword with well-formed arg list + */ +static const char supported_config_1[] = "" + "HAG(foo) {localhost}\n" + + "GENERIC(WELL, FORMED, ARG, LIST)\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported element should be silently ignored, but the rest of the config is processed. + * + * top-unknown-keyword(WELL,FORMED,LIST) { WELL,FORMED,LIST } + * - valid top level keyword with well-formed arg list and valid arg list body + */ +static const char supported_config_2[] = "" + "HAG(foo) {localhost}\n" + + "SIMPLE(WELL, FORMED, ARG, LIST) {\n" + " WELL, FORMED, LIST\n" + "}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported element should be silently ignored, but the rest of the config is processed. + * + * top-unknown-keyword(WELL,FORMED,LIST) { recursive-body-keyword(WELL,FORMED,LIST) } + * - valid top level keyword with well-formed arg list and valid recursive body + * - includes quoted strings, integers, and floating point numbers + */ +static const char supported_config_3[] = "" + "HAG(foo) {localhost}\n" + + "COMPLEX_ARGUMENTS(1, WELL, \"FORMED\", ARG, LIST) {\n" + " ALSO_GENERIC(WELL, FORMED, ARG, LIST, 2.0) \n" + "}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported element should be silently ignored, but the rest of the config is processed. + * + * top-unknown-keyword(WELL,FORMED,LIST) { recursive-body-keyword(WELL,FORMED,LIST) { AND_BODY } } + * - valid top level keyword with well-formed arg list and valid recursive body, with a nested body + * - includes floating point numbers, and an empty arg list + */ +static const char supported_config_4[] = "" +"HAG(foo) {localhost}\n" + +"SUB_BLOCKS(1.0, ARGS) {\n" +" ALSO_GENERIC() {\n" +" AND_LIST_BODY\n" +" }\n" +" ANOTHER_GENERIC() {\n" +" BIGGER, LIST, BODY\n" +" }\n" +"}\n" + +"ASG(DEFAULT) {\n" +" RULE(0, NONE)\n" +"}\n" + +"ASG(ro) {\n" +" RULE(0, NONE)\n" +" RULE(1, READ) {\n" +" HAG(foo)\n" +" }\n" +"}\n"; + +/** + * Test data with unsupported elements. + * The unsupported element should be silently ignored, but the rest of the config is processed. + * + * top-unknown-keyword(WELL,FORMED,LIST) { recursive-body-keyword(WELL,FORMED,LIST) { AND_RECURSIVE_BODY() {LIST, LIST } } + * - valid top level keyword with well-formed arg list and valid recursive body, with a nested recursion + * - includes floating point numbers, and an empty arg list + */ +static const char supported_config_5[] = "" + "HAG(foo) {localhost}\n" + + "RECURSIVE_SUB_BLOCKS(1.0, -2.3, +4.5, ARGS, +2.71828E-23, -2.71828e+23, +12, -13, +-14) {\n" + " ALSO_GENERIC() {\n" + " AND_RECURSIVE(FOO) {\n" + " LIST, BODY\n" + " }\n" + " }\n" + "}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(+1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported element should be silently ignored, but the rest of the config is processed. + * + * top-unknown-keyword(KEYWORD) { KEYWORD(KEYWORD) } + * - valid top level keyword with keyword for args, recursive body name, and arg list + * - top level generic items referenced in RULES, then RULES are ignored + */ +static const char supported_config_6[] = "" + "HAG(foo) {localhost}\n" + + "WITH_KEYWORDS(UAG) {\n" + " ASG(HAL, IMP, CALC, RULE)\n" + " HAL(USG, MAL) {\n" + " HAG(foo)\n" + " }\n" + "}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ignored) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " WITH_KEYWORDS(UAG)\n" + " }\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + " RULE(2, WRITE) {\n" + " WITH_KEYWORDS(UAG)\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported elements should be silently ignored, and the rule will not match, + * but the rest of the config is processed. + * + * - RULE contains unsupported elements + */ +static const char supported_config_7[] = "" + "HAG(foo) {localhost}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " BAD_PREDICATE(\"x509\")\n" + " BAD_PREDICATE_AS_WELL(\"EPICS Certificate Authority\")\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported elements should be silently ignored, and the rule will not match, + * but the rest of the config is processed. + * + * - unexpected permission name in arg list for RULE element ignored + */ +static const char supported_config_8[] = "" + "HAG(foo) {localhost}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, ADDITIONAL_PERMISSION) {\n" + " HAG(foo)\n" + " }\n" + "}\n" + ; + +/** + * Test data with unsupported elements. + * The unsupported element should cause an error as the format is invalid. + * + * top-unknown-keyword( a b ) + * - invalid arg list missing commas + */ +static const char unsupported_config_1[] = "" + "HAG(foo) {localhost}\n" + + "GENERIC(not well-formed arg list)\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported element should cause an error as the format is invalid. + * + * top-unknown-keyword(WELL,FORMED,LIST) { a b } + * - invalid string list + */ +static const char unsupported_config_2[] = "" + "HAG(foo) {localhost}\n" + + "GENERIC(WELL, FORMED, ARG, LIST) {\n" + " NOT WELL-FORMED BODY\n" + "}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported element should cause an error as the format is invalid. + * + * top-unknown-keyword { a, b } + * - missing parameters (must have at least an empty arg list) + */ +static const char unsupported_config_3[] = "" + "HAG(foo) {localhost}\n" + + "GENERIC {\n" + " WELL, FORMED, LIST, BODY\n" + "}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported element should cause an error as the format is invalid. + * + * top-unknown-keyword(WELL,FORMED,LIST) { X, Y(a b c) } + * - bad arg list for recursive body + */ +static const char unsupported_config_4[] = "" + "HAG(foo) {localhost}\n" + + "GENERIC(WELL, FORMED, ARG, LIST) {\n" + " BODY(BAD ARG LIST)\n" + "}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * Test data with unsupported elements. + * The unsupported element should cause an error as the format is invalid. + * + * top-unknown-keyword(WELL,FORMED,LIST) { X, Y(a b c) } + * - mix of list and recursive type bodies + */ +static const char unsupported_config_5[] = "" + "HAG(foo) {localhost}\n" + + "GENERIC(WELL, FORMED, ARG, LIST) {\n" + " LIST, BODY, MIXED, WITH,\n" + " RECURSIVE_BODY(ARG, LIST)\n" + "}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * The modification to a well known element should cause an error. + * + * - bad arg list for ASG element + */ +static const char unsupported_mod_1[] = "" + "HAG(foo) {localhost}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro BAD ARG LIST) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * The modification to a well known element should cause an error. + * + * - bad arg list for HAG element + */ +static const char unsupported_mod_2[] = "" + "HAG(BAD ARG LIST) {localhost}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * The modification to a well known element should cause an error. + * + * - bad arg list for RULE element + */ +static const char unsupported_mod_3[] = "" + "HAG(foo) {localhost}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0 BAD ARG LIST)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * The modification to a well known element should cause an error. + * + * - bad arg count for ASG element + */ +static const char unsupported_mod_4[] = "" + "HAG(foo) {localhost}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro, UNKNOWN_PERMISSION) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * The modification to a well known element should cause an error. + * + * - unexpected name in arg list for RULE element + */ +static const char unsupported_mod_5[] = "" + "HAG(foo) {localhost}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE, UNKNOWN_FLAG)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * The modification to a well known element should cause an error. + * + * - unexpected recursive body mixed in with HAG string list body + */ +static const char unsupported_mod_6[] = "" + "HAG(foo) {\n" + " localhost,\n" + " NETWORK(\"127.0.0.1\")\n" + "}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * The modification to a well known element should cause an error. + * + * - unexpected recursive body mixed in with UAG string list body + */ +static const char unsupported_mod_7[] = "" + "UAG(foo) {\n" + " alice,\n" + " GROUP(admin)\n" + "}\n" + + "ASG(DEFAULT) {\n" + " RULE(0, NONE)\n" + "}\n" + + "ASG(ro) {\n" + " RULE(0, NONE)\n" + " RULE(1, READ) {\n" + " HAG(foo)\n" + " }\n" + "}\n"; + +/** + * Set the username for the authorization tests + */ static void setUser(const char *name) { free(asUser); asUser = epicsStrDup(name); } +/** + * Set the hostname for the authorization tests + */ static void setHost(const char *name) { free(asHost); asHost = epicsStrDup(name); } +/** + * Test the access control system with the given ASG, user, and hostname + * This will test that the expected access given by mask is granted + * when the Access Security Group (ASG) is interpreted in the + * context of the configured user, host, and Level (asl). + */ static void testAccess(const char *asg, unsigned mask) { ASMEMBERPVT asp = 0; /* aka dbCommon::asp */ @@ -67,18 +587,13 @@ static void testSyntaxErrors(void) long ret; testDiag("testSyntaxErrors()"); + asCheckClientIP = 0; eltc(0); ret = asInitMem(empty, NULL); testOk(ret==S_asLib_badConfig, "load \"empty\" config -> %s", errSymMsg(ret)); eltc(1); } -static const char hostname_config[] = "" - "HAG(foo) {localhost}\n" - "ASG(DEFAULT) {RULE(0, NONE)}\n" - "ASG(ro) {RULE(0, NONE)RULE(1, READ) {HAG(foo)}}\n" - "ASG(rw) {RULE(1, WRITE) {HAG(foo)}}\n" - ; static void testHostNames(void) { @@ -144,10 +659,132 @@ static void testUseIP(void) testAccess("rw", 0); } +static void testFutureProofParser(void) +{ + long ret; + + testDiag("testFutureProofParser()"); + asCheckClientIP = 0; + + eltc(0); /* Suppress error messages during test */ + + /* Test parsing should reject unsupported elements badly placed or formed */ + ret = asInitMem(unsupported_config_1, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects invalid arg list missing commas -> %s", errSymMsg(ret)); + + ret = asInitMem(unsupported_config_2, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects invalid string list -> %s", errSymMsg(ret)); + + ret = asInitMem(unsupported_config_3, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects missing parameters (must have at least an empty arg list) -> %s", errSymMsg(ret)); + + ret = asInitMem(unsupported_config_4, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects bad arg list for recursive body -> %s", errSymMsg(ret)); + + ret = asInitMem(unsupported_config_5, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects mix of list and recursive type bodies -> %s", errSymMsg(ret)); + + + /* Test supported elements badly modified should be rejected */ + ret = asInitMem(unsupported_mod_1, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects bad arg list for ASG element -> %s", errSymMsg(ret)); + + ret = asInitMem(unsupported_mod_2, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects bad arg list for HAG element-> %s", errSymMsg(ret)); + + ret = asInitMem(unsupported_mod_3, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects bad arg list for RULE element -> %s", errSymMsg(ret)); + + ret = asInitMem(unsupported_mod_4, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects bad arg count for ASG element -> %s", errSymMsg(ret)); + + ret = asInitMem(unsupported_mod_5, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects unexpected name in arg list for RULE element -> %s", errSymMsg(ret)); + + ret = asInitMem(unsupported_mod_6, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects unexpected recursive body in HAG element body -> %s", errSymMsg(ret)); + + ret = asInitMem(unsupported_mod_7, NULL); + testOk(ret==S_asLib_badConfig, "parsing rejects unexpected recursive body in UAG element body -> %s", errSymMsg(ret)); + + + eltc(1); + + /* Test supported for known elements containing unsupported elements, well-formed and ignored */ + setUser("testing"); + setHost("localhost"); + + ret = asInitMem(supported_config_1, NULL); + testOk(ret==0, "unknown elements ignored -> %s", errSymMsg(ret)); + if (!ret) { + asAsl = 0; + testAccess("DEFAULT", 0); + testAccess("ro", 1); + } + + ret = asInitMem(supported_config_2, NULL); + testOk(ret==0, "unknown elements with body ignored -> %s", errSymMsg(ret)); + if (!ret) { + asAsl = 0; + testAccess("DEFAULT", 0); + testAccess("ro", 1); + } + + ret = asInitMem(supported_config_3, NULL); + testOk(ret==0, "unknown elements with string and double args and a body, ignored -> %s", errSymMsg(ret)); + if (!ret) { + asAsl = 0; + testAccess("DEFAULT", 0); + testAccess("ro", 1); + } + + ret = asInitMem(supported_config_4, NULL); + testOk(ret==0, "unknown elements with recursive body ignored -> %s", errSymMsg(ret)); + if (!ret) { + asAsl = 0; + testAccess("DEFAULT", 0); + testAccess("ro", 1); + } + + ret = asInitMem(supported_config_5, NULL); + testOk(ret==0, "unknown elements with recursive body with recursion ignored -> %s", errSymMsg(ret)); + if (!ret) { + asAsl = 0; + testAccess("DEFAULT", 0); + testAccess("ro", 1); + } + + ret = asInitMem(supported_config_6, NULL); + testOk(ret==0, "unknown elements with keywords arguments and body names ignored -> %s", errSymMsg(ret)); + if (!ret) { + asAsl = 0; + testAccess("DEFAULT", 0); + testAccess("ignored", 0); + testAccess("ro", 1); + } + + ret = asInitMem(supported_config_7, NULL); + testOk(ret==0, "rules with unknown elements ignored -> %s", errSymMsg(ret)); + if (!ret) { + asAsl = 0; + testAccess("DEFAULT", 0); + testAccess("ro", 0); + } + + ret = asInitMem(supported_config_8, NULL); + testOk(ret==0, "rules with unknown permission names ignored -> %s", errSymMsg(ret)); + if (!ret) { + asAsl = 0; + testAccess("DEFAULT", 0); + testAccess("ro", 0); + } +} + MAIN(aslibtest) { - testPlan(27); + testPlan(64); testSyntaxErrors(); + testFutureProofParser(); testHostNames(); testUseIP(); errlogFlush();