Redid some of the code to make it easier to add more rule types.

Added a timerange for the rules when they are active, defaults to always on.
Added custom error messages.
This commit is contained in:
Markus Makela
2014-10-26 10:34:26 +02:00
parent 67101278ac
commit 38de0909c3

View File

@ -109,9 +109,9 @@ typedef enum{
*/ */
typedef enum { typedef enum {
RT_UNDEFINED, RT_UNDEFINED,
RT_USER,
RT_COLUMN, RT_COLUMN,
RT_TIME, RT_TIME,
RT_PERMISSION,
RT_WILDCARD RT_WILDCARD
}ruletype_t; }ruletype_t;
@ -140,6 +140,8 @@ typedef struct rule_t{
void* data; void* data;
char* name; char* name;
ruletype_t type; ruletype_t type;
bool allow;
TIMERANGE* active;
}RULE; }RULE;
/** /**
@ -490,7 +492,6 @@ TIMERANGE* parse_time(char* str, FW_INSTANCE* instance)
assert(str != NULL && instance != NULL); assert(str != NULL && instance != NULL);
tr = (TIMERANGE*)malloc(sizeof(TIMERANGE)); tr = (TIMERANGE*)malloc(sizeof(TIMERANGE));
if(tr == NULL){ if(tr == NULL){
@ -520,6 +521,10 @@ TIMERANGE* parse_time(char* str, FW_INSTANCE* instance)
CHK_TIMES(tmptr); CHK_TIMES(tmptr);
if(*ptr == '\0'){
return tr;
}
idest = intbuffer; idest = intbuffer;
tmptr = &tr->end; tmptr = &tr->end;
} }
@ -617,7 +622,7 @@ void add_users(char* rule, FW_INSTANCE* instance)
{ {
assert(rule != NULL && instance != NULL); assert(rule != NULL && instance != NULL);
STRLINK* link = malloc(sizeof(STRLINK)); STRLINK* link = calloc(1,sizeof(STRLINK));
link->next = instance->userstrings; link->next = instance->userstrings;
link->value = strdup(rule); link->value = strdup(rule);
instance->userstrings = link; instance->userstrings = link;
@ -687,7 +692,6 @@ void parse_rule(char* rule, FW_INSTANCE* instance)
assert(rule != NULL && instance != NULL); assert(rule != NULL && instance != NULL);
char *rulecpy = strdup(rule); char *rulecpy = strdup(rule);
char *ptr = rule;
char *tok = strtok(rulecpy," ,"); char *tok = strtok(rulecpy," ,");
bool allow,deny,mode; bool allow,deny,mode;
RULE* ruledef = NULL; RULE* ruledef = NULL;
@ -702,11 +706,10 @@ void parse_rule(char* rule, FW_INSTANCE* instance)
RULELIST* rlist = NULL; RULELIST* rlist = NULL;
ruledef = (RULE*)malloc(sizeof(RULE)); ruledef = (RULE*)calloc(1,sizeof(RULE));
rlist = (RULELIST*)malloc(sizeof(RULELIST)); rlist = (RULELIST*)calloc(1,sizeof(RULELIST));
ruledef->name = strdup(tok); ruledef->name = strdup(tok);
ruledef->type = RT_UNDEFINED; ruledef->type = RT_UNDEFINED;
ruledef->data = NULL;
rlist->rule = ruledef; rlist->rule = ruledef;
rlist->next = instance->rules; rlist->next = instance->rules;
instance->rules = rlist; instance->rules = rlist;
@ -720,27 +723,25 @@ void parse_rule(char* rule, FW_INSTANCE* instance)
tok = strtok(NULL, " ,"); tok = strtok(NULL, " ,");
/**IP range rules*/
if((allow = (strcmp(tok,"allow") == 0)) || if((allow = (strcmp(tok,"allow") == 0)) ||
(deny = (strcmp(tok,"deny") == 0))){ (deny = (strcmp(tok,"deny") == 0))){
mode = allow ? true:false; mode = allow ? true:false;
ruledef->allow = mode;
ruledef->type = RT_PERMISSION;
tok = strtok(NULL, " ,"); tok = strtok(NULL, " ,");
bool is_user = false, is_column = false, is_time = false;
if(strcmp(tok,"wildcard") == 0) if(strcmp(tok,"wildcard") == 0)
{ {
ruledef->type = RT_WILDCARD; ruledef->type = RT_WILDCARD;
ruledef->data = (void*)mode;
} }
else if(strcmp(tok,"columns") == 0) else if(strcmp(tok,"columns") == 0)
{ {
STRLINK *tail = NULL,*current; STRLINK *tail = NULL,*current;
ruledef->type = RT_COLUMN; ruledef->type = RT_COLUMN;
tok = strtok(NULL, " ,"); tok = strtok(NULL, " ,");
while(tok){ while(tok && strcmp(tok,"times") != 0){
current = malloc(sizeof(STRLINK)); current = malloc(sizeof(STRLINK));
current->value = strdup(tok); current->value = strdup(tok);
current->next = tail; current->next = tail;
@ -755,78 +756,21 @@ void parse_rule(char* rule, FW_INSTANCE* instance)
{ {
tok = strtok(NULL, " ,"); tok = strtok(NULL, " ,");
ruledef->type = RT_TIME;
TIMERANGE *tr = parse_time(tok,instance); TIMERANGE *tr = parse_time(tok,instance);
if(IS_RVRS_TIME(tr)){ if(IS_RVRS_TIME(tr)){
TIMERANGE *tmptr = split_reverse_time(tr); tr = split_reverse_time(tr);
tr = tmptr;
} }
ruledef->data = (void*)tr; ruledef->active = tr;
} }
goto retblock; goto retblock;
tok = strtok(NULL," ,\0");
if(is_user || is_column || is_time){
while(tok){
/**Add value to hashtable*/
ruletype_t rtype =
is_user ? RT_USER :
is_column ? RT_COLUMN:
is_time ? RT_TIME :
RT_UNDEFINED;
if(rtype == RT_USER || rtype == RT_COLUMN)
{
hashtable_add(instance->htable,
(void *)tok,
(void *)rtype);
}
else if(rtype == RT_TIME && check_time(tok))
{
parse_time(tok,instance);
}
tok = strtok(NULL," ,\0");
}
} }
}else if((ptr = strstr(rule,"require")) != NULL){
if((ptr = strstr(ptr,"where")) != NULL &&
(ptr = strchr(ptr,' ')) != NULL){
char* tok;
ptr++;
tok = strtok(ptr," ,\0");
while(tok){
if(strcmp(tok, "all") == 0){
instance->require_where[ALL] = true;
break;
}else if(strcmp(tok, "select") == 0){
instance->require_where[SELECT] = true;
}else if(strcmp(tok, "insert") == 0){
instance->require_where[INSERT] = true;
}else if(strcmp(tok, "update") == 0){
instance->require_where[UPDATE] = true;
}else if(strcmp(tok, "delete") == 0){
instance->require_where[DELETE] = true;
}
tok = strtok(NULL," ,\0");
}
}
}
retblock: retblock:
free(rulecpy); free(rulecpy);
} }
/** /**
@ -862,6 +806,9 @@ createInstance(char **options, FILTER_PARAMETER **params)
for(i = 0;params[i];i++){ for(i = 0;params[i];i++){
if(strstr(params[i]->name,"rule")){ if(strstr(params[i]->name,"rule")){
parse_rule(strip_tags(params[i]->value),my_instance); parse_rule(strip_tags(params[i]->value),my_instance);
}else if(strcmp(params[i]->name,"mode") == 0 &&
strcmp(params[i]->value,"whitelist") == 0){
my_instance->def_op = false;
} }
} }
@ -949,14 +896,23 @@ setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream)
* Generates a dummy error packet for the client. * Generates a dummy error packet for the client.
* @return The dummy packet or NULL if an error occurred * @return The dummy packet or NULL if an error occurred
*/ */
GWBUF* gen_dummy_error(FW_SESSION* session) GWBUF* gen_dummy_error(FW_SESSION* session, char* msg)
{ {
GWBUF* buf; GWBUF* buf;
char errmsg[512]; char* errmsg;
DCB* dcb = session->session->client; DCB* dcb = session->session->client;
MYSQL_session* mysql_session = (MYSQL_session*)session->session->data; MYSQL_session* mysql_session = (MYSQL_session*)session->session->data;
unsigned int errlen, pktlen; unsigned int errlen, pktlen;
errlen = msg != NULL ? strlen(msg) : 0;
errmsg = malloc((512 + errlen)*sizeof(char));
if(errmsg == NULL){
skygw_log_write_flush(LOGFILE_ERROR, "Fatal Error: malloc returned NULL.");
return NULL;
}
if(mysql_session->db[0] == '\0') if(mysql_session->db[0] == '\0')
{ {
sprintf(errmsg, sprintf(errmsg,
@ -971,6 +927,13 @@ GWBUF* gen_dummy_error(FW_SESSION* session)
dcb->remote, dcb->remote,
mysql_session->db); mysql_session->db);
} }
if(msg != NULL){
char* ptr = strchr(errmsg,'\0');
sprintf(ptr,": %s",msg);
}
errlen = strlen(errmsg); errlen = strlen(errmsg);
pktlen = errlen + 9; pktlen = errlen + 9;
buf = gwbuf_alloc(13 + errlen); buf = gwbuf_alloc(13 + errlen);
@ -989,6 +952,40 @@ GWBUF* gen_dummy_error(FW_SESSION* session)
return buf; return buf;
} }
bool inside_timerange(TIMERANGE* comp)
{
struct tm* tm_now;
struct tm tm_before,tm_after;
time_t before,after,now, time_now;
double to_before,to_after;
time(&time_now);
tm_now = localtime(&time_now);
memcpy(&tm_before,tm_now,sizeof(struct tm));
memcpy(&tm_after,tm_now,sizeof(struct tm));
tm_before.tm_sec = comp->start.tm_sec;
tm_before.tm_min = comp->start.tm_min;
tm_before.tm_hour = comp->start.tm_hour;
tm_after.tm_sec = comp->end.tm_sec;
tm_after.tm_min = comp->end.tm_min;
tm_after.tm_hour = comp->end.tm_hour;
before = mktime(&tm_before);
after = mktime(&tm_after);
now = mktime(tm_now);
to_before = difftime(now,before);
to_after = difftime(now,after);
if(to_before > 0.0 && to_after < 0.0){
return true;
}
return false;
}
/** /**
* The routeQuery entry point. This is passed the query buffer * The routeQuery entry point. This is passed the query buffer
* to which the filter should be applied. Once processed the * to which the filter should be applied. Once processed the
@ -1004,49 +1001,79 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
{ {
FW_SESSION *my_session = (FW_SESSION *)session; FW_SESSION *my_session = (FW_SESSION *)session;
FW_INSTANCE *my_instance = (FW_INSTANCE *)instance; FW_INSTANCE *my_instance = (FW_INSTANCE *)instance;
IPRANGE* ipranges = my_instance->networks;
TIMERANGE* times = my_instance->times;
time_t time_now; time_t time_now;
struct tm* tm_now; struct tm* tm_now;
struct tm tm_before,tm_after; bool accept = my_instance->def_op;
bool accept = false, match = false; char *where, *msg = NULL;
char *where; char uname[128];
char username[128]; char uname_addr[128];
uint32_t ip; char addr[128];
ruletype_t rtype = RT_UNDEFINED; char emsg[1024];
skygw_query_op_t queryop;
DCB* dcb = my_session->session->client; DCB* dcb = my_session->session->client;
RULELIST *rulelist = NULL; RULELIST *rulelist = NULL;
STRLINK* strln = NULL; STRLINK* strln = NULL;
TIMERANGE *times;
sprintf(username,"%s@%s",dcb->user,dcb->remote); sprintf(uname_addr,"%s@%s",dcb->user,dcb->remote);
sprintf(uname,"%s@%%",dcb->user);
sprintf(addr,"%%@%s",dcb->remote);
rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username); time(&time_now);
if(rulelist == NULL){ tm_now = localtime(&time_now);
sprintf(username,"%s@%%",dcb->user);
rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username);
}
if(rulelist == NULL){
sprintf(username,"%%@%s",dcb->remote);
rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username);
}
if(rulelist == NULL){
sprintf(username,"%%@%%");
rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username);
}
if(rulelist == NULL){
accept = true;
if((rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, uname_addr)) == NULL &&
(rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, uname)) == NULL &&
(rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, addr)) == NULL &&
(rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, "%@%")) == NULL)
{
/**
*No rules matched, do default operation.
* TODO: add incremental wildcard search of network addresses
* i.e. iteration from 127.0.0.1 all the way up to 127.%
*/
goto queryresolved; goto queryresolved;
} }
while(rulelist){ while(rulelist){
if(rulelist->rule->active != NULL){
bool rule_active = false;
times = (TIMERANGE*)rulelist->rule->active;
while(times){
if(inside_timerange(times)){
rule_active = true;
break;
}
times = times->next;
}
if(!rule_active){
rulelist = rulelist->next;
continue;
}
}
switch(rulelist->rule->type){ switch(rulelist->rule->type){
case RT_UNDEFINED: case RT_UNDEFINED:
accept = true; break;
case RT_PERMISSION:
if(!rulelist->rule->allow){
accept = false;
msg = strdup("Permission denied.");
goto queryresolved; goto queryresolved;
}else{
break;
}
break;
case RT_COLUMN: case RT_COLUMN:
@ -1065,8 +1092,16 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
while(strln){ while(strln){
if(strstr(where,strln->value)){ if(strstr(where,strln->value)){
accept = false;
accept = rulelist->rule->allow;
if(!rulelist->rule->allow){
sprintf(emsg,"Permission denied to column '%s'.",strln->value);
msg = strdup(emsg);
goto queryresolved; goto queryresolved;
}else{
break;
}
} }
strln = strln->next; strln = strln->next;
} }
@ -1075,141 +1110,61 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
} }
break; break;
case RT_TIME: /**Obsolete*/
times = (TIMERANGE*)rulelist->rule->data;
while(times){
if(inside_timerange(times)){
accept = rulelist->rule->allow;
skygw_log_write(LOGFILE_TRACE, "Firewall: Query entered at %s, rule %s activated.",asctime(tm_now),rulelist->rule->name);
if(!rulelist->rule->allow){
goto queryresolved;
}else{
break;
}
break;
}
times = times->next;
}
break;
case RT_WILDCARD:
if(modutil_is_SQL(queue)){
if(!query_is_parsed(queue)){
parse_query(queue);
}
if(skygw_is_real_query(queue)){
where = skygw_get_affected_fields(queue);
if(where != NULL){
if(strchr(where,'*')){
accept = rulelist->rule->allow;
if(!rulelist->rule->allow){
msg = strdup("Usage of wildcard denied.");
goto queryresolved;
}
}
}
}
}
break;
} }
rulelist = rulelist->next; rulelist = rulelist->next;
} }
if(rulelist == NULL){
accept = true;
goto queryresolved;
}
time(&time_now);
tm_now = localtime(&time_now);
memcpy(&tm_before,tm_now,sizeof(struct tm));
memcpy(&tm_after,tm_now,sizeof(struct tm));
rtype = (ruletype_t)hashtable_fetch(my_instance->htable, dcb->user);
if(rtype == RT_USER){
match = true;
accept = my_instance->whitelist_users;
skygw_log_write(LOGFILE_TRACE, "Firewall: %s@%s was %s.",
dcb->user, dcb->remote,
(my_instance->whitelist_users ?
"allowed":"denied"));
}
if(!match){
ip = strtoip(dcb->remote);
while(ipranges){
if(ip >= ipranges->ip && ip <= ipranges->ip + ipranges->mask){
match = true;
accept = my_instance->whitelist_networks;
skygw_log_write(LOGFILE_TRACE, "Firewall: %s@%s was %s.",
dcb->user,dcb->remote,(my_instance->whitelist_networks ? "allowed":"denied"));
break;
}
ipranges = ipranges->next;
}
}
while(times){
tm_before.tm_sec = times->start.tm_sec;
tm_before.tm_min = times->start.tm_min;
tm_before.tm_hour = times->start.tm_hour;
tm_after.tm_sec = times->end.tm_sec;
tm_after.tm_min = times->end.tm_min;
tm_after.tm_hour = times->end.tm_hour;
time_t before = mktime(&tm_before);
time_t after = mktime(&tm_after);
time_t now = mktime(tm_now);
double to_before = difftime(now,before);
double to_after = difftime(now,after);
/**Inside time range*/
if(to_before > 0.0 && to_after < 0.0){
match = true;
accept = my_instance->whitelist_times;
skygw_log_write(LOGFILE_TRACE, "Firewall: Query entered during restricted time: %s.",asctime(tm_now));
break;
}
times = times->next;
}
if(modutil_is_SQL(queue)){
if(!query_is_parsed(queue)){
parse_query(queue);
}
if(skygw_is_real_query(queue)){
match = false;
if(!skygw_query_has_clause(queue)){
queryop = query_classifier_get_operation(queue);
/**
*TODO: Add column name requirement in addition to query type, match rules against users
*/
if(my_instance->require_where[ALL] ||
(my_instance->require_where[SELECT] && queryop == QUERY_OP_SELECT) ||
(my_instance->require_where[UPDATE] && queryop == QUERY_OP_UPDATE) ||
(my_instance->require_where[INSERT] && queryop == QUERY_OP_INSERT) ||
(my_instance->require_where[DELETE] && queryop == QUERY_OP_DELETE)){
match = true;
accept = false;
skygw_log_write(LOGFILE_TRACE, "Firewall: query does not have a where clause or a having clause, denying it: %.*s",GWBUF_LENGTH(queue) - 5,(char*)(queue->start + 5));
}
}
if(!match){
where = skygw_get_affected_fields(queue);
if(my_instance->deny_wildcard &&
where && strchr(where,'*') != NULL)
{
match = true;
accept = false;
skygw_log_write(LOGFILE_TRACE, "Firewall: query contains wildcard, denying it: %.*s",GWBUF_LENGTH(queue),(char*)(queue->start + 5));
}
else if(where)
{
char* tok = strtok(where," ");
while(tok){
rtype = (ruletype_t)hashtable_fetch(my_instance->htable, tok);
if(rtype == RT_COLUMN){
match = true;
accept = false;
skygw_log_write(LOGFILE_TRACE, "Firewall: query contains a forbidden column %s, denying it: %.*s",tok,GWBUF_LENGTH(queue),(char*)(queue->start + 5));
}
tok = strtok(NULL," ");
}
}
free(where);
}
}
}
/**If no rules matched, do the default operation. (allow by default)*/
if(!match){
accept = my_instance->def_op;
}
queryresolved: queryresolved:
if(accept){ if(accept){
@ -1219,7 +1174,10 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
}else{ }else{
gwbuf_free(queue); gwbuf_free(queue);
GWBUF* forward = gen_dummy_error(my_session); GWBUF* forward = gen_dummy_error(my_session,msg);
if(msg){
free(msg);
}
return dcb->func.write(dcb,forward); return dcb->func.write(dcb,forward);
} }
} }