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 {
RT_UNDEFINED,
RT_USER,
RT_COLUMN,
RT_TIME,
RT_PERMISSION,
RT_WILDCARD
}ruletype_t;
@ -140,6 +140,8 @@ typedef struct rule_t{
void* data;
char* name;
ruletype_t type;
bool allow;
TIMERANGE* active;
}RULE;
/**
@ -490,7 +492,6 @@ TIMERANGE* parse_time(char* str, FW_INSTANCE* instance)
assert(str != NULL && instance != NULL);
tr = (TIMERANGE*)malloc(sizeof(TIMERANGE));
if(tr == NULL){
@ -520,6 +521,10 @@ TIMERANGE* parse_time(char* str, FW_INSTANCE* instance)
CHK_TIMES(tmptr);
if(*ptr == '\0'){
return tr;
}
idest = intbuffer;
tmptr = &tr->end;
}
@ -617,7 +622,7 @@ void add_users(char* rule, FW_INSTANCE* instance)
{
assert(rule != NULL && instance != NULL);
STRLINK* link = malloc(sizeof(STRLINK));
STRLINK* link = calloc(1,sizeof(STRLINK));
link->next = instance->userstrings;
link->value = strdup(rule);
instance->userstrings = link;
@ -687,7 +692,6 @@ void parse_rule(char* rule, FW_INSTANCE* instance)
assert(rule != NULL && instance != NULL);
char *rulecpy = strdup(rule);
char *ptr = rule;
char *tok = strtok(rulecpy," ,");
bool allow,deny,mode;
RULE* ruledef = NULL;
@ -702,11 +706,10 @@ void parse_rule(char* rule, FW_INSTANCE* instance)
RULELIST* rlist = NULL;
ruledef = (RULE*)malloc(sizeof(RULE));
rlist = (RULELIST*)malloc(sizeof(RULELIST));
ruledef = (RULE*)calloc(1,sizeof(RULE));
rlist = (RULELIST*)calloc(1,sizeof(RULELIST));
ruledef->name = strdup(tok);
ruledef->type = RT_UNDEFINED;
ruledef->data = NULL;
rlist->rule = ruledef;
rlist->next = instance->rules;
instance->rules = rlist;
@ -720,27 +723,25 @@ void parse_rule(char* rule, FW_INSTANCE* instance)
tok = strtok(NULL, " ,");
/**IP range rules*/
if((allow = (strcmp(tok,"allow") == 0)) ||
(deny = (strcmp(tok,"deny") == 0))){
mode = allow ? true:false;
ruledef->allow = mode;
ruledef->type = RT_PERMISSION;
tok = strtok(NULL, " ,");
bool is_user = false, is_column = false, is_time = false;
if(strcmp(tok,"wildcard") == 0)
{
ruledef->type = RT_WILDCARD;
ruledef->data = (void*)mode;
}
else if(strcmp(tok,"columns") == 0)
{
STRLINK *tail = NULL,*current;
ruledef->type = RT_COLUMN;
tok = strtok(NULL, " ,");
while(tok){
while(tok && strcmp(tok,"times") != 0){
current = malloc(sizeof(STRLINK));
current->value = strdup(tok);
current->next = tail;
@ -755,78 +756,21 @@ void parse_rule(char* rule, FW_INSTANCE* instance)
{
tok = strtok(NULL, " ,");
ruledef->type = RT_TIME;
TIMERANGE *tr = parse_time(tok,instance);
if(IS_RVRS_TIME(tr)){
TIMERANGE *tmptr = split_reverse_time(tr);
tr = tmptr;
tr = split_reverse_time(tr);
}
ruledef->data = (void*)tr;
ruledef->active = tr;
}
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:
free(rulecpy);
}
/**
@ -862,6 +806,9 @@ createInstance(char **options, FILTER_PARAMETER **params)
for(i = 0;params[i];i++){
if(strstr(params[i]->name,"rule")){
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,13 +896,22 @@ setDownstream(FILTER *instance, void *session, DOWNSTREAM *downstream)
* Generates a dummy error packet for the client.
* @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;
char errmsg[512];
char* errmsg;
DCB* dcb = session->session->client;
MYSQL_session* mysql_session = (MYSQL_session*)session->session->data;
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')
{
@ -966,11 +922,18 @@ GWBUF* gen_dummy_error(FW_SESSION* session)
}else
{
sprintf(errmsg,
"Access denied for user '%s'@'%s' to database '%s' ",
"Access denied for user '%s'@'%s' to database '%s'",
dcb->user,
dcb->remote,
mysql_session->db);
}
if(msg != NULL){
char* ptr = strchr(errmsg,'\0');
sprintf(ptr,": %s",msg);
}
errlen = strlen(errmsg);
pktlen = errlen + 9;
buf = gwbuf_alloc(13 + errlen);
@ -989,6 +952,40 @@ GWBUF* gen_dummy_error(FW_SESSION* session)
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
* 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_INSTANCE *my_instance = (FW_INSTANCE *)instance;
IPRANGE* ipranges = my_instance->networks;
TIMERANGE* times = my_instance->times;
time_t time_now;
struct tm* tm_now;
struct tm tm_before,tm_after;
bool accept = false, match = false;
char *where;
char username[128];
uint32_t ip;
ruletype_t rtype = RT_UNDEFINED;
skygw_query_op_t queryop;
bool accept = my_instance->def_op;
char *where, *msg = NULL;
char uname[128];
char uname_addr[128];
char addr[128];
char emsg[1024];
DCB* dcb = my_session->session->client;
RULELIST* rulelist = NULL;
RULELIST *rulelist = 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);
if(rulelist == NULL){
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);
}
time(&time_now);
tm_now = localtime(&time_now);
if(rulelist == NULL){
sprintf(username,"%%@%%");
rulelist = (RULELIST*)hashtable_fetch(my_instance->htable, username);
}
if(rulelist == NULL){
accept = true;
goto queryresolved;
}
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;
}
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){
case RT_UNDEFINED:
accept = true;
goto queryresolved;
break;
case RT_PERMISSION:
if(!rulelist->rule->allow){
accept = false;
msg = strdup("Permission denied.");
goto queryresolved;
}else{
break;
}
break;
case RT_COLUMN:
@ -1065,8 +1092,16 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
while(strln){
if(strstr(where,strln->value)){
accept = false;
goto queryresolved;
accept = rulelist->rule->allow;
if(!rulelist->rule->allow){
sprintf(emsg,"Permission denied to column '%s'.",strln->value);
msg = strdup(emsg);
goto queryresolved;
}else{
break;
}
}
strln = strln->next;
}
@ -1074,142 +1109,62 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
}
}
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;
}
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:
if(accept){
@ -1219,7 +1174,10 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
}else{
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);
}
}