MXS-1346: Add DbfwSession method implementations
Added the implementation of the DbfwSession methods.
This commit is contained in:
@ -1236,30 +1236,226 @@ setDownstream(MXS_FILTER *instance, MXS_FILTER_SESSION *session, MXS_DOWNSTREAM
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a dummy error packet for the client with a custom message.
|
* Retrieve the user specific data for this session
|
||||||
* @param session The FW_SESSION object
|
*
|
||||||
* @param msg Custom error message for the packet.
|
* @param users Map containing the user data
|
||||||
* @return The dummy packet or NULL if an error occurred
|
* @param name Username
|
||||||
|
* @param remote Remove network address
|
||||||
|
* @return The user data or NULL if it was not found
|
||||||
*/
|
*/
|
||||||
GWBUF* gen_dummy_error(FW_SESSION* session, char* msg)
|
static SUser find_user_data(const UserMap& users, std::string name, std::string remote)
|
||||||
{
|
{
|
||||||
ss_dassert(session && session->session && session->session->client_dcb);
|
char nameaddr[name.length() + remote.length() + 2];
|
||||||
DCB* dcb = session->session->client_dcb;
|
snprintf(nameaddr, sizeof(nameaddr), "%s@%s", name.c_str(), remote.c_str());
|
||||||
const char* db = mxs_mysql_get_current_db(session->session);
|
UserMap::const_iterator it = users.find(nameaddr);
|
||||||
|
|
||||||
|
if (it == users.end())
|
||||||
|
{
|
||||||
|
char *ip_start = strchr(nameaddr, '@') + 1;
|
||||||
|
while (it == users.end() && next_ip_class(ip_start))
|
||||||
|
{
|
||||||
|
it = users.find(nameaddr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it == users.end())
|
||||||
|
{
|
||||||
|
snprintf(nameaddr, sizeof(nameaddr), "%%@%s", remote.c_str());
|
||||||
|
ip_start = strchr(nameaddr, '@') + 1;
|
||||||
|
|
||||||
|
while (it == users.end() && next_ip_class(ip_start))
|
||||||
|
{
|
||||||
|
it = users.find(nameaddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return it != users.end() ? it->second : SUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool command_is_mandatory(const GWBUF *buffer)
|
||||||
|
{
|
||||||
|
switch (MYSQL_GET_COMMAND((uint8_t*)GWBUF_DATA(buffer)))
|
||||||
|
{
|
||||||
|
case MYSQL_COM_QUIT:
|
||||||
|
case MYSQL_COM_PING:
|
||||||
|
case MYSQL_COM_CHANGE_USER:
|
||||||
|
case MYSQL_COM_SET_OPTION:
|
||||||
|
case MYSQL_COM_FIELD_LIST:
|
||||||
|
case MYSQL_COM_PROCESS_KILL:
|
||||||
|
case MYSQL_COM_PROCESS_INFO:
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string get_sql(GWBUF* buffer)
|
||||||
|
{
|
||||||
|
char *sql;
|
||||||
|
int len;
|
||||||
|
modutil_extract_SQL(buffer, &sql, &len);
|
||||||
|
len = MXS_MIN(len, FW_MAX_SQL_LEN);
|
||||||
|
return std::string(sql, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
DbfwSession::DbfwSession(Dbfw* instance, MXS_SESSION* session):
|
||||||
|
query_speed(NULL),
|
||||||
|
instance(instance),
|
||||||
|
session(session)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
DbfwSession::~DbfwSession()
|
||||||
|
{
|
||||||
|
delete query_speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DbfwSession::set_error(std::string error)
|
||||||
|
{
|
||||||
|
m_error = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DbfwSession::get_error()const
|
||||||
|
{
|
||||||
|
return m_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DbfwSession::clear_error()
|
||||||
|
{
|
||||||
|
m_error.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DbfwSession::user() const
|
||||||
|
{
|
||||||
|
return session->client_dcb->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DbfwSession::remote() const
|
||||||
|
{
|
||||||
|
return session->client_dcb->remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
int DbfwSession::send_error()
|
||||||
|
{
|
||||||
|
ss_dassert(session && session->client_dcb);
|
||||||
|
DCB* dcb = session->client_dcb;
|
||||||
|
const char* db = mxs_mysql_get_current_db(session);
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << "Access denied for user '" << dcb->user << "'@'" << dcb->remote << "'";
|
ss << "Access denied for user '" << user() << "'@'" << remote() << "'";
|
||||||
|
|
||||||
if (db[0])
|
if (db[0])
|
||||||
{
|
{
|
||||||
ss << " to database '" << db << "'";
|
ss << " to database '" << db << "'";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg)
|
if (m_error.length())
|
||||||
{
|
{
|
||||||
ss << ": " << msg;
|
ss << ": " << m_error;
|
||||||
|
clear_error();
|
||||||
}
|
}
|
||||||
|
|
||||||
return modutil_create_mysql_err_msg(1, 0, 1141, "HY000", ss.str().c_str());
|
return dcb->func.write(dcb, modutil_create_mysql_err_msg(1, 0, 1141,
|
||||||
|
"HY000", ss.str().c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
int DbfwSession::routeQuery(GWBUF* buffer)
|
||||||
|
{
|
||||||
|
int rval = 0;
|
||||||
|
uint32_t type = 0;
|
||||||
|
|
||||||
|
if (modutil_is_SQL(buffer) || modutil_is_SQL_prepare(buffer))
|
||||||
|
{
|
||||||
|
type = qc_get_type_mask(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (modutil_is_SQL(buffer) && modutil_count_statements(buffer) > 1)
|
||||||
|
{
|
||||||
|
set_error("This filter does not support multi-statements.");
|
||||||
|
rval = send_error();
|
||||||
|
gwbuf_free(buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GWBUF* analyzed_queue = buffer;
|
||||||
|
|
||||||
|
// QUERY_TYPE_PREPARE_STMT need not be handled separately as the
|
||||||
|
// information about statements in COM_STMT_PREPARE packets is
|
||||||
|
// accessed exactly like the information of COM_QUERY packets. However,
|
||||||
|
// with named prepared statements in COM_QUERY packets, we need to take
|
||||||
|
// out the preparable statement and base our decisions on that.
|
||||||
|
|
||||||
|
if (qc_query_is_type(type, QUERY_TYPE_PREPARE_NAMED_STMT))
|
||||||
|
{
|
||||||
|
analyzed_queue = qc_get_preparable_stmt(buffer);
|
||||||
|
ss_dassert(analyzed_queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
SUser suser = find_user_data(this_thread.users, user(), remote());
|
||||||
|
bool query_ok = command_is_mandatory(buffer);
|
||||||
|
|
||||||
|
if (suser)
|
||||||
|
{
|
||||||
|
char* rname = NULL;
|
||||||
|
bool match = suser->match(instance, this, analyzed_queue, &rname);
|
||||||
|
|
||||||
|
switch (instance->action)
|
||||||
|
{
|
||||||
|
case FW_ACTION_ALLOW:
|
||||||
|
query_ok = match;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FW_ACTION_BLOCK:
|
||||||
|
query_ok = !match;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FW_ACTION_IGNORE:
|
||||||
|
query_ok = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
MXS_ERROR("Unknown dbfwfilter action: %d", instance->action);
|
||||||
|
ss_dassert(false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance->log_match != FW_LOG_NONE)
|
||||||
|
{
|
||||||
|
if (match && instance->log_match & FW_LOG_MATCH)
|
||||||
|
{
|
||||||
|
MXS_NOTICE("[%s] Rule '%s' for '%s' matched by %s@%s: %s",
|
||||||
|
session->service->name, rname, suser->name(),
|
||||||
|
user().c_str(), remote().c_str(), get_sql(buffer).c_str());
|
||||||
|
}
|
||||||
|
else if (!match && instance->log_match & FW_LOG_NO_MATCH)
|
||||||
|
{
|
||||||
|
MXS_NOTICE("[%s] Query for '%s' by %s@%s was not matched: %s",
|
||||||
|
session->service->name, suser->name(), user().c_str(),
|
||||||
|
remote().c_str(), get_sql(buffer).c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MXS_FREE(rname);
|
||||||
|
}
|
||||||
|
/** If the instance is in whitelist mode, only users that have a rule
|
||||||
|
* defined for them are allowed */
|
||||||
|
else if (instance->action != FW_ACTION_ALLOW)
|
||||||
|
{
|
||||||
|
query_ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query_ok)
|
||||||
|
{
|
||||||
|
rval = down.routeQuery(down.instance, down.session, buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rval = send_error();
|
||||||
|
gwbuf_free(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1434,68 +1630,13 @@ bool rule_matches(Dbfw* my_instance,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MXS_FREE(my_session->errmsg);
|
my_session->set_error(msg);
|
||||||
my_session->errmsg = msg;
|
MXS_FREE(msg);
|
||||||
|
|
||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
static bool update_rules(Dbfw* my_instance)
|
||||||
* Retrieve the user specific data for this session
|
|
||||||
*
|
|
||||||
* @param users Map containing the user data
|
|
||||||
* @param name Username
|
|
||||||
* @param remote Remove network address
|
|
||||||
* @return The user data or NULL if it was not found
|
|
||||||
*/
|
|
||||||
SUser find_user_data(const UserMap& users, const char *name, const char *remote)
|
|
||||||
{
|
|
||||||
char nameaddr[strlen(name) + strlen(remote) + 2];
|
|
||||||
snprintf(nameaddr, sizeof(nameaddr), "%s@%s", name, remote);
|
|
||||||
UserMap::const_iterator it = users.find(nameaddr);
|
|
||||||
|
|
||||||
if (it == users.end())
|
|
||||||
{
|
|
||||||
char *ip_start = strchr(nameaddr, '@') + 1;
|
|
||||||
while (it == users.end() && next_ip_class(ip_start))
|
|
||||||
{
|
|
||||||
it = users.find(nameaddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (it == users.end())
|
|
||||||
{
|
|
||||||
snprintf(nameaddr, sizeof(nameaddr), "%%@%s", remote);
|
|
||||||
ip_start = strchr(nameaddr, '@') + 1;
|
|
||||||
|
|
||||||
while (it == users.end() && next_ip_class(ip_start))
|
|
||||||
{
|
|
||||||
it = users.find(nameaddr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return it != users.end() ? it->second : SUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool command_is_mandatory(const GWBUF *buffer)
|
|
||||||
{
|
|
||||||
switch (MYSQL_GET_COMMAND((uint8_t*)GWBUF_DATA(buffer)))
|
|
||||||
{
|
|
||||||
case MYSQL_COM_QUIT:
|
|
||||||
case MYSQL_COM_PING:
|
|
||||||
case MYSQL_COM_CHANGE_USER:
|
|
||||||
case MYSQL_COM_SET_OPTION:
|
|
||||||
case MYSQL_COM_FIELD_LIST:
|
|
||||||
case MYSQL_COM_PROCESS_KILL:
|
|
||||||
case MYSQL_COM_PROCESS_INFO:
|
|
||||||
return true;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool update_rules(FW_INSTANCE* my_instance)
|
|
||||||
{
|
{
|
||||||
bool rval = true;
|
bool rval = true;
|
||||||
int rule_version = my_instance->rule_version;
|
int rule_version = my_instance->rule_version;
|
||||||
@ -1526,131 +1667,15 @@ static bool update_rules(FW_INSTANCE* my_instance)
|
|||||||
static int
|
static int
|
||||||
routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue)
|
routeQuery(MXS_FILTER *instance, MXS_FILTER_SESSION *session, GWBUF *queue)
|
||||||
{
|
{
|
||||||
FW_INSTANCE *my_instance = (FW_INSTANCE *) instance;
|
Dbfw *my_instance = (Dbfw *) instance;
|
||||||
|
|
||||||
if (!update_rules(my_instance))
|
if (!update_rules(my_instance))
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
FW_SESSION *my_session = (FW_SESSION *) session;
|
DbfwSession *my_session = (DbfwSession *) session;
|
||||||
DCB *dcb = my_session->session->client_dcb;
|
return my_session->routeQuery(queue);
|
||||||
int rval = 0;
|
|
||||||
ss_dassert(dcb && dcb->session);
|
|
||||||
uint32_t type = 0;
|
|
||||||
|
|
||||||
if (modutil_is_SQL(queue) || modutil_is_SQL_prepare(queue))
|
|
||||||
{
|
|
||||||
type = qc_get_type_mask(queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modutil_is_SQL(queue) && modutil_count_statements(queue) > 1)
|
|
||||||
{
|
|
||||||
GWBUF* err = gen_dummy_error(my_session, (char*)"This filter does not support "
|
|
||||||
"multi-statements.");
|
|
||||||
gwbuf_free(queue);
|
|
||||||
MXS_FREE(my_session->errmsg);
|
|
||||||
my_session->errmsg = NULL;
|
|
||||||
rval = dcb->func.write(dcb, err);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GWBUF* analyzed_queue = queue;
|
|
||||||
|
|
||||||
// QUERY_TYPE_PREPARE_STMT need not be handled separately as the
|
|
||||||
// information about statements in COM_STMT_PREPARE packets is
|
|
||||||
// accessed exactly like the information of COM_QUERY packets. However,
|
|
||||||
// with named prepared statements in COM_QUERY packets, we need to take
|
|
||||||
// out the preparable statement and base our decisions on that.
|
|
||||||
|
|
||||||
if (qc_query_is_type(type, QUERY_TYPE_PREPARE_NAMED_STMT))
|
|
||||||
{
|
|
||||||
analyzed_queue = qc_get_preparable_stmt(queue);
|
|
||||||
ss_dassert(analyzed_queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
SUser user = find_user_data(this_thread.users, dcb->user, dcb->remote);
|
|
||||||
bool query_ok = command_is_mandatory(queue);
|
|
||||||
|
|
||||||
if (user)
|
|
||||||
{
|
|
||||||
char* rname = NULL;
|
|
||||||
bool match = user->match(my_instance, my_session, analyzed_queue, &rname);
|
|
||||||
|
|
||||||
switch (my_instance->action)
|
|
||||||
{
|
|
||||||
case FW_ACTION_ALLOW:
|
|
||||||
if (match)
|
|
||||||
{
|
|
||||||
query_ok = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FW_ACTION_BLOCK:
|
|
||||||
if (!match)
|
|
||||||
{
|
|
||||||
query_ok = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case FW_ACTION_IGNORE:
|
|
||||||
query_ok = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
MXS_ERROR("Unknown dbfwfilter action: %d", my_instance->action);
|
|
||||||
ss_dassert(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (my_instance->log_match != FW_LOG_NONE)
|
|
||||||
{
|
|
||||||
char *sql;
|
|
||||||
int len;
|
|
||||||
if (modutil_extract_SQL(analyzed_queue, &sql, &len))
|
|
||||||
{
|
|
||||||
len = MXS_MIN(len, FW_MAX_SQL_LEN);
|
|
||||||
if (match && my_instance->log_match & FW_LOG_MATCH)
|
|
||||||
{
|
|
||||||
ss_dassert(rname);
|
|
||||||
MXS_NOTICE("[%s] Rule '%s' for '%s' matched by %s@%s: %.*s",
|
|
||||||
dcb->service->name, rname, user->name(),
|
|
||||||
dcb->user, dcb->remote, len, sql);
|
|
||||||
}
|
|
||||||
else if (!match && my_instance->log_match & FW_LOG_NO_MATCH)
|
|
||||||
{
|
|
||||||
MXS_NOTICE("[%s] Query for '%s' by %s@%s was not matched: %.*s",
|
|
||||||
dcb->service->name, user->name(), dcb->user,
|
|
||||||
dcb->remote, len, sql);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MXS_FREE(rname);
|
|
||||||
}
|
|
||||||
/** If the instance is in whitelist mode, only users that have a rule
|
|
||||||
* defined for them are allowed */
|
|
||||||
else if (my_instance->action != FW_ACTION_ALLOW)
|
|
||||||
{
|
|
||||||
query_ok = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query_ok)
|
|
||||||
{
|
|
||||||
rval = my_session->down.routeQuery(my_session->down.instance,
|
|
||||||
my_session->down.session, queue);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GWBUF* forward = gen_dummy_error(my_session, my_session->errmsg);
|
|
||||||
gwbuf_free(queue);
|
|
||||||
MXS_FREE(my_session->errmsg);
|
|
||||||
my_session->errmsg = NULL;
|
|
||||||
rval = dcb->func.write(dcb, forward);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return rval;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user