Merge branch 'develop' into firewall
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -30,6 +30,7 @@ depend.mk
|
|||||||
*~
|
*~
|
||||||
*#
|
*#
|
||||||
.#*
|
.#*
|
||||||
|
._*
|
||||||
|
|
||||||
# Vi swap files
|
# Vi swap files
|
||||||
.*.swp
|
.*.swp
|
||||||
|
999
Documentation/experimental/ConfigurationGuide.asciidoc
Normal file
999
Documentation/experimental/ConfigurationGuide.asciidoc
Normal file
@ -0,0 +1,999 @@
|
|||||||
|
|
||||||
|
|
||||||
|
MaxScale
|
||||||
|
|
||||||
|
Configuration & Usage Scenarios
|
||||||
|
|
||||||
|
|
||||||
|
Mark Riddoch
|
||||||
|
|
||||||
|
Last Updated: 2nd July 2014
|
||||||
|
|
||||||
|
|
||||||
|
== Contents
|
||||||
|
|
||||||
|
Contents
|
||||||
|
Document History
|
||||||
|
Introduction
|
||||||
|
Terms
|
||||||
|
Configuration
|
||||||
|
Global Settings
|
||||||
|
Threads
|
||||||
|
Service
|
||||||
|
Router
|
||||||
|
Filters
|
||||||
|
Servers
|
||||||
|
User
|
||||||
|
Passwd
|
||||||
|
weightby
|
||||||
|
Server
|
||||||
|
Address
|
||||||
|
Port
|
||||||
|
Protocol
|
||||||
|
Monitoruser
|
||||||
|
MonitorPw
|
||||||
|
Listener
|
||||||
|
Service
|
||||||
|
Protocol
|
||||||
|
Address
|
||||||
|
Port
|
||||||
|
Filter
|
||||||
|
Module
|
||||||
|
Options
|
||||||
|
Other Parameters
|
||||||
|
Monitor
|
||||||
|
Module
|
||||||
|
Servers
|
||||||
|
User
|
||||||
|
Passwd
|
||||||
|
Protocol Modules
|
||||||
|
MySQLClient
|
||||||
|
MySQLBackend
|
||||||
|
Telnetd
|
||||||
|
maxscaled
|
||||||
|
HTTPD
|
||||||
|
Router Modules
|
||||||
|
Connection Based Routing
|
||||||
|
Statement Based Routing
|
||||||
|
Available Routing Modules
|
||||||
|
Readconnroute
|
||||||
|
Master/Slave Replication Setup
|
||||||
|
Galera Cluster Configuration
|
||||||
|
Readwritesplit
|
||||||
|
Master/Slave Replication Setup
|
||||||
|
Debugcli
|
||||||
|
Debug CLI Configuration
|
||||||
|
CLI
|
||||||
|
CLI Configuration
|
||||||
|
Monitor Modules
|
||||||
|
Mysqlmon
|
||||||
|
Galeramon
|
||||||
|
Filter Modules
|
||||||
|
Statement Counting Filter
|
||||||
|
Query Log All Filter
|
||||||
|
Regular Expression Filter
|
||||||
|
Tee Filter
|
||||||
|
Encrypting Passwords
|
||||||
|
Creating Encrypted Passwords
|
||||||
|
Configuration Updates
|
||||||
|
Limitations
|
||||||
|
Authentication
|
||||||
|
Wildcard Hosts
|
||||||
|
Limitations
|
||||||
|
Error Reporting
|
||||||
|
|
||||||
|
|
||||||
|
==
|
||||||
|
==
|
||||||
|
== Document History
|
||||||
|
|
||||||
|
|===
|
||||||
|
|*Date*|*Change*|*Who*
|
||||||
|
|
||||||
|
|21st July 2013|Initial version|Mark Riddoch
|
||||||
|
|23rd July 2013|Addition of default user and password for a monitor and discussion of monitor user requirements|Mark Riddoch
|
||||||
|
|13th November 2013|state for Galera Monitor is “synced”|Massimiliano Pinto
|
||||||
|
|2nd December 2013|Updated the description of the command line arguments to match the code updates.|Mark Riddoch
|
||||||
|
|6th February 2014|Added “enable_root_user” as a service parameter|Massimiliano Pinto
|
||||||
|
|7th February 2014|Addition of bind address information|Mark Riddoch
|
||||||
|
|3rd March 2014|MySQL authentication with hostnames|Massimiliano Pinto
|
||||||
|
|3rd March 2014|Addition of section that describes authentication requirements and the rules for creating user credentials|Mark Riddoch
|
||||||
|
|28th March 2014|Unix socket support|Massimiliano Pinto
|
||||||
|
|8th May 2014|Added “version_string” parameter in service|Massimiliano Pinto
|
||||||
|
|29th May 2014|Added troubleshooting section|Massimiliano Pinto
|
||||||
|
|2nd June 2014|Correction of some typos, clarification of the meaning of session modification statements and the default user for the CLI.|Mark Riddoch
|
||||||
|
|4th June 2014|Addition of “monitor_interval” for monitors|Massimiliano Pinto
|
||||||
|
|6th June 2014|Addition of filters sections|Mark Riddoch
|
||||||
|
|27th June 2014|Addition of server weighting, the configuration for the maxadmin client|Mark Riddoch
|
||||||
|
|2nd July 2014|Addition of new readwritesplit router options with description and examples.|Vilho Raatikka
|
||||||
|
|===
|
||||||
|
==
|
||||||
|
== Introduction
|
||||||
|
|
||||||
|
The purpose of this document is to describe how to configure MaxScale and to discuss some possible usage scenarios for MaxScale. MaxScale is designed with flexibility in mind, and consists of an event processing core with various support functions and plugin modules that tailor the behaviour of the MaxScale itself.
|
||||||
|
|
||||||
|
=== Terms
|
||||||
|
|
||||||
|
|===
|
||||||
|
|*Term*|*Description*
|
||||||
|
|
||||||
|
|service|A service represents a set of databases with a specific access mechanism that is offered to clients of MaxScale. The access mechanism defines the algorithm that MaxScale will use to direct particular requests to the individual databases.
|
||||||
|
|server|A server represents an individual database server to which a client can be connected via MaxScale.
|
||||||
|
|router|A router is a module within MaxScale that will route client requests to the various database servers which MaxScale provides a service interface to.
|
||||||
|
|connection routing|Connection routing is a method of handling requests in which MaxScale will accept connections from a client and route data on that connection to a single database using a single connection. Connection based routing will not examine individual quests on a connection and it will not move that connection once it is established.
|
||||||
|
|statement routing|Statement routing is a method of handling requests in which each request within a connection will be handled individually. Requests may be sent to one or more servers and connections may be dynamically added or removed from the session.
|
||||||
|
|protocol|A protocol is a module of software that is used to communicate with another software entity within the system. MaxScale supports the dynamic loading of protocol modules to allow for increased flexibility.
|
||||||
|
|module|A module is a separate code entity that may be loaded dynamically into MaxScale to increase the available functionality. Modules are implemented as run-time loadable shared objects.
|
||||||
|
|monitor|A monitor is a module that can be executed within MaxScale to monitor the state of a set of database. The use of an internal monitor is optional, monitoring may be performed externally to MaxScale.
|
||||||
|
|listener|A listener is the network endpoint that is used to listen for connections to MaxScale from the client applications. A listener is associated to a single service, however a service may have many listeners.
|
||||||
|
|connection failover|When a connection currently being used between MaxScale and the database server fails a replacement will be automatically created to another server by MaxScale without client intervention
|
||||||
|
|backend database|A term used to refer to a database that sits behind MaxScale and is accessed by applications via MaxScale.
|
||||||
|
|filter|A module that can be placed between the client and the MaxScale router module. All client data passes through the filter module and may be examined or modified by the filter modules.
|
||||||
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
==
|
||||||
|
==
|
||||||
|
== Configuration
|
||||||
|
|
||||||
|
The MaxScale configuration is read from a file which can be located in a number of placing, MaxScale will search for the configuration file in a number of locations.
|
||||||
|
|
||||||
|
. If the environment variable MAXSCALE_HOME is set then MaxScale will look for a configuration file called MaxScale.cnf in the directory $MAXSCALE_HOME/etc
|
||||||
|
. If MAXSCALE_HOME is not set or the configuration file is not in the location above MaxScale will look for a file in /etc/MaxScale.cnf
|
||||||
|
|
||||||
|
Alternatively MaxScale can be started with the -c flag and the path of the MaxScale home directory tree.
|
||||||
|
|
||||||
|
An explicit path to a configuration file can be passed by using the -f option to MaxScale.
|
||||||
|
|
||||||
|
The configuration file itself is based on the “ini” file format and consists of various sections that are used to build the configuration, these sections define services, servers, listeners, monitors and global settings.
|
||||||
|
|
||||||
|
=== Global Settings
|
||||||
|
The global settings, in a section named [MaxScale], allow various parameters that affect MaxScale as a whole to be tuned. Currently the only setting that is supported is the number of threads to use to handle the network traffic. MaxScale will also accept the section name of [gateway] for global settings. This is for backward compatibility with versions prior to the naming of MaxScale.
|
||||||
|
|
||||||
|
==== Threads
|
||||||
|
To control the number of threads that poll for network traffic set the parameter threads to a number. It is recommended that you start with a single thread and add more as you find the performance is not satisfactory. MaxScale is implemented to be very thread efficient, so a small number of threads is usually adequate to support reasonably heavy workloads. Adding more threads may not improve performance and can consume resources needlessly.
|
||||||
|
|
||||||
|
----
|
||||||
|
# Valid options are:
|
||||||
|
# threads=<number of epoll threads>
|
||||||
|
[MaxScale]
|
||||||
|
threads=1
|
||||||
|
----
|
||||||
|
|
||||||
|
It should be noted that additional threads will be created to execute other internal services within MaxScale, this setting is merely used to configure the number of threads that will be used to manage the user connections.
|
||||||
|
|
||||||
|
=== Service
|
||||||
|
A service represents the database service that MaxScale offers to the clients. In general a service consists of a set of backend database servers and a routing algorithm that determines how MaxScale decides to send statements or route connections to those backend servers.
|
||||||
|
|
||||||
|
A service may be considered as a virtual database server that MaxScale makes available to its clients.
|
||||||
|
|
||||||
|
Several different services may be defined using the same set of backend servers. For example a connection based routing service might be used by clients that already performed internal read/write splitting, whilst a different statement based router may be used by clients that are not written with this functionality in place. Both sets of applications could access the same data in the same databases.
|
||||||
|
|
||||||
|
A service is identified by a service name, which is the name of the configuration file section and a type parameter of service
|
||||||
|
|
||||||
|
----
|
||||||
|
[Test Service]
|
||||||
|
type=service
|
||||||
|
----
|
||||||
|
|
||||||
|
In order for MaxScale to forward any requests it must have at least one service defined within the configuration file. The definition of a service alone is not enough to allow MaxScale to forward requests however, the service is merely present to link together the other configuration elements.
|
||||||
|
|
||||||
|
==== Router
|
||||||
|
The router parameter of a service defines the name of the router module that will be used to implement the routing algorithm between the client of MaxScale and the backend databases. Additionally routers may also be passed a comma separated list of options that are used to control the behaviour of the routing algorithm. The two parameters that control the routing choice are router and router_options. The router options are specific to a particular router and are used to modify the behaviour of the router. The read connection router can be passed options of master, slave or synced, an example of configuring a service to use this router and limiting the choice of servers to those in slave state would be as follows.
|
||||||
|
|
||||||
|
----
|
||||||
|
router=readconnroute
|
||||||
|
router_options=slave
|
||||||
|
----
|
||||||
|
|
||||||
|
To change the router to connect on to servers in the master state as well as slave servers, the router options can be modified to include the master state.
|
||||||
|
|
||||||
|
----
|
||||||
|
router=readconnroute
|
||||||
|
router_options=master,slave
|
||||||
|
----
|
||||||
|
|
||||||
|
A more complete description of router options and what is available for a given router is included with the documentation of the router itself.
|
||||||
|
|
||||||
|
==== Filters
|
||||||
|
The filters option allow a set of filters to be defined for a service; requests from the client are passed through these filters before being sent to the router for dispatch to the backend server. The filters parameter takes one or more filter names, as defined within the filter definition section of the configuration file. Multiple filters are separated using the | character.
|
||||||
|
|
||||||
|
+filters=counter | QLA+
|
||||||
|
|
||||||
|
The requests pass through the filters from left to right in the order defined in the configuration parameter.
|
||||||
|
|
||||||
|
==== Servers
|
||||||
|
The servers parameter in a service definition provides a comma separated list of the backend servers that comprise the service. The server names are those used in the name section of a block with a type parameter of server (see below).
|
||||||
|
|
||||||
|
+servers=server1,server2,server3+
|
||||||
|
|
||||||
|
==== User
|
||||||
|
The user parameter, along with the passwd parameter are used to define the credentials used to connect to the backend servers to extract the list of database users from the backend database that is used for the client authentication.
|
||||||
|
|
||||||
|
----
|
||||||
|
user=maxscale
|
||||||
|
passwd=Mhu87p2D
|
||||||
|
----
|
||||||
|
|
||||||
|
Authentication of incoming connections is performed by MaxScale itself rather than by the database server to which the client is connected. The client will authenticate itself with MaxScale, using the username, hostname and password information that MaxScale has extracted from the backend database servers. For a detailed discussion of how this impacts the authentication process please see the “Authentication” section below.
|
||||||
|
|
||||||
|
The host matching criteria is restricted to IPv4, IPv6 will be added in a future release.
|
||||||
|
|
||||||
|
Existing user configuration in the backend databases must be checked and may be updated before successful MaxScale authentication:
|
||||||
|
|
||||||
|
|
||||||
|
In order for MaxScale to obtain all the data it must be given a username it can use to connect to the database and retrieve that data. This is the parameter that gives MaxScale the username to use for this purpose.
|
||||||
|
|
||||||
|
The account used must be able to select from the mysql.user table, the following is an example showing how to create this user.
|
||||||
|
|
||||||
|
----
|
||||||
|
MariaDB [mysql]> create user 'maxscale'@'maxscalehost' identified by 'Mhu87p2D';
|
||||||
|
Query OK, 0 rows affected (0.01 sec)
|
||||||
|
|
||||||
|
MariaDB [mysql]> grant SELECT on mysql.user to 'maxscale'@'maxscalehost';
|
||||||
|
----
|
||||||
|
Query OK, 0 rows affected (0.00 sec)
|
||||||
|
|
||||||
|
==== Passwd
|
||||||
|
The auth parameter provides the password information for the above user and may be either a plain text password or it may be an encrypted password. See the section on encrypting passwords for use in the MaxScale.cnf file. This user must be capable of connecting to the backend database and executing the SQL statement “SELECT user, host, password FROM mysql.user”.
|
||||||
|
|
||||||
|
*enable_root_user* +
|
||||||
|
This parameter controls the ability of the root user to connect to MaxScale and hence onwards to the backend servers via MaxScale.
|
||||||
|
|
||||||
|
The default value is 0, disabling the ability of the root user to connect to MaxScale.
|
||||||
|
|
||||||
|
Example for enabling root user: +
|
||||||
|
enable_root_user=1
|
||||||
|
|
||||||
|
Values of “on” or “true” may also be given to enable the root user and “off” or “false” may be given to disable the use of the root user. +
|
||||||
|
+enable_root_user=true+
|
||||||
|
|
||||||
|
*version_string* +
|
||||||
|
This parameter sets a custom version string that is sent in the MySQL Handshake from MaxScale to clients.
|
||||||
|
|
||||||
|
Example: +
|
||||||
|
version_string=5.5.37-MariaDB-RWsplit
|
||||||
|
|
||||||
|
If not set, the default value is the server version of the embedded MySQL/MariaDB library. Example: 5.5.35-MariaDB
|
||||||
|
|
||||||
|
==== weightby
|
||||||
|
The weightby parameter is used in conjunction with server parameters in order to control the load balancing applied in the router in use by the service. This allows varying weights to be applied to each server to create a non-uniform distribution of the load amongst the servers.
|
||||||
|
|
||||||
|
An example of this might be to define a parameter for each server that represents the amount of resource available on the server, we could call this serversize. Every server should then have a serversize parameter set for the server.
|
||||||
|
|
||||||
|
+serversize=10+
|
||||||
|
|
||||||
|
The service would then have the parameter weightby set. If there are 4 servers defined in the service, serverA, serverB, serverC and serverD, with the serversize set as shown in the table below, the connections would balanced using the percentages in this table.
|
||||||
|
|
||||||
|
|===
|
||||||
|
|Server|serversize|% connections
|
||||||
|
|
||||||
|
|serverA|10|18%
|
||||||
|
|serverB|15|27%
|
||||||
|
|serverC|10|18%
|
||||||
|
|serverD|20|36%
|
||||||
|
|===
|
||||||
|
|
||||||
|
=== Server
|
||||||
|
|
||||||
|
Server sections are used to define the backend database servers that can be formed into a service. A server may be a member of one or more services within MaxScale. Servers are identified by a server name which is the section name in the configuration file. Servers have a type parameter of server, plus address port and protocol parameters.
|
||||||
|
|
||||||
|
----
|
||||||
|
[server1]
|
||||||
|
type=server
|
||||||
|
address=127.0.0.1
|
||||||
|
port=3000
|
||||||
|
protocol=MySQLBackend
|
||||||
|
----
|
||||||
|
==== Address
|
||||||
|
The IP address or hostname of the machine running the database server that is being defined. MaxScale will use this address to connect to the backend database server.
|
||||||
|
|
||||||
|
==== Port
|
||||||
|
The port on which the database listens for incoming connections. MaxScale will use this port to connect to the database server.
|
||||||
|
|
||||||
|
==== Protocol
|
||||||
|
The name for the protocol module to use to connect MaxScale to the database. Currently only one backend protocol is supported, the MySQLBackend module.
|
||||||
|
|
||||||
|
==== Monitoruser
|
||||||
|
The monitor has a username and password that is used to connect to all servers for monitoring purposes, this may be overridden by supplying a monitoruser statement for each individual server
|
||||||
|
|
||||||
|
+monitoruser=mymonitoruser+
|
||||||
|
|
||||||
|
==== MonitorPw
|
||||||
|
The monitor has a username and password that is used to connect to all servers for monitoring purposes, this may be overridden by supplying a monpasswd statement for the individual servers
|
||||||
|
|
||||||
|
----
|
||||||
|
monitorpw=mymonitorpasswd
|
||||||
|
|
||||||
|
----
|
||||||
|
The monpasswd parameter may be either a plain text password or it may be an encrypted password. See the section on encrypting passwords for use in the MaxScale.cnf file.
|
||||||
|
|
||||||
|
=== Listener
|
||||||
|
|
||||||
|
The listener defines a port and protocol pair that is used to listen for connections to a service. A service may have multiple listeners associated with it, either to support multiple protocols or multiple ports. As with other elements of the configuration the section name is the listener name and a type parameter is used to identify the section as a listener definition.
|
||||||
|
|
||||||
|
----
|
||||||
|
[Test Listener]
|
||||||
|
type=listener
|
||||||
|
service=Test Service
|
||||||
|
protocol=MySQLClient
|
||||||
|
address=localhost
|
||||||
|
port=4008
|
||||||
|
socket=/tmp/testlistener.sock
|
||||||
|
----
|
||||||
|
==== Service
|
||||||
|
The service to which the listener is associated. This is the name of a service that is defined elsewhere in the configuration file.
|
||||||
|
|
||||||
|
==== Protocol
|
||||||
|
The name of the protocol module that is used for the communication between the client and MaxScale itself.
|
||||||
|
|
||||||
|
==== Address
|
||||||
|
The address option sets the address that will be used to bind the listening socket. The address may be specified as an IP address in ‘dot notation’ or as a hostname. If the address option is not included in the listener definition the listener will bind to all network interfaces.
|
||||||
|
|
||||||
|
==== Port
|
||||||
|
The port to use to listen for incoming connections to MaxScale from the clients. If the port is omitted from the configuration a default port for the protocol will be used.
|
||||||
|
|
||||||
|
*Socket* +
|
||||||
|
The socket option may be included in a listener definition, this configures the listener to use Unix domain sockets to listen for incoming connections. The parameter value given is the name of the socket to use.
|
||||||
|
|
||||||
|
If a socket option and an address option is given then the listener will listen on both the specific IP address and the Unix socket.
|
||||||
|
|
||||||
|
=== Filter
|
||||||
|
Filters provide a means to manipulate or process requests as they pass through MaxScale between the client side protocol and the query router. A filter should be defined in a section with a type of filter.
|
||||||
|
|
||||||
|
----
|
||||||
|
[QLA]
|
||||||
|
type=filter
|
||||||
|
module=qlafilter
|
||||||
|
options=/tmp/QueryLog
|
||||||
|
----
|
||||||
|
|
||||||
|
The section name may then be used in one or more services by using the filters= parameter in the service section. In order to use the above filter for a service called “QLA Service”, an entry of the following form would exist for that service.
|
||||||
|
|
||||||
|
----
|
||||||
|
[QLA Service]
|
||||||
|
type=service
|
||||||
|
router=readconnroute
|
||||||
|
router_options=slave
|
||||||
|
servers=server1,server2,server3,server4
|
||||||
|
user=massi
|
||||||
|
passwd=6628C50E07CCE1F0392EDEEB9D1203F3
|
||||||
|
filters=QLA
|
||||||
|
----
|
||||||
|
|
||||||
|
See the Services section for more details on how to configure the various options of a service.
|
||||||
|
|
||||||
|
==== Module
|
||||||
|
The module parameter defines the name of the loadable module that implements the filter.
|
||||||
|
|
||||||
|
==== Options
|
||||||
|
The options parameter is used to pass options to the filter to control the actions the filter will perform. The values that can be passed differ between filter implementation, the inclusion of an options parameter is optional.
|
||||||
|
|
||||||
|
==== Other Parameters
|
||||||
|
Any other parameters present in the filters section will be passed to the filter to be interpreted by the filter. An example of this is the regexfilter that requires the two parameters match and replace
|
||||||
|
|
||||||
|
----
|
||||||
|
[regex]
|
||||||
|
type=filter
|
||||||
|
module=regexfilter
|
||||||
|
match=form
|
||||||
|
replace=from
|
||||||
|
----
|
||||||
|
|
||||||
|
=== Monitor
|
||||||
|
|
||||||
|
In order for the various router modules to function correctly they require information about the state of the servers that are part of the service they provide. MaxScale has the ability to internally monitor the state of the back-end database servers or that state may be feed into MaxScale from external monitoring systems. If automated monitoring and failover of services is required this is achieved by running a monitor module that is designed for the particular database architecture that is in use.
|
||||||
|
|
||||||
|
Monitors are defined in much the same way as other elements in the configuration file, with the section name being the name of the monitor instance and the type being set to monitor.
|
||||||
|
|
||||||
|
----
|
||||||
|
[MySQL Monitor]
|
||||||
|
type=monitor
|
||||||
|
module=mysqlmon
|
||||||
|
servers=server1,server2,server3
|
||||||
|
user=dbmonitoruser
|
||||||
|
passwd=dbmonitorpwd
|
||||||
|
monitor_interval=8000
|
||||||
|
|
||||||
|
----
|
||||||
|
==== Module
|
||||||
|
The module parameter defines the name of the loadable module that implements the monitor. This module is loaded and executed on a separate thread within MaxScale.
|
||||||
|
|
||||||
|
==== Servers
|
||||||
|
The servers parameter is a comma separated list of server names to monitor, these are the names defined elsewhere in the configuration file. The set of servers monitored by a single monitor need not be the same as the set of servers used within any particular server, a single monitor instance may monitor servers in multiple servers.
|
||||||
|
|
||||||
|
==== User
|
||||||
|
The user parameter defines the username that the monitor will use to connect to the monitored databases. Depending on the monitoring module used this user will require specific privileges in order to determine the state of the nodes, details of those privileges can be found in the sections on each of the monitor modules.
|
||||||
|
|
||||||
|
Individual servers may define override values for the user and password the monitor uses by setting the monuser and monpasswd parameters in the server section.
|
||||||
|
|
||||||
|
==== Passwd
|
||||||
|
The password parameter may be either a plain text password or it may be an encrypted password. See the section on encrypting passwords for use in the MaxScale.cnf file.
|
||||||
|
|
||||||
|
*Monitor_interval* +
|
||||||
|
The monitor_interval parameter sets the sampling interval in milliseconds for each monitor, the default value is 10000 milliseconds.
|
||||||
|
==
|
||||||
|
==
|
||||||
|
== Protocol Modules
|
||||||
|
The protocols supported by MaxScale are implemented as external modules that are loaded dynamically into the MaxScale core. These modules reside in the directory $MAXSCALE_HOME/module, if the environment variable $MAXSCALE_HOME is not set it defaults to /usr/local/skysql/MaxScale. It may also be set by passing the -c option on the MaxScale command line.
|
||||||
|
|
||||||
|
=== MySQLClient
|
||||||
|
|
||||||
|
This is the implementation of the MySQL protocol that is used by clients of MaxScale to connect to MaxScale.
|
||||||
|
|
||||||
|
=== MySQLBackend
|
||||||
|
|
||||||
|
The MySQLBackend protocol module is the implementation of the protocol that MaxScale uses to connect to the backend MySQL, MariaDB and Percona Server databases. This implementation is tailored for the MaxScale to MySQL Database traffic and is not a general purpose implementation of the MySQL protocol.
|
||||||
|
|
||||||
|
=== Telnetd
|
||||||
|
|
||||||
|
The telnetd protocol module is used for connections to MaxScale itself for the purposes of creating interactive user sessions with the MaxScale instance itself. Currently this is used in conjunction with a special router implementation, the debugcli.
|
||||||
|
|
||||||
|
=== maxscaled
|
||||||
|
The protocol used used by the maxadmin client application in order to connect to MaxScale and access the command line interface.
|
||||||
|
|
||||||
|
=== HTTPD
|
||||||
|
|
||||||
|
This protocol module is currently still under development, it provides a means to create HTTP connections to MaxScale for use by web browsers or RESTful API clients.
|
||||||
|
==
|
||||||
|
==
|
||||||
|
== Router Modules
|
||||||
|
The main task of MaxScale is to accept database connections from client applications and route the connections or the statements sent over those connections to the various services supported by MaxScale.
|
||||||
|
|
||||||
|
There are two flavours of routing that MaxScale can perform, connection based routing and statement based routine. These each have their own characteristics and costs associated with them.
|
||||||
|
|
||||||
|
=== Connection Based Routing
|
||||||
|
|
||||||
|
Connection based routing is a mechanism by which MaxScale will, for each incoming connection decide on an appropriate outbound server and will forward all statements to that server without examining the internals of the statement. Once an inbound connection is associated to a particular backend database it will remain connected to that server until the connection is closed or the server fails.
|
||||||
|
|
||||||
|
=== Statement Based Routing
|
||||||
|
|
||||||
|
Statement based routing is somewhat different, the routing modules examine every statement the client sends and determines, on a per statement basis, which of the set of backend servers in the service is best to execute the statement. This gives better dynamic balancing of the load within the cluster but comes at a cost. The query router must understand the statement that is being routing and will typically need to parse the statement in order to achieve this. This parsing within the router adds a significant overhead to the cost of routing and makes this type of router only really suitable for loads in which the gains outweigh this added cost.
|
||||||
|
|
||||||
|
=== Available Routing Modules
|
||||||
|
|
||||||
|
Currently a small number of query routers are available, these are in different stages of completion and offer different facilities.
|
||||||
|
|
||||||
|
==== Readconnroute
|
||||||
|
This is a statement based query router that was originally targeted at environments in which the clients already performed splitting of read and write queries into separate connections.
|
||||||
|
|
||||||
|
Whenever a new connection is received the router will examine the state of all the servers that form part of the service and route the connection to the server with least connections currently that matches the filter constraints given in the router options. This results in a balancing of the active connections, however different connections may have different lifetimes and the connections may become unbalanced when later viewed.
|
||||||
|
|
||||||
|
The readconnroute router can be configured to balance the connections from the clients across all the backend servers that are running, just those backend servers that are currently replication slaves or those that are replication masters when routing to a master slave replication environment. When a Galera cluster environment is in use the servers can be filtered to just the set that are part of the cluster and in the ‘synced’ state. These options are configurable via the router_options that can be set within a service. The router_option strings supported are “master”, “slave” and “synced”.
|
||||||
|
|
||||||
|
===== Master/Slave Replication Setup
|
||||||
|
|
||||||
|
To setup MaxScale to route connections evenly between all the current slave servers in a replication cluster, a service entry of the form shown below is required.
|
||||||
|
|
||||||
|
----
|
||||||
|
[Read Service]
|
||||||
|
type=service
|
||||||
|
router=readconnroute
|
||||||
|
router_options=slave
|
||||||
|
servers=server1,server2,server3,server4
|
||||||
|
user=maxscale
|
||||||
|
auth=thepasswd
|
||||||
|
----
|
||||||
|
|
||||||
|
With the addition of a listener for this service, which defines the port and protocol that MaxScale uses +
|
||||||
|
----
|
||||||
|
[Read Listener]
|
||||||
|
type=listener
|
||||||
|
service=Read Service
|
||||||
|
protocol=MySQLClient
|
||||||
|
port=4006
|
||||||
|
----
|
||||||
|
|
||||||
|
the client can now connect to port 4006 on the host which is running MaxScale. Statements sent using this connection will then be routed to one of the slaves in the server set defined in the Read Service. Exactly which is selected will be determined by balancing the number of connections to each of those whose current state is “slave”.
|
||||||
|
|
||||||
|
Altering the router options to be slave, master would result in the connections being balanced between all the servers within the cluster.
|
||||||
|
|
||||||
|
It is assumed that the client will have a separate connection to the master server, however this can be routed via MaxScale, allowing MaxScale to manage the determination of which server is master. To do this you would add a second service and listener definition for the master server.
|
||||||
|
|
||||||
|
----
|
||||||
|
[Write Service]
|
||||||
|
type=service
|
||||||
|
router=readconnroute
|
||||||
|
router_options=master
|
||||||
|
servers=server1,server2,server3,server4
|
||||||
|
user=maxscale
|
||||||
|
auth=thepasswd
|
||||||
|
|
||||||
|
[Write Listener]
|
||||||
|
type=listener
|
||||||
|
service=Write Service
|
||||||
|
protocol=MySQLClient
|
||||||
|
port=4007
|
||||||
|
----
|
||||||
|
|
||||||
|
This allows the clients to direct write requests to port 4007 and read requests to port 4006 of the MaxScale host without the clients needing to understand the configuration of the Master/Slave replication cluster.
|
||||||
|
|
||||||
|
Connections to port 4007 would automatically be directed to the server that is the master for replication at the time connection is opened. Whilst this is a simple mapping to a single server it does give the advantage that the clients have no requirement to track which server is currently the master, devolving responsibility for managing the failover to MaxScale.
|
||||||
|
|
||||||
|
In order for MaxScale to be able to determine the state of these servers the mysqlmon monitor module should be run against the set of servers that comprise the service.
|
||||||
|
|
||||||
|
===== Galera Cluster Configuration
|
||||||
|
|
||||||
|
Although not primarily designed for a multi-master replication setup, it is possible to use the readconnroute in this situation. The readconnroute connection router can be used to balance the connections across a Galera cluster. A special monitor is available that detects if nodes are joined to a Galera Cluster, with the addition of a router option to only route connections to nodes marked as synced. MaxScale can ensure that users are never connected to a node that is not a full cluster member.
|
||||||
|
|
||||||
|
----
|
||||||
|
[Galera Service]
|
||||||
|
type=service
|
||||||
|
router=readconnroute
|
||||||
|
router_options=synced
|
||||||
|
servers=server1,server2,server3,server4
|
||||||
|
user=maxscale
|
||||||
|
auth=thepasswd
|
||||||
|
|
||||||
|
[Galera Listener]
|
||||||
|
type=listener
|
||||||
|
service=Galera Service
|
||||||
|
protocol=MySQLClient
|
||||||
|
port=3336
|
||||||
|
----
|
||||||
|
|
||||||
|
----
|
||||||
|
[Galera Monitor]
|
||||||
|
type=monitor
|
||||||
|
module=galeramon
|
||||||
|
servers=server1,server2,server3,server4
|
||||||
|
user=galeramon
|
||||||
|
passwd=galeramon
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
The specialized Galera monitor can also select one of the node in the cluster as master, the others will be marked as slave. +
|
||||||
|
These roles are only assigned to synced nodes.
|
||||||
|
|
||||||
|
It then possible to have services/listeners with router_options=master or slave accessing a subset of all galera nodes. +
|
||||||
|
The “synced” simply means: access all nodes.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
----
|
||||||
|
[Galera Master Service]
|
||||||
|
type=service
|
||||||
|
router=readconnroute
|
||||||
|
router_options=master
|
||||||
|
|
||||||
|
[Galera Slave Service]
|
||||||
|
type=service
|
||||||
|
router=readconnroute
|
||||||
|
router_options=slave
|
||||||
|
|
||||||
|
----
|
||||||
|
The Master and Slave roles are also available for the Read/Write Split router operation
|
||||||
|
|
||||||
|
==== Readwritesplit
|
||||||
|
|
||||||
|
The readwritesplit is a statement based router that has been designed for use within Master/Slave replication environments. It examines every statement, parsing it to determine if the statement falls into one of three categories;
|
||||||
|
* read only statement
|
||||||
|
* possible write statement
|
||||||
|
session modification statement
|
||||||
|
Each of these three categories has a different action associated with it. Read only statements are sent to a slave server in the replication cluster. Possible write statements, which may include read statements that have an undeterminable side effect, are sent to the current replication master. Statements that modify the session are sent to all the servers, with the result that is generated by the master server being returned to the user.
|
||||||
|
|
||||||
|
Session modification statements must be replicated as they affect the future results of read and write operations, so they must be executed on all servers that could execute statements on behalf of this client.
|
||||||
|
|
||||||
|
Currently the readwritesplit router module is under development and has the following limitations:
|
||||||
|
* Connection failover support has not yet been implemented. Client connections will fail if the master server fails over.
|
||||||
|
===== Master/Slave Replication Setup
|
||||||
|
|
||||||
|
To setup the readwritesplit connection router in a master/slave failover environment is extremely simple, a service definition is required with the router defined for the service and an associated listener. +
|
||||||
|
The router_options parameter is not required but it can be used to specify how slave(s) are selected. Available option is slave_selection_criteria and possible value are LEAST_BEHIND_MASTER and LEAST_CURRENT_OPERATIONS. +
|
||||||
|
max_slave_connections is a readwritesplit-only option, which sets the upper limit for the number of slaves a router session can use. max_slave_replication_lag is (currently) another readwritesplit-specific option, which sets maximum allowed lag for slave in seconds. The criteria is checked when router chooses slaves and only slaves having smaller lag are eligible for selection. The lag is not checked after connection phase.
|
||||||
|
|
||||||
|
----
|
||||||
|
[Split Service]
|
||||||
|
type=service
|
||||||
|
router=readwritesplit
|
||||||
|
router_options=slave_selection_criteria=LEAST_BEHIND_MASTER
|
||||||
|
max_slave_connections=50%
|
||||||
|
max_slave_replication_lag=30
|
||||||
|
servers=server1,server2,server3,server4
|
||||||
|
user=maxscale
|
||||||
|
auth=thepasswd
|
||||||
|
|
||||||
|
[Split Listener]
|
||||||
|
type=listener
|
||||||
|
service=Split Service
|
||||||
|
protocol=MySQLClient
|
||||||
|
port=3336
|
||||||
|
----
|
||||||
|
|
||||||
|
The client would merely connect to port 3336 on the MaxScale host and statements would be directed to the master or slave as appropriate. Determination of the master or slave status may be done via a monitor module within MaxScale or externally. In this latter case the server flags would need to be set via the MaxScale debug interface, in future versions an API will be available for this purpose.
|
||||||
|
|
||||||
|
+++<u>Galera Cluster Configuration</u>+++ +
|
||||||
|
Master and Slave roles that galera monitor assign to nodes make possible the Read Write split approach to Galera Cluster as well.
|
||||||
|
|
||||||
|
Simply configure a Split Service with galera nodes:
|
||||||
|
|
||||||
|
----
|
||||||
|
[Galera Split Service]
|
||||||
|
type=service
|
||||||
|
router=readwritesplit
|
||||||
|
----
|
||||||
|
servers=galera_node1,galera_node2,galera_node3
|
||||||
|
|
||||||
|
|
||||||
|
==== Debugcli
|
||||||
|
|
||||||
|
The debugcli is a special case of a statement based router. Rather than direct the statements at an external data source they are handled internally. These statements are simple text commands and the results are the output of debug commands within MaxScale. The service and listener definitions for a debug cli service only differ from other services in that they require no backend server definitions.
|
||||||
|
|
||||||
|
===== Debug CLI Configuration
|
||||||
|
|
||||||
|
The definition of the debug cli service is illustrated below
|
||||||
|
|
||||||
|
----
|
||||||
|
[Debug Service]
|
||||||
|
type=service
|
||||||
|
router=debugcli
|
||||||
|
|
||||||
|
[Debug Listener]
|
||||||
|
type=listener
|
||||||
|
service=Debug Service
|
||||||
|
protocol=telnetd
|
||||||
|
port=4442
|
||||||
|
----
|
||||||
|
|
||||||
|
Connections using the telnet protocol to port 4442 of the MaxScale host will result in a new debug CLI session. A default username and password are used for this module, new users may be created using the add user command. As soon as any users are explicitly created the default username will no longer continue to work. The default username is admin with a password of skysql.
|
||||||
|
|
||||||
|
The debugcli supports two modes of operation, developer mode and user mode. The mode is set via the router_options parameter of the debugcli. The user mode is more suited to end-users and administrators, whilst the develop mode is explicitly targeted to software developing adding or maintaining the MaxScale code base. Details of the differences between the modes can be found in the debugging guide for MaxScale. The default mode for the debugcli is user mode. The following service definition would enable a developer version of the debugcli.
|
||||||
|
|
||||||
|
----
|
||||||
|
[Debug Service]
|
||||||
|
type=service
|
||||||
|
router=debugcli
|
||||||
|
----
|
||||||
|
router_options=developer
|
||||||
|
|
||||||
|
It should be noted that both a user and a developer version of the debugcli may be defined within the same instance of MaxScale, however they must be defined as two distinct services, each with a distinct listener.
|
||||||
|
----
|
||||||
|
|
||||||
|
[Debug Service]
|
||||||
|
type=service
|
||||||
|
router=debugcli
|
||||||
|
router_options=developer
|
||||||
|
|
||||||
|
[Debug Listener]
|
||||||
|
type=listener
|
||||||
|
service=Debug Service
|
||||||
|
protocol=telnetd
|
||||||
|
port=4442
|
||||||
|
|
||||||
|
[Admin Service]
|
||||||
|
type=service
|
||||||
|
router=debugcli
|
||||||
|
|
||||||
|
[Admin Listener]
|
||||||
|
type=listener
|
||||||
|
service=Debug Service
|
||||||
|
protocol=telnetd
|
||||||
|
----
|
||||||
|
port=4242
|
||||||
|
|
||||||
|
==== CLI
|
||||||
|
The command line interface as used by maxadmin. This is a variant of the debugcli that is built slightly differently so that it may be accessed by the client application maxadmin. The CLI requires the use of the maxscaled protocol.
|
||||||
|
|
||||||
|
===== CLI Configuration
|
||||||
|
There are two components to the definition required in order to run the command line interface to use with MaxAdmin; a service and a listener. +
|
||||||
|
The default entries required are shown below.
|
||||||
|
----
|
||||||
|
|
||||||
|
[CLI]
|
||||||
|
type=service
|
||||||
|
router=cli
|
||||||
|
|
||||||
|
[CLI Listener]
|
||||||
|
type=listener
|
||||||
|
service=CLI
|
||||||
|
protocol=maxscaled
|
||||||
|
address=localhost
|
||||||
|
port=6603
|
||||||
|
----
|
||||||
|
|
||||||
|
Note that this uses the default port of 6603 and confines the connections to localhost connections only. Remove the address= entry to allow connections from any machine on your network. Changing the port from 6603 will mean that you must allows pass a -p option to the MaxAdmin command.
|
||||||
|
==
|
||||||
|
==
|
||||||
|
== Monitor Modules
|
||||||
|
Monitor modules are used by MaxScale to internally monitor the state of the backend databases in order to set the server flags for each of those servers. The router modules then use these flags to determine if the particular server is a suitable destination for routing connections for particular query classifications. The monitors are run within separate threads of MaxScale and do not affect the MaxScale performance.
|
||||||
|
|
||||||
|
The use of monitors is optional, it is possible to run MaxScale with external monitoring, in which case arrangements must be made for an external entity to set the status of each of the servers that MaxScale can route to.
|
||||||
|
|
||||||
|
=== Mysqlmon
|
||||||
|
|
||||||
|
The MySQLMon monitor is a simple monitor designed for use with MySQL Master/Slave replication cluster. To execute the mysqlmon monitor an entry as shown below should be added to the MaxScale configuration file.
|
||||||
|
|
||||||
|
----
|
||||||
|
[MySQL Monitor]
|
||||||
|
type=monitor
|
||||||
|
module=mysqlmon
|
||||||
|
servers=server1,server2,server3,server4
|
||||||
|
----
|
||||||
|
|
||||||
|
This will monitor the 4 servers; server1, server2, server3 and server4. It will set the status of running or failed and master or slave for each of the servers.
|
||||||
|
|
||||||
|
The monitor uses the username given in the monitor section or the server specific user that is given in the server section to connect to the server. This user must have sufficient permissions on the database to determine the state of replication. The roles that must be granted to this user are REPLICATION SLAVE and REPLICATION CLIENT.
|
||||||
|
|
||||||
|
To create a user that can be used to monitor the state of the cluster, the following commands could be used.
|
||||||
|
|
||||||
|
----
|
||||||
|
MariaDB [mysql]> create user 'maxscalemon'@'maxscalehost' identified by 'Ha79hjds';
|
||||||
|
Query OK, 0 rows affected (0.01 sec)
|
||||||
|
|
||||||
|
MariaDB [mysql]> grant REPLICATION SLAVE on *.* to 'maxscalemon'@'maxscalehost';
|
||||||
|
Query OK, 0 rows affected (0.00 sec)
|
||||||
|
|
||||||
|
MariaDB [mysql]> grant REPLICATION CLIENT on *.* to 'maxscalemon'@'maxscalehost';
|
||||||
|
Query OK, 0 rows affected (0.00 sec)
|
||||||
|
|
||||||
|
MariaDB [mysql]>
|
||||||
|
----
|
||||||
|
|
||||||
|
Assuming that MaxScale is running on the host maxscalehost.
|
||||||
|
|
||||||
|
=== Galeramon
|
||||||
|
|
||||||
|
The Galeramon monitor is a simple router designed for use with MySQL Galera cluster. To execute the galeramon monitor an entry as shown below should be added to the MaxScale configuration file.
|
||||||
|
|
||||||
|
----
|
||||||
|
[Galera Monitor]
|
||||||
|
type=monitor
|
||||||
|
module=galeramon
|
||||||
|
servers=server1,server2,server3,server4
|
||||||
|
----
|
||||||
|
|
||||||
|
This will monitor the 4 servers; server1, server2, server3 and server4. It will set the status of running or failed and joined for those servers that reported the Galera JOINED status.
|
||||||
|
|
||||||
|
The user that is configured for use with the Galera monitor must have sufficient privileges to select from the information_schema database and GLOBAL_STATUS table within that database.
|
||||||
|
|
||||||
|
To create a user that can be used to monitor the state of the cluster, the following commands could be used.
|
||||||
|
|
||||||
|
----
|
||||||
|
MariaDB [mysql]> create user 'maxscalemon'@'maxscalehost' identified by 'Ha79hjds';
|
||||||
|
Query OK, 0 rows affected (0.01 sec)
|
||||||
|
|
||||||
|
MariaDB [mysql]> grant SELECT on INFORMATION_SCHEMA.GLOBAL_STATUS to 'maxscalemon'@'maxscalehost';
|
||||||
|
Query OK, 0 rows affected (0.00 sec)
|
||||||
|
|
||||||
|
MariaDB [mysql]>
|
||||||
|
----
|
||||||
|
|
||||||
|
Assuming that MaxScale is running on the host maxscalehost.
|
||||||
|
|
||||||
|
|
||||||
|
The Galera monitor can also assign Master and Slave roles to the configured nodes:
|
||||||
|
|
||||||
|
among the set of synced servers, the one with the lowest value of ‘wsrep_local_index’ is selected as the current master while the others are slaves.
|
||||||
|
|
||||||
|
This way is possible to configure the node access based not only on ‘synced’ state but even on Master and Slave role enabling the use of Read Write split operation on a Galera cluster and avoiding any possible write conflict.
|
||||||
|
|
||||||
|
Example status for a Galera server node is:
|
||||||
|
|
||||||
|
----
|
||||||
|
Server 0x261fe50 (server2)
|
||||||
|
Server: 192.168.1.101
|
||||||
|
----
|
||||||
|
Status: Master, Synced, Running
|
||||||
|
|
||||||
|
|
||||||
|
==
|
||||||
|
==
|
||||||
|
== Filter Modules
|
||||||
|
Currently four example filters are included in the MaxScale distribution
|
||||||
|
|
||||||
|
|===
|
||||||
|
|*Module*|*Description*
|
||||||
|
|
||||||
|
|testfilter|Statement counting Filter - a simple filter that counts the number of SQL statements executed within a session. Results may be viewed via the debug interface.
|
||||||
|
|qlafilter|Query Logging Filter - a simple query logging filter that write all statements for a session into a log file for that session.
|
||||||
|
|regexfilter|Query Rewrite Filter - an example of how filters can alter the query contents. This filter allows a regular expression to be defined, along with replacement text that should be substituted for every match of that regular expression.
|
||||||
|
|tee|A filter that duplicates SQL requests and sends the duplicates to another service within MaxScale.
|
||||||
|
|===
|
||||||
|
|
||||||
|
These filters are merely examples of what may be achieved with the filter API and are not sophisticated or consider as suitable for production use, they merely illustrate the functionality possible.
|
||||||
|
|
||||||
|
=== Statement Counting Filter
|
||||||
|
The statement counting filter is implemented in the module names testfilter and merely keeps a count of the number of SQL statements executed. The filter requires no options to be passed and takes no parameters. The statement count can be viewed via the diagnostic and debug interface of MaxScale.
|
||||||
|
|
||||||
|
In order to add this filter to an existing service create a filter section to name the filter as follows
|
||||||
|
|
||||||
|
----
|
||||||
|
[counter]
|
||||||
|
type=filter
|
||||||
|
module=testfilter
|
||||||
|
----
|
||||||
|
|
||||||
|
Then add the filter to your service by including the filters= parameter in the service section.
|
||||||
|
|
||||||
|
+filters=counter+
|
||||||
|
|
||||||
|
=== Query Log All Filter
|
||||||
|
The QLA filter simply writes all SQL statements to a log file along with a timestamp for the statement. An example of the file produced by the QLA filter is shown below
|
||||||
|
|
||||||
|
----
|
||||||
|
00:36:04.922 5/06/2014, select @@version_comment limit 1
|
||||||
|
00:36:12.663 5/06/2014, SELECT DATABASE()
|
||||||
|
00:36:12.664 5/06/2014, show databases
|
||||||
|
00:36:12.665 5/06/2014, show tables
|
||||||
|
----
|
||||||
|
|
||||||
|
A new file is created for each client connection, the name of the logfile can be controlled by the use of the router options. No parameters are used by the QLA filter. The filter is implemented by the loadable module qlafilter.
|
||||||
|
|
||||||
|
To add the QLA filter to a service you must create a filter section to name the filter, associated the loadable module and define the filename option.
|
||||||
|
|
||||||
|
----
|
||||||
|
[QLA]
|
||||||
|
type=filter
|
||||||
|
module=qlafilter
|
||||||
|
options=/tmp/QueryLog
|
||||||
|
----
|
||||||
|
|
||||||
|
Then add the filters= parameter into the service that you wish to log by adding this parameter to the service section
|
||||||
|
|
||||||
|
+filters=QLA+
|
||||||
|
|
||||||
|
A log file will be created for each client connection, the name of that log file will be /tmp/QueryLog.<number>
|
||||||
|
|
||||||
|
=== Regular Expression Filter
|
||||||
|
The regular expression filter is a simple text based query rewriting filter. It allows a regular expression to be used to match text in a SQL query and then a string replacement to be made against that match. The filter is implemented by the regexfilter loadable module and is passed two parameters, a match string and a replacement string.
|
||||||
|
|
||||||
|
To add the filter to your service you must first create a filter section to name the filter and give the match and replacement strings. Here we define a filter that will convert to MariaDB 10 command show all slaves status to the older form of show slave status for MariaDB 5.5.
|
||||||
|
|
||||||
|
----
|
||||||
|
[slavestatus]
|
||||||
|
type=filter
|
||||||
|
module=regexfilter
|
||||||
|
match=show *all *slaves
|
||||||
|
replace=show slave
|
||||||
|
----
|
||||||
|
|
||||||
|
You must then add this filter to your service by adding the filters= option
|
||||||
|
|
||||||
|
+filters=slavestatus+
|
||||||
|
|
||||||
|
Another example would be a filter to convert from the MySQL 5.1 create table syntax that used the TYPE keyword to the newer ENGINE keyword.
|
||||||
|
|
||||||
|
----
|
||||||
|
[EnginerFilter]
|
||||||
|
type=filter
|
||||||
|
module=regexfilter
|
||||||
|
match=TYPE
|
||||||
|
replace=ENGINE
|
||||||
|
----
|
||||||
|
|
||||||
|
This would then change the SQL sent by a client application written to work with MySQL 5.1 into SQL that was compliant with MySQL 5.5. The statement
|
||||||
|
|
||||||
|
+create table supplier(id integer, name varchar(80)) type=innodb+
|
||||||
|
|
||||||
|
would be replaced with
|
||||||
|
|
||||||
|
+create table supplier(id integer, name varchar(80)) ENGINE=innodb+
|
||||||
|
|
||||||
|
before being sent to the server. Note that the text in the match string is case independent.
|
||||||
|
|
||||||
|
=== Tee Filter
|
||||||
|
The tee filter is a filter module for MaxScale is a “plumbing” fitting in the MaxScale filter toolkit. It can be used in a filter pipeline of a service to make a copy of requests from the client and dispatch a copy of the request to another service within MaxScale.
|
||||||
|
|
||||||
|
The configuration block for the TEE filter requires the minimal filter parameters in it’s section within the MaxScale.cnf file that defines the filter to load and the service to send the duplicates to.
|
||||||
|
|
||||||
|
----
|
||||||
|
[ArchieveFilter]
|
||||||
|
type=filter
|
||||||
|
module=tee
|
||||||
|
service=Archieve
|
||||||
|
|
||||||
|
----
|
||||||
|
In addition parameters may be added to define patterns to match against to either include or exclude particular SQL statements to be duplicated. You may also define that the filter is only active for connections from a particular source or when a particular user is connected.
|
||||||
|
==
|
||||||
|
==
|
||||||
|
== Encrypting Passwords
|
||||||
|
|
||||||
|
Passwords stored in the MaxScale.cnf file may optionally be encrypted for added security. This is done by creation of an encryption key on installation of MaxScale. Encryption keys may be created manually by executing the maxkeys utility with the argument of the filename to store the key.
|
||||||
|
|
||||||
|
+maxkeys $MAXSCALE_HOME/etc/.secrets+
|
||||||
|
|
||||||
|
Changing the encryption key for MaxScale will invalidate any currently encrypted keys stored in the MaxScale.cnf file.
|
||||||
|
|
||||||
|
=== Creating Encrypted Passwords
|
||||||
|
|
||||||
|
Encrypted passwords are created by executing the maxpasswd command with the password you require to encrypt as an argument. The environment variable MAXSCALE_HOME must be set, or MaxScale must be installed in the default location before maxpasswd can be executed.
|
||||||
|
|
||||||
|
----
|
||||||
|
maxpasswd MaxScalePw001
|
||||||
|
61DD955512C39A4A8BC4BB1E5F116705
|
||||||
|
----
|
||||||
|
|
||||||
|
The output of the maxpasswd command is a hexadecimal string, this should be inserted into the MaxScale.cnf file in place of the ordinary, plain text, password. MaxScale will determine this as an encrypted password and automatically decrypt it before sending it the database server.
|
||||||
|
|
||||||
|
----
|
||||||
|
[Split Service]
|
||||||
|
type=service
|
||||||
|
router=readwritesplit
|
||||||
|
servers=server1,server2,server3,server4
|
||||||
|
user=maxscale
|
||||||
|
password=61DD955512C39A4A8BC4BB1E5F116705
|
||||||
|
----
|
||||||
|
==
|
||||||
|
==
|
||||||
|
== Configuration Updates
|
||||||
|
The current MaxScale configuration may be updating by editing the configuration file and then forcing MaxScale to reread the configuration file. To force MaxScale to reread the configuration file a SIGTERM signal is sent to the MaxScale process.
|
||||||
|
|
||||||
|
Some changes in configuration can not be dynamically changed and require a complete restart of MaxScale, whilst others will take some time to be applied.
|
||||||
|
|
||||||
|
=== Limitations
|
||||||
|
Services that are removed via the configuration update mechanism can not be physically removed from MaxScale until there are no longer any connections using the service.
|
||||||
|
|
||||||
|
When the number of threads is decreased the threads will not actually be terminated until such time as they complete the current operation of that thread.
|
||||||
|
|
||||||
|
Monitors can not be completely removed from the running MaxScale.
|
||||||
|
==
|
||||||
|
==
|
||||||
|
== Authentication
|
||||||
|
MySQL uses username, passwords and the client host in order to authenticate a user, so a typical user would be defined as user X at host Y and would be given a password to connect. MaxScale uses exactly the same rules as MySQL when users connect to the MaxScale instance, i.e. it will check the address from which the client is connecting and treat this in exactly the same way that MySQL would. MaxScale will pull the authentication data from one of the backend servers and use this to match the incoming connections, the assumption being that all the backend servers for a particular service will share the same set of user credentials.
|
||||||
|
|
||||||
|
It is important to understand, however, that when MaxScale itself makes connections to the backend servers the backend server will see all connections as originating from the host that runs MaxScale and not the original host from which the client connected to MaxScale. Therefore the backend servers should be configured to allow connections from the MaxScale host for every user that can connect from any host. Since there is only a single password within the database server for a given host, this limits the configuration such that a given user name must have the same password for every host from which they can connect.
|
||||||
|
|
||||||
|
To clarify, if a user X is defined as using password _pass1_ from host a and _pass2_ from host b then there must be an entry in the user table for user X form the MaxScale host, say _pass1_.
|
||||||
|
|
||||||
|
This would result in rows in the user table as follows
|
||||||
|
|===
|
||||||
|
|*Username*|*Password*|*Client Host*
|
||||||
|
|
||||||
|
|X|pass1|a
|
||||||
|
|X|pass2|b
|
||||||
|
|X|pass1|MaxScale
|
||||||
|
|===
|
||||||
|
|
||||||
|
|
||||||
|
In this case the user X would be able to connect to MaxScale from host a giving the password of _pass1_. In addition MaxScale would be able to create connections for this user to the backend servers using the username X and password _pass1_, since the MaxScale host is also defined to have password _pass1_. User X would not however be able to connect from host b since they would need to provide the password _pass2_ in order to connect to MaxScale, but then MaxScale would not be able to connect to the backends as it would also use the password _pass2_ for these connections.
|
||||||
|
|
||||||
|
=== Wildcard Hosts
|
||||||
|
|
||||||
|
Hostname mapping in MaxScale works in exactly the same way as for MySQL, if the wildcard is used for the host then any host other than the localhost (127.0.0.1) will match. It is important to consider that the localhost check will be performed at the MaxScale level and at the MySQL server level.
|
||||||
|
|
||||||
|
If MaxScale and the databases are on separate hosts there are two important changes in behaviour to consider:
|
||||||
|
|
||||||
|
. Clients running on the same machine as the backend database now may access the database using the wildcard entry. The localhost check between the client and MaxScale will allow the use of the wildcard, since the client is not running on the MaxScale host. Also the wildcard entry can be used on the database host as MaxScale is making that connection and it is not running on the same host as the database.
|
||||||
|
. Clients running on the same host as MaxScale can not access the database via MaxScale using the wildcard entry since the connection to MaxScale will be from the localhost. These clients are able to access the database directly, as they will use the wildcard entry.
|
||||||
|
|
||||||
|
If MaxScale is running on the same host as one or more of the database nodes to which it is acting as a proxy then the wildcard host entries can be used to connect to MaxScale but not to connect onwards to the database running on the same node.
|
||||||
|
|
||||||
|
In all these cases the issue may be solved by adding an explicit entry for the localhost address that has the same password as the wildcard entry. This may be done using a statement as below for each of the databases that are required:
|
||||||
|
|
||||||
|
+MariaDB [mysql]> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP ON employee.* 'user1'@'localhost' IDENTIFIED BY ‘xxx’;+ +
|
||||||
|
Query OK, 0 rows affected (0.00 sec)
|
||||||
|
|
||||||
|
=== Limitations
|
||||||
|
|
||||||
|
At the time of writing the authentication mechanism within MaxScale does not support IPV6 address matching in connections rules. This is also in line with the current protocol modules that do not support IPV6.
|
||||||
|
|
||||||
|
Partial address matching, such as 10.% is also not supported in the current version of MaxScale.
|
||||||
|
==
|
||||||
|
== Error Reporting
|
||||||
|
MaxScale is designed to be executed as a service, therefore all error reports, including configuration errors, are written to the MaxScale error log file. MaxScale will log to a set of files in the directory $MAXSCALE_HOME/log, the only exception to this is if the log directory is not writable, in which case a message is sent to the standard error descriptor.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Troubleshooting +
|
||||||
|
MaxScale binds on TCP ports and UNIX sockets as well.
|
||||||
|
|
||||||
|
If there is a local firewall in the server where MaxScale is installed, the IP and port must be configured in order to receive connections from outside.
|
||||||
|
|
||||||
|
If the firewall is a network facility among all the involved servers, a configuration update is required as well.
|
||||||
|
|
||||||
|
Example: +
|
||||||
|
----
|
||||||
|
[Galera Listener]
|
||||||
|
type=listener
|
||||||
|
----
|
||||||
|
address=192.1681.3.33 +
|
||||||
|
----
|
||||||
|
port=4408
|
||||||
|
socket=/servers/maxscale/galera.sock
|
||||||
|
----
|
||||||
|
|
||||||
|
TCP/IP Traffic must be permitted to 192.1681.3.33 port 4408
|
||||||
|
|
||||||
|
For Unix socket, the socket file path (example: /servers/maxscale/galera.sock) must be writable by the Unix user MaxScale runs as.
|
||||||
|
|
||||||
|
|
1228
Documentation/experimental/ConfigurationGuide.md
Normal file
1228
Documentation/experimental/ConfigurationGuide.md
Normal file
File diff suppressed because it is too large
Load Diff
1447
Documentation/experimental/ConfigurationGuide.textile
Normal file
1447
Documentation/experimental/ConfigurationGuide.textile
Normal file
File diff suppressed because it is too large
Load Diff
1252
Documentation/experimental/ConfigurationGuide.xml
Normal file
1252
Documentation/experimental/ConfigurationGuide.xml
Normal file
File diff suppressed because it is too large
Load Diff
10
README
10
README
@ -6,11 +6,11 @@ a semantic understanding of the database statements and the roles of
|
|||||||
the various servers within the backend cluster of databases.
|
the various servers within the backend cluster of databases.
|
||||||
|
|
||||||
MaxScale is designed to provide load balancing and high availability
|
MaxScale is designed to provide load balancing and high availability
|
||||||
functionality transparantly to the applications. In addition it provides
|
functionality transparently to the applications. In addition it provides
|
||||||
a highly scalable and flexibile architecture, with plugin components to
|
a highly scalable and flexibile architecture, with plugin components to
|
||||||
support different protocols and routing decissions.
|
support different protocols and routing decissions.
|
||||||
|
|
||||||
MaxScale is implemented in C and makes entensive use of the
|
MaxScale is implemented in C and makes extensive use of the
|
||||||
asynchronous I/O capabilities of the Linux operating system. The epoll
|
asynchronous I/O capabilities of the Linux operating system. The epoll
|
||||||
system is used to provide the event driven framework for the input and
|
system is used to provide the event driven framework for the input and
|
||||||
output via sockets.
|
output via sockets.
|
||||||
@ -47,13 +47,13 @@ MariaDB-5.5.34-centos6-x86_64-common.rpm
|
|||||||
MariaDB-5.5.34-centos6-x86_64-compat.rpm
|
MariaDB-5.5.34-centos6-x86_64-compat.rpm
|
||||||
MariaDB-5.5.34-centos6-x86_64-devel.rpm
|
MariaDB-5.5.34-centos6-x86_64-devel.rpm
|
||||||
|
|
||||||
Please backup any existent my.cnf file before installing the RPMs
|
Please backup any existing my.cnf file before installing the RPMs
|
||||||
|
|
||||||
Install the RPM files using:
|
Install the RPM files using:
|
||||||
|
|
||||||
rpm -i MariaDB-5.5.34-centos6-x86_64-common.rpm MariaDB-5.5.34-centos6-x86_64-compat.rpm MariaDB-5.5.34-centos6-x86_64-devel.rpm
|
rpm -i MariaDB-5.5.34-centos6-x86_64-common.rpm MariaDB-5.5.34-centos6-x86_64-compat.rpm MariaDB-5.5.34-centos6-x86_64-devel.rpm
|
||||||
|
|
||||||
Note, if you wish to relocate the package to avoid an exisitng MariaDB
|
Note, if you wish to relocate the package to avoid an existing MariaDB
|
||||||
or MySQL installation you will need to use the --force option in addition
|
or MySQL installation you will need to use the --force option in addition
|
||||||
to the --relocate option.
|
to the --relocate option.
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ Example:
|
|||||||
|
|
||||||
|
|
||||||
Please note the errmsg.sys file is NOT included in the RPMs at the
|
Please note the errmsg.sys file is NOT included in the RPMs at the
|
||||||
curent time, it must be taken from an existing MariaDB setup. The
|
current time, it must be taken from an existing MariaDB setup. The
|
||||||
version of the errmsg.sys file must match the version of the developer
|
version of the errmsg.sys file must match the version of the developer
|
||||||
package you are using. A version mismatch will cause the library to fail
|
package you are using. A version mismatch will cause the library to fail
|
||||||
to initialise.
|
to initialise.
|
||||||
|
@ -569,7 +569,10 @@ int len;
|
|||||||
|
|
||||||
if ((newbuf = gwbuf_alloc(gwbuf_length(orig))) != NULL)
|
if ((newbuf = gwbuf_alloc(gwbuf_length(orig))) != NULL)
|
||||||
{
|
{
|
||||||
|
newbuf->gwbuf_type = orig->gwbuf_type;
|
||||||
|
newbuf->hint = hint_dup(orig->hint);
|
||||||
ptr = GWBUF_DATA(newbuf);
|
ptr = GWBUF_DATA(newbuf);
|
||||||
|
|
||||||
while (orig)
|
while (orig)
|
||||||
{
|
{
|
||||||
len = GWBUF_LENGTH(orig);
|
len = GWBUF_LENGTH(orig);
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
* 29/09/2014 Massimiliano Pinto Added Mysql user@host authentication with wildcard in IPv4 hosts:
|
* 29/09/2014 Massimiliano Pinto Added Mysql user@host authentication with wildcard in IPv4 hosts:
|
||||||
* x.y.z.%, x.y.%.%, x.%.%.%
|
* x.y.z.%, x.y.%.%, x.%.%.%
|
||||||
* 03/10/14 Massimiliano Pinto Added netmask to user@host authentication for wildcard in IPv4 hosts
|
* 03/10/14 Massimiliano Pinto Added netmask to user@host authentication for wildcard in IPv4 hosts
|
||||||
|
* 13/10/14 Massimiliano Pinto Added (user@host)@db authentication
|
||||||
*
|
*
|
||||||
* @endverbatim
|
* @endverbatim
|
||||||
*/
|
*/
|
||||||
@ -48,9 +49,18 @@
|
|||||||
#include <mysql_client_server_protocol.h>
|
#include <mysql_client_server_protocol.h>
|
||||||
|
|
||||||
#define USERS_QUERY_NO_ROOT " AND user NOT IN ('root')"
|
#define USERS_QUERY_NO_ROOT " AND user NOT IN ('root')"
|
||||||
#define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password) AS userdata FROM mysql.user WHERE user IS NOT NULL AND user <> ''"
|
#define LOAD_MYSQL_USERS_QUERY "SELECT user, host, password, concat(user,host,password,Select_priv) AS userdata, Select_priv AS anydb FROM mysql.user WHERE user IS NOT NULL AND user <> ''"
|
||||||
#define MYSQL_USERS_COUNT "SELECT COUNT(1) AS nusers FROM mysql.user"
|
#define MYSQL_USERS_COUNT "SELECT COUNT(1) AS nusers FROM mysql.user"
|
||||||
|
|
||||||
|
#define MYSQL_USERS_WITH_DB_ORDER " ORDER BY host DESC"
|
||||||
|
#define LOAD_MYSQL_USERS_WITH_DB_QUERY "SELECT user.user AS user,user.host AS host,user.password AS password,concat(user.user,user.host,user.password,user.Select_priv,IFNULL(db,'')) AS userdata, user.Select_priv AS anydb,db.db AS db FROM mysql.user LEFT JOIN mysql.db ON user.user=db.user AND user.host=db.host WHERE user.user IS NOT NULL AND user.user <> ''" MYSQL_USERS_WITH_DB_ORDER
|
||||||
|
|
||||||
|
#define MYSQL_USERS_WITH_DB_COUNT "SELECT COUNT(1) AS nusers_db FROM (" LOAD_MYSQL_USERS_WITH_DB_QUERY ") AS tbl_count"
|
||||||
|
|
||||||
|
#define LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT "SELECT * FROM (" LOAD_MYSQL_USERS_WITH_DB_QUERY ") AS t1 WHERE user NOT IN ('root')" MYSQL_USERS_WITH_DB_ORDER
|
||||||
|
|
||||||
|
#define LOAD_MYSQL_DATABASE_NAMES "SELECT * FROM ( (SELECT COUNT(1) AS ndbs FROM INFORMATION_SCHEMA.SCHEMATA) AS tbl1, (SELECT GRANTEE,PRIVILEGE_TYPE from INFORMATION_SCHEMA.USER_PRIVILEGES WHERE privilege_type='SHOW DATABASES' AND REPLACE(GRANTEE, \"\'\",\"\")=CURRENT_USER()) AS tbl2)"
|
||||||
|
|
||||||
extern int lm_enabled_logfiles_bitmask;
|
extern int lm_enabled_logfiles_bitmask;
|
||||||
|
|
||||||
static int getUsers(SERVICE *service, USERS *users);
|
static int getUsers(SERVICE *service, USERS *users);
|
||||||
@ -60,7 +70,13 @@ static void uh_keyfree( void* key);
|
|||||||
static int uh_hfun( void* key);
|
static int uh_hfun( void* key);
|
||||||
char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key);
|
char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key);
|
||||||
char *mysql_format_user_entry(void *data);
|
char *mysql_format_user_entry(void *data);
|
||||||
int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd);
|
int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db);
|
||||||
|
static int getDatabases(SERVICE *, MYSQL *);
|
||||||
|
HASHTABLE *resource_alloc();
|
||||||
|
void resource_free(HASHTABLE *resource);
|
||||||
|
void *resource_fetch(HASHTABLE *, char *);
|
||||||
|
int resource_add(HASHTABLE *, char *, char *);
|
||||||
|
int resource_hash(char *);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the user/passwd form mysql.user table into the service users' hashtable
|
* Load the user/passwd form mysql.user table into the service users' hashtable
|
||||||
@ -87,15 +103,26 @@ reload_mysql_users(SERVICE *service)
|
|||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
USERS *newusers, *oldusers;
|
USERS *newusers, *oldusers;
|
||||||
|
HASHTABLE *oldresources;
|
||||||
|
|
||||||
if ((newusers = mysql_users_alloc()) == NULL)
|
if ((newusers = mysql_users_alloc()) == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
oldresources = service->resources;
|
||||||
|
|
||||||
i = getUsers(service, newusers);
|
i = getUsers(service, newusers);
|
||||||
|
|
||||||
spinlock_acquire(&service->spin);
|
spinlock_acquire(&service->spin);
|
||||||
oldusers = service->users;
|
oldusers = service->users;
|
||||||
|
|
||||||
service->users = newusers;
|
service->users = newusers;
|
||||||
|
|
||||||
spinlock_release(&service->spin);
|
spinlock_release(&service->spin);
|
||||||
|
|
||||||
|
/* free the old table */
|
||||||
users_free(oldusers);
|
users_free(oldusers);
|
||||||
|
/* free old resources */
|
||||||
|
resource_free(oldresources);
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@ -113,14 +140,20 @@ replace_mysql_users(SERVICE *service)
|
|||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
USERS *newusers, *oldusers;
|
USERS *newusers, *oldusers;
|
||||||
|
HASHTABLE *oldresources;
|
||||||
|
|
||||||
if ((newusers = mysql_users_alloc()) == NULL)
|
if ((newusers = mysql_users_alloc()) == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
oldresources = service->resources;
|
||||||
|
|
||||||
|
/* load db users ad db grants */
|
||||||
i = getUsers(service, newusers);
|
i = getUsers(service, newusers);
|
||||||
|
|
||||||
if (i <= 0) {
|
if (i <= 0) {
|
||||||
users_free(newusers);
|
users_free(newusers);
|
||||||
|
/* restore resources */
|
||||||
|
service->resources = oldresources;
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +167,7 @@ USERS *newusers, *oldusers;
|
|||||||
LOGFILE_DEBUG,
|
LOGFILE_DEBUG,
|
||||||
"%lu [replace_mysql_users] users' tables not switched, checksum is the same",
|
"%lu [replace_mysql_users] users' tables not switched, checksum is the same",
|
||||||
pthread_self())));
|
pthread_self())));
|
||||||
|
|
||||||
/* free the new table */
|
/* free the new table */
|
||||||
users_free(newusers);
|
users_free(newusers);
|
||||||
i = 0;
|
i = 0;
|
||||||
@ -146,10 +180,15 @@ USERS *newusers, *oldusers;
|
|||||||
service->users = newusers;
|
service->users = newusers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* free old resources */
|
||||||
|
resource_free(oldresources);
|
||||||
|
|
||||||
spinlock_release(&service->spin);
|
spinlock_release(&service->spin);
|
||||||
|
|
||||||
if (i)
|
if (i) {
|
||||||
|
/* free the old table */
|
||||||
users_free(oldusers);
|
users_free(oldusers);
|
||||||
|
}
|
||||||
|
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
@ -169,7 +208,7 @@ USERS *newusers, *oldusers;
|
|||||||
* @return 1 on success, 0 on failure
|
* @return 1 on success, 0 on failure
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd) {
|
int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db) {
|
||||||
struct sockaddr_in serv_addr;
|
struct sockaddr_in serv_addr;
|
||||||
MYSQL_USER_HOST key;
|
MYSQL_USER_HOST key;
|
||||||
char ret_ip[INET_ADDRSTRLEN + 1]="";
|
char ret_ip[INET_ADDRSTRLEN + 1]="";
|
||||||
@ -177,9 +216,13 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p
|
|||||||
int found_any=0;
|
int found_any=0;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
|
if (users == NULL || user == NULL || host == NULL) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/* prepare the user@host data struct */
|
/* prepare the user@host data struct */
|
||||||
memset(&serv_addr, 0, sizeof(serv_addr));
|
memset(&serv_addr, 0, sizeof(serv_addr));
|
||||||
memset(&key, 0, sizeof(key));
|
memset(&key, '\0', sizeof(key));
|
||||||
|
|
||||||
/* set user */
|
/* set user */
|
||||||
key.user = strdup(user);
|
key.user = strdup(user);
|
||||||
@ -188,6 +231,20 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* for anydb == Y key.resource is '\0' as set by memset */
|
||||||
|
if (anydb == NULL) {
|
||||||
|
key.resource = NULL;
|
||||||
|
} else {
|
||||||
|
if (strcmp(anydb, "N") == 0) {
|
||||||
|
if (db != NULL)
|
||||||
|
key.resource = strdup(db);
|
||||||
|
else
|
||||||
|
key.resource = NULL;
|
||||||
|
} else {
|
||||||
|
key.resource = strdup("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* handle ANY, Class C,B,A */
|
/* handle ANY, Class C,B,A */
|
||||||
|
|
||||||
/* ANY */
|
/* ANY */
|
||||||
@ -196,11 +253,12 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p
|
|||||||
found_any = 1;
|
found_any = 1;
|
||||||
} else {
|
} else {
|
||||||
char *tmp;
|
char *tmp;
|
||||||
strcpy(ret_ip, host);
|
strncpy(ret_ip, host, INET_ADDRSTRLEN);
|
||||||
tmp = ret_ip+strlen(ret_ip)-1;
|
tmp = ret_ip+strlen(ret_ip)-1;
|
||||||
|
|
||||||
/* start from Class C */
|
/* start from Class C */
|
||||||
while(*tmp) {
|
|
||||||
|
while(tmp > ret_ip) {
|
||||||
if (*tmp == '%') {
|
if (*tmp == '%') {
|
||||||
/* set only the last IPv4 byte to 1
|
/* set only the last IPv4 byte to 1
|
||||||
* avoiding setipadress() failure
|
* avoiding setipadress() failure
|
||||||
@ -231,15 +289,125 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* add user@host as key and passwd as value in the MySQL users hash table */
|
/* add user@host as key and passwd as value in the MySQL users hash table */
|
||||||
if (mysql_users_add(users, &key, passwd))
|
if (mysql_users_add(users, &key, passwd)) {
|
||||||
ret = 1;
|
ret = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
free(key.user);
|
free(key.user);
|
||||||
|
if (key.resource)
|
||||||
|
free(key.resource);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load the database specific grants from mysql.db table into the service resources hashtable
|
||||||
|
* environment.
|
||||||
|
*
|
||||||
|
* @param service The current service
|
||||||
|
* @param users The users table into which to load the users
|
||||||
|
* @return -1 on any error or the number of users inserted (0 means no users at all)
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
getDatabases(SERVICE *service, MYSQL *con)
|
||||||
|
{
|
||||||
|
MYSQL_ROW row;
|
||||||
|
MYSQL_RES *result = NULL;
|
||||||
|
char *service_user = NULL;
|
||||||
|
char *service_passwd = NULL;
|
||||||
|
int ndbs = 0;
|
||||||
|
|
||||||
|
char *get_showdbs_priv_query = LOAD_MYSQL_DATABASE_NAMES;
|
||||||
|
|
||||||
|
serviceGetUser(service, &service_user, &service_passwd);
|
||||||
|
|
||||||
|
if (service_user == NULL || service_passwd == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (mysql_query(con, get_showdbs_priv_query)) {
|
||||||
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
|
LOGFILE_ERROR,
|
||||||
|
"Error : Loading database names for service %s encountered "
|
||||||
|
"error: %s.",
|
||||||
|
service->name,
|
||||||
|
mysql_error(con))));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = mysql_store_result(con);
|
||||||
|
|
||||||
|
if (result == NULL) {
|
||||||
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
|
LOGFILE_ERROR,
|
||||||
|
"Error : Loading database names for service %s encountered "
|
||||||
|
"error: %s.",
|
||||||
|
service->name,
|
||||||
|
mysql_error(con))));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Result has only one row */
|
||||||
|
row = mysql_fetch_row(result);
|
||||||
|
|
||||||
|
if (row) {
|
||||||
|
ndbs = atoi(row[0]);
|
||||||
|
} else {
|
||||||
|
ndbs = 0;
|
||||||
|
|
||||||
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
|
LOGFILE_ERROR,
|
||||||
|
"Warning: Loading DB names for service [%s] returned 0 rows."
|
||||||
|
" SHOW DATABASES grant to user [%s] is required for MaxScale DB Name Authentication",
|
||||||
|
service->name,
|
||||||
|
service_user)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* free resut set */
|
||||||
|
mysql_free_result(result);
|
||||||
|
|
||||||
|
if (!ndbs) {
|
||||||
|
/* return if no db names are available */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mysql_query(con, "SHOW DATABASES")) {
|
||||||
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
|
LOGFILE_ERROR,
|
||||||
|
"Error : Loading database names for service %s encountered "
|
||||||
|
"error: %s.",
|
||||||
|
service->name,
|
||||||
|
mysql_error(con))));
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = mysql_store_result(con);
|
||||||
|
|
||||||
|
if (result == NULL) {
|
||||||
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
|
LOGFILE_ERROR,
|
||||||
|
"Error : Loading database names for service %s encountered "
|
||||||
|
"error: %s.",
|
||||||
|
service->name,
|
||||||
|
mysql_error(con))));
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Now populate service->resources hashatable with db names */
|
||||||
|
service->resources = resource_alloc();
|
||||||
|
|
||||||
|
/* insert key and value "" */
|
||||||
|
while ((row = mysql_fetch_row(result))) {
|
||||||
|
resource_add(service->resources, row[0], "");
|
||||||
|
}
|
||||||
|
|
||||||
|
mysql_free_result(result);
|
||||||
|
|
||||||
|
return ndbs;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the user/passwd form mysql.user table into the service users' hashtable
|
* Load the user/passwd form mysql.user table into the service users' hashtable
|
||||||
* environment.
|
* environment.
|
||||||
@ -251,29 +419,24 @@ int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *p
|
|||||||
static int
|
static int
|
||||||
getUsers(SERVICE *service, USERS *users)
|
getUsers(SERVICE *service, USERS *users)
|
||||||
{
|
{
|
||||||
MYSQL *con = NULL;
|
MYSQL *con = NULL;
|
||||||
MYSQL_ROW row;
|
MYSQL_ROW row;
|
||||||
MYSQL_RES *result = NULL;
|
MYSQL_RES *result = NULL;
|
||||||
int num_fields = 0;
|
char *service_user = NULL;
|
||||||
char *service_user = NULL;
|
char *service_passwd = NULL;
|
||||||
char *service_passwd = NULL;
|
char *dpwd;
|
||||||
char *dpwd;
|
int total_users = 0;
|
||||||
int total_users = 0;
|
SERVER *server;
|
||||||
SERVER *server;
|
char *users_query;
|
||||||
char *users_query;
|
unsigned char hash[SHA_DIGEST_LENGTH]="";
|
||||||
unsigned char hash[SHA_DIGEST_LENGTH]="";
|
char *users_data = NULL;
|
||||||
char *users_data = NULL;
|
int nusers = 0;
|
||||||
int nusers = 0;
|
int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN + sizeof(char) + MYSQL_DATABASE_MAXLEN;
|
||||||
int users_data_row_len = MYSQL_USER_MAXLEN + MYSQL_HOST_MAXLEN + MYSQL_PASSWORD_LEN;
|
int dbnames = 0;
|
||||||
|
int db_grants = 0;
|
||||||
/* enable_root for MySQL protocol module means load the root user credentials from backend databases */
|
|
||||||
if(service->enable_root) {
|
|
||||||
users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC";
|
|
||||||
} else {
|
|
||||||
users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT " ORDER BY HOST DESC";
|
|
||||||
}
|
|
||||||
|
|
||||||
serviceGetUser(service, &service_user, &service_passwd);
|
serviceGetUser(service, &service_user, &service_passwd);
|
||||||
|
|
||||||
if (service_user == NULL || service_passwd == NULL)
|
if (service_user == NULL || service_passwd == NULL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
@ -302,6 +465,7 @@ getUsers(SERVICE *service, USERS *users)
|
|||||||
*/
|
*/
|
||||||
server = service->databases;
|
server = service->databases;
|
||||||
dpwd = decryptPassword(service_passwd);
|
dpwd = decryptPassword(service_passwd);
|
||||||
|
|
||||||
while (server != NULL && (mysql_real_connect(con,
|
while (server != NULL && (mysql_real_connect(con,
|
||||||
server->name,
|
server->name,
|
||||||
service_user,
|
service_user,
|
||||||
@ -320,35 +484,57 @@ getUsers(SERVICE *service, USERS *users)
|
|||||||
LOGIF(LE, (skygw_log_write_flush(
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
LOGFILE_ERROR,
|
LOGFILE_ERROR,
|
||||||
"Error : Unable to get user data from backend database "
|
"Error : Unable to get user data from backend database "
|
||||||
"for service %s. Missing server information.",
|
"for service [%s]. Missing server information.",
|
||||||
service->name)));
|
service->name)));
|
||||||
mysql_close(con);
|
mysql_close(con);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mysql_query(con, MYSQL_USERS_COUNT)) {
|
/* count users */
|
||||||
LOGIF(LE, (skygw_log_write_flush(
|
|
||||||
LOGFILE_ERROR,
|
/* start with users and db grants for users */
|
||||||
"Error : Loading users for service %s encountered "
|
if (mysql_query(con, MYSQL_USERS_WITH_DB_COUNT)) {
|
||||||
"error: %s.",
|
if (1142 != mysql_errno(con)) {
|
||||||
service->name,
|
/* This is an error we cannot handle, return */
|
||||||
mysql_error(con))));
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
mysql_close(con);
|
LOGFILE_ERROR,
|
||||||
return -1;
|
"Error : Loading users for service [%s] encountered "
|
||||||
|
"error: [%s].",
|
||||||
|
service->name,
|
||||||
|
mysql_error(con))));
|
||||||
|
mysql_close(con);
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* We have got ER_TABLEACCESS_DENIED_ERROR
|
||||||
|
* try counting users from mysql.user without DB names.
|
||||||
|
*/
|
||||||
|
if (mysql_query(con, MYSQL_USERS_COUNT)) {
|
||||||
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
|
LOGFILE_ERROR,
|
||||||
|
"Error : Loading users for service [%s] encountered "
|
||||||
|
"error: [%s].",
|
||||||
|
service->name,
|
||||||
|
mysql_error(con))));
|
||||||
|
mysql_close(con);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result = mysql_store_result(con);
|
result = mysql_store_result(con);
|
||||||
|
|
||||||
if (result == NULL) {
|
if (result == NULL) {
|
||||||
LOGIF(LE, (skygw_log_write_flush(
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
LOGFILE_ERROR,
|
LOGFILE_ERROR,
|
||||||
"Error : Loading users for service %s encountered "
|
"Error : Loading users for service [%s] encountered "
|
||||||
"error: %s.",
|
"error: [%s].",
|
||||||
service->name,
|
service->name,
|
||||||
mysql_error(con))));
|
mysql_error(con))));
|
||||||
mysql_close(con);
|
mysql_close(con);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
num_fields = mysql_num_fields(result);
|
|
||||||
row = mysql_fetch_row(result);
|
row = mysql_fetch_row(result);
|
||||||
|
|
||||||
nusers = atoi(row[0]);
|
nusers = atoi(row[0]);
|
||||||
@ -364,15 +550,80 @@ getUsers(SERVICE *service, USERS *users)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(service->enable_root) {
|
||||||
|
/* enable_root for MySQL protocol module means load the root user credentials from backend databases */
|
||||||
|
users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY;
|
||||||
|
} else {
|
||||||
|
users_query = LOAD_MYSQL_USERS_WITH_DB_QUERY_NO_ROOT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* send first the query that fetches users and db grants */
|
||||||
if (mysql_query(con, users_query)) {
|
if (mysql_query(con, users_query)) {
|
||||||
LOGIF(LE, (skygw_log_write_flush(
|
/*
|
||||||
LOGFILE_ERROR,
|
* An error occurred executing the query
|
||||||
"Error : Loading users for service %s encountered "
|
*
|
||||||
"error: %s.",
|
* Check mysql_errno() against ER_TABLEACCESS_DENIED_ERROR)
|
||||||
service->name,
|
*/
|
||||||
mysql_error(con))));
|
|
||||||
mysql_close(con);
|
if (1142 != mysql_errno(con)) {
|
||||||
return -1;
|
/* This is an error we cannot handle, return */
|
||||||
|
|
||||||
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
|
LOGFILE_ERROR,
|
||||||
|
"Error : Loading users with dbnames for service [%s] encountered "
|
||||||
|
"error: [%s], MySQL errno %i",
|
||||||
|
service->name,
|
||||||
|
mysql_error(con),
|
||||||
|
mysql_errno(con))));
|
||||||
|
|
||||||
|
mysql_close(con);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* We have got ER_TABLEACCESS_DENIED_ERROR
|
||||||
|
* try loading users from mysql.user without DB names.
|
||||||
|
*/
|
||||||
|
|
||||||
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
|
LOGFILE_ERROR,
|
||||||
|
"Error: Loading DB grants failed: GRANT is required on [mysql.db] to user [%s]. Try loading DB users for service [%s] without DB name MaxScale Authentication", service_user, service->name)));
|
||||||
|
|
||||||
|
/* check for root user select */
|
||||||
|
if(service->enable_root) {
|
||||||
|
users_query = LOAD_MYSQL_USERS_QUERY " ORDER BY HOST DESC";
|
||||||
|
} else {
|
||||||
|
users_query = LOAD_MYSQL_USERS_QUERY USERS_QUERY_NO_ROOT " ORDER BY HOST DESC";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mysql_query(con, users_query)) {
|
||||||
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
|
LOGFILE_ERROR,
|
||||||
|
"Error : Loading users for service [%s] encountered "
|
||||||
|
"error: [%s], code %i",
|
||||||
|
service->name,
|
||||||
|
mysql_error(con),
|
||||||
|
mysql_errno(con))));
|
||||||
|
|
||||||
|
mysql_close(con);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* users successfully loaded but without db grants */
|
||||||
|
|
||||||
|
LOGIF(LM, (skygw_log_write_flush(
|
||||||
|
LOGFILE_MESSAGE,
|
||||||
|
"Loading users from [mysql.user] without DB grants from [mysql.db] for service [%s]."
|
||||||
|
" MaxScale Authentication with DBname on connect will not work",
|
||||||
|
service->name)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* users successfully loaded with db grants.
|
||||||
|
*/
|
||||||
|
|
||||||
|
db_grants = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
result = mysql_store_result(con);
|
result = mysql_store_result(con);
|
||||||
@ -384,34 +635,96 @@ getUsers(SERVICE *service, USERS *users)
|
|||||||
"error: %s.",
|
"error: %s.",
|
||||||
service->name,
|
service->name,
|
||||||
mysql_error(con))));
|
mysql_error(con))));
|
||||||
|
|
||||||
|
mysql_free_result(result);
|
||||||
mysql_close(con);
|
mysql_close(con);
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
num_fields = mysql_num_fields(result);
|
|
||||||
|
|
||||||
users_data = (char *)calloc(nusers, (users_data_row_len * sizeof(char)) + 1);
|
users_data = (char *)calloc(nusers, (users_data_row_len * sizeof(char)) + 1);
|
||||||
|
|
||||||
if(users_data == NULL)
|
if(users_data == NULL) {
|
||||||
return -1;
|
mysql_free_result(result);
|
||||||
|
mysql_close(con);
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db_grants) {
|
||||||
|
/* load all mysql database names */
|
||||||
|
dbnames = getDatabases(service, con);
|
||||||
|
|
||||||
|
LOGIF(LM, (skygw_log_write(
|
||||||
|
LOGFILE_MESSAGE,
|
||||||
|
"Loaded %d MySQL Database Names for service [%s]",
|
||||||
|
dbnames,
|
||||||
|
service->name)));
|
||||||
|
} else {
|
||||||
|
service->resources = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((row = mysql_fetch_row(result))) {
|
||||||
|
|
||||||
while ((row = mysql_fetch_row(result))) {
|
|
||||||
/**
|
/**
|
||||||
* Four fields should be returned.
|
* Up to six fields could be returned.
|
||||||
* user and passwd+1 (escaping the first byte that is '*') are
|
* user,host,passwd,concat(),anydb,db
|
||||||
* added to hashtable.
|
* passwd+1 (escaping the first byte that is '*')
|
||||||
*/
|
*/
|
||||||
|
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
|
char *password = NULL;
|
||||||
|
if (row[2] != NULL) {
|
||||||
|
if (strlen(row[2]) > 1)
|
||||||
|
password = row[2] +1;
|
||||||
|
else
|
||||||
|
password = row[2];
|
||||||
|
}
|
||||||
|
|
||||||
rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], strlen(row[2]) ? row[2]+1 : row[2]);
|
/*
|
||||||
|
* add user@host and DB global priv and specificsa grant (if possible)
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (db_grants) {
|
||||||
|
/* we have dbgrants, store them */
|
||||||
|
rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, row[4], row[5]);
|
||||||
|
} else {
|
||||||
|
/* we don't have dbgrants, simply set ANY DB for the user */
|
||||||
|
rc = add_mysql_users_with_host_ipv4(users, row[0], row[1], password, "Y", NULL);
|
||||||
|
}
|
||||||
|
|
||||||
if (rc == 1) {
|
if (rc == 1) {
|
||||||
LOGIF(LD, (skygw_log_write_flush(
|
if (db_grants) {
|
||||||
LOGFILE_DEBUG,
|
char dbgrant[MYSQL_DATABASE_MAXLEN + 1]="";
|
||||||
"%lu [mysql_users_add()] Added user %s@%s",
|
if (row[4] != NULL) {
|
||||||
pthread_self(),
|
if (strcmp(row[4], "Y"))
|
||||||
row[0],
|
strcpy(dbgrant, "ANY");
|
||||||
row[1])));
|
else {
|
||||||
|
if (row[5])
|
||||||
|
strncpy(dbgrant, row[5], MYSQL_DATABASE_MAXLEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!strlen(dbgrant))
|
||||||
|
strcpy(dbgrant, "no db");
|
||||||
|
|
||||||
|
/* Log the user being added with its db grants */
|
||||||
|
LOGIF(LD, (skygw_log_write_flush(
|
||||||
|
LOGFILE_DEBUG,
|
||||||
|
"%lu [mysql_users_add()] Added user %s@%s with DB grants on [%s]",
|
||||||
|
pthread_self(),
|
||||||
|
row[0],
|
||||||
|
row[1],
|
||||||
|
dbgrant)));
|
||||||
|
} else {
|
||||||
|
/* Log the user being added (without db grants) */
|
||||||
|
LOGIF(LD, (skygw_log_write_flush(
|
||||||
|
LOGFILE_DEBUG,
|
||||||
|
"%lu [mysql_users_add()] Added user %s@%s",
|
||||||
|
pthread_self(),
|
||||||
|
row[0],
|
||||||
|
row[1])));
|
||||||
|
}
|
||||||
|
|
||||||
/* Append data in the memory area for SHA1 digest */
|
/* Append data in the memory area for SHA1 digest */
|
||||||
strncat(users_data, row[3], users_data_row_len);
|
strncat(users_data, row[3], users_data_row_len);
|
||||||
@ -420,10 +733,11 @@ getUsers(SERVICE *service, USERS *users)
|
|||||||
} else {
|
} else {
|
||||||
LOGIF(LE, (skygw_log_write_flush(
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
LOGFILE_ERROR,
|
LOGFILE_ERROR,
|
||||||
"%lu [mysql_users_add()] Failed adding user %s@%s",
|
"%lu [mysql_users_add()] Failed adding user %s@%s for service [%s]",
|
||||||
pthread_self(),
|
pthread_self(),
|
||||||
row[0],
|
row[0],
|
||||||
row[1])));
|
row[1],
|
||||||
|
service->name)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,7 +816,7 @@ char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key) {
|
|||||||
if (key == NULL)
|
if (key == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
atomic_add(&users->stats.n_fetches, 1);
|
atomic_add(&users->stats.n_fetches, 1);
|
||||||
return hashtable_fetch(users->data, key);
|
return hashtable_fetch(users->data, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -536,11 +850,37 @@ static int uh_cmpfun( void* v1, void* v2) {
|
|||||||
MYSQL_USER_HOST *hu1 = (MYSQL_USER_HOST *) v1;
|
MYSQL_USER_HOST *hu1 = (MYSQL_USER_HOST *) v1;
|
||||||
MYSQL_USER_HOST *hu2 = (MYSQL_USER_HOST *) v2;
|
MYSQL_USER_HOST *hu2 = (MYSQL_USER_HOST *) v2;
|
||||||
|
|
||||||
if (v1 == NULL || v2 == NULL || hu1 == NULL || hu2 == NULL || hu1->user == NULL || hu2->user == NULL)
|
if (v1 == NULL || v2 == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (strcmp(hu1->user, hu2->user) == 0 && (hu1->ipv4.sin_addr.s_addr == hu2->ipv4.sin_addr.s_addr) && (hu1->netmask >= hu2->netmask)) {
|
if (hu1 == NULL || hu2 == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
if (hu1->user == NULL || hu2->user == NULL)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (strcmp(hu1->user, hu2->user) == 0 && (hu1->ipv4.sin_addr.s_addr == hu2->ipv4.sin_addr.s_addr) && (hu1->netmask >= hu2->netmask)) {
|
||||||
|
|
||||||
|
/* if no database name was passed, auth is ok */
|
||||||
|
if (hu1->resource == NULL || (hu1->resource && !strlen(hu1->resource))) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
/* (1) check for no database grants at all and deny auth */
|
||||||
|
if (hu2->resource == NULL) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
/* (2) check for ANY database grant and allow auth */
|
||||||
|
if (!strlen(hu2->resource)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* (3) check for database name specific grant and allow auth */
|
||||||
|
if (hu1->resource && hu2->resource && strcmp(hu1->resource,hu2->resource) == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* no matches, deny auth */
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@ -568,6 +908,9 @@ static void *uh_keydup(void* key) {
|
|||||||
memcpy(&rval->ipv4, ¤t_key->ipv4, sizeof(struct sockaddr_in));
|
memcpy(&rval->ipv4, ¤t_key->ipv4, sizeof(struct sockaddr_in));
|
||||||
memcpy(&rval->netmask, ¤t_key->netmask, sizeof(int));
|
memcpy(&rval->netmask, ¤t_key->netmask, sizeof(int));
|
||||||
|
|
||||||
|
if (current_key->resource)
|
||||||
|
rval->resource = strdup(current_key->resource);
|
||||||
|
|
||||||
return (void *) rval;
|
return (void *) rval;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,6 +928,9 @@ static void uh_keyfree( void* key) {
|
|||||||
if (current_key && current_key->user)
|
if (current_key && current_key->user)
|
||||||
free(current_key->user);
|
free(current_key->user);
|
||||||
|
|
||||||
|
if (current_key && current_key->resource)
|
||||||
|
free(current_key->resource);
|
||||||
|
|
||||||
free(key);
|
free(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -600,7 +946,7 @@ char *mysql_format_user_entry(void *data)
|
|||||||
MYSQL_USER_HOST *entry;
|
MYSQL_USER_HOST *entry;
|
||||||
char *mysql_user;
|
char *mysql_user;
|
||||||
/* the returned user string is "USER" + "@" + "HOST" + '\0' */
|
/* the returned user string is "USER" + "@" + "HOST" + '\0' */
|
||||||
int mysql_user_len = MYSQL_USER_MAXLEN + 1 + INET_ADDRSTRLEN + 1;
|
int mysql_user_len = MYSQL_USER_MAXLEN + 1 + INET_ADDRSTRLEN + 10 + MYSQL_USER_MAXLEN + 1;
|
||||||
|
|
||||||
if (data == NULL)
|
if (data == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -614,6 +960,8 @@ char *mysql_format_user_entry(void *data)
|
|||||||
|
|
||||||
if (mysql_user == NULL)
|
if (mysql_user == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
/* format user@host based on wildcards */
|
||||||
|
|
||||||
if (entry->ipv4.sin_addr.s_addr == INADDR_ANY && entry->netmask == 0) {
|
if (entry->ipv4.sin_addr.s_addr == INADDR_ANY && entry->netmask == 0) {
|
||||||
snprintf(mysql_user, mysql_user_len-1, "%s@%%", entry->user);
|
snprintf(mysql_user, mysql_user_len-1, "%s@%%", entry->user);
|
||||||
@ -628,12 +976,83 @@ char *mysql_format_user_entry(void *data)
|
|||||||
strcat(mysql_user, "@");
|
strcat(mysql_user, "@");
|
||||||
inet_ntop(AF_INET, &(entry->ipv4).sin_addr, mysql_user+strlen(mysql_user), INET_ADDRSTRLEN);
|
inet_ntop(AF_INET, &(entry->ipv4).sin_addr, mysql_user+strlen(mysql_user), INET_ADDRSTRLEN);
|
||||||
} else {
|
} else {
|
||||||
snprintf(mysql_user, MYSQL_USER_MAXLEN-6, "warn: %s", entry->user);
|
snprintf(mysql_user, MYSQL_USER_MAXLEN-5, "Err: %s", entry->user);
|
||||||
strcat(mysql_user, "@");
|
strcat(mysql_user, "@");
|
||||||
inet_ntop(AF_INET, &(entry->ipv4).sin_addr, mysql_user+strlen(mysql_user), INET_ADDRSTRLEN);
|
inet_ntop(AF_INET, &(entry->ipv4).sin_addr, mysql_user+strlen(mysql_user), INET_ADDRSTRLEN);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return mysql_user;
|
return mysql_user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The hash function we use for storing MySQL database names.
|
||||||
|
*
|
||||||
|
* @param key The key value
|
||||||
|
* @return The hash key
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
resource_hash(char *key)
|
||||||
|
{
|
||||||
|
return (*key + *(key + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the resources table
|
||||||
|
*
|
||||||
|
* @param resources The resources table to remove
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
resource_free(HASHTABLE *resources)
|
||||||
|
{
|
||||||
|
if (resources) {
|
||||||
|
hashtable_free(resources);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate a MySQL database names table
|
||||||
|
*
|
||||||
|
* @return The database names table
|
||||||
|
*/
|
||||||
|
HASHTABLE *
|
||||||
|
resource_alloc()
|
||||||
|
{
|
||||||
|
HASHTABLE *resources;
|
||||||
|
|
||||||
|
if ((resources = hashtable_alloc(10, resource_hash, strcmp)) == NULL)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
hashtable_memory_fns(resources, (HASHMEMORYFN)strdup, (HASHMEMORYFN)strdup, (HASHMEMORYFN)free, (HASHMEMORYFN)free);
|
||||||
|
|
||||||
|
return resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new MySQL database name to the resources table. The resource name must be unique
|
||||||
|
*
|
||||||
|
* @param resources The resources table
|
||||||
|
* @param key The resource name
|
||||||
|
* @param value The value for resource (not used)
|
||||||
|
* @return The number of resources dded to the table
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
resource_add(HASHTABLE *resources, char *key, char *value)
|
||||||
|
{
|
||||||
|
return hashtable_add(resources, key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a particular database name from the resources table
|
||||||
|
*
|
||||||
|
* @param resources The MySQL database names table
|
||||||
|
* @param key The database name to fetch
|
||||||
|
* @return The database esists or NULL if not found
|
||||||
|
*/
|
||||||
|
void *
|
||||||
|
resource_fetch(HASHTABLE *resources, char *key)
|
||||||
|
{
|
||||||
|
return hashtable_fetch(resources, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,8 @@
|
|||||||
*
|
*
|
||||||
* @param hint The hint list to duplicate
|
* @param hint The hint list to duplicate
|
||||||
* @return A duplicate of the list
|
* @return A duplicate of the list
|
||||||
|
*
|
||||||
|
* Note : Optimize this to use version numbering instead of copying memory
|
||||||
*/
|
*/
|
||||||
HINT *
|
HINT *
|
||||||
hint_dup(HINT *hint)
|
hint_dup(HINT *hint)
|
||||||
|
@ -22,8 +22,9 @@
|
|||||||
* @verbatim
|
* @verbatim
|
||||||
* Revision History
|
* Revision History
|
||||||
*
|
*
|
||||||
* Date Who Description
|
* Date Who Description
|
||||||
* 04/06/14 Mark Riddoch Initial implementation
|
* 04/06/14 Mark Riddoch Initial implementation
|
||||||
|
* 24/10/14 Massimiliano Pinto Added modutil_send_mysql_err_packet, modutil_create_mysql_err_msg
|
||||||
*
|
*
|
||||||
* @endverbatim
|
* @endverbatim
|
||||||
*/
|
*/
|
||||||
@ -167,6 +168,7 @@ GWBUF *addition;
|
|||||||
*ptr++ = (newlength + 1) & 0xff;
|
*ptr++ = (newlength + 1) & 0xff;
|
||||||
*ptr++ = ((newlength + 1) >> 8) & 0xff;
|
*ptr++ = ((newlength + 1) >> 8) & 0xff;
|
||||||
*ptr++ = ((newlength + 1) >> 16) & 0xff;
|
*ptr++ = ((newlength + 1) >> 16) & 0xff;
|
||||||
|
addition->gwbuf_type = orig->gwbuf_type;
|
||||||
orig->next = addition;
|
orig->next = addition;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,3 +228,118 @@ modutil_get_query(GWBUF *buf)
|
|||||||
retblock:
|
retblock:
|
||||||
return query_str;
|
return query_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create a GWBUFF with a MySQL ERR packet
|
||||||
|
*
|
||||||
|
* @param packet_number MySQL protocol sequence number in the packet
|
||||||
|
* @param in_affected_rows MySQL affected rows
|
||||||
|
* @param mysql_errno The MySQL errno
|
||||||
|
* @param sqlstate_msg The MySQL State Message
|
||||||
|
* @param mysql_message The Error Message
|
||||||
|
* @return The allocated GWBUF or NULL on failure
|
||||||
|
*/
|
||||||
|
GWBUF *modutil_create_mysql_err_msg(
|
||||||
|
int packet_number,
|
||||||
|
int affected_rows,
|
||||||
|
int merrno,
|
||||||
|
const char *statemsg,
|
||||||
|
const char * msg)
|
||||||
|
{
|
||||||
|
uint8_t *outbuf = NULL;
|
||||||
|
uint8_t mysql_payload_size = 0;
|
||||||
|
uint8_t mysql_packet_header[4];
|
||||||
|
uint8_t *mysql_payload = NULL;
|
||||||
|
uint8_t field_count = 0;
|
||||||
|
uint8_t mysql_err[2];
|
||||||
|
uint8_t mysql_statemsg[6];
|
||||||
|
unsigned int mysql_errno = 0;
|
||||||
|
const char *mysql_error_msg = NULL;
|
||||||
|
const char *mysql_state = NULL;
|
||||||
|
GWBUF *errbuf = NULL;
|
||||||
|
|
||||||
|
mysql_errno = (unsigned int)merrno;
|
||||||
|
mysql_error_msg = msg;
|
||||||
|
mysql_state = statemsg;
|
||||||
|
|
||||||
|
field_count = 0xff;
|
||||||
|
|
||||||
|
gw_mysql_set_byte2(mysql_err, mysql_errno);
|
||||||
|
|
||||||
|
mysql_statemsg[0]='#';
|
||||||
|
memcpy(mysql_statemsg+1, mysql_state, 5);
|
||||||
|
|
||||||
|
if (msg != NULL) {
|
||||||
|
mysql_error_msg = msg;
|
||||||
|
}
|
||||||
|
mysql_payload_size = sizeof(field_count) +
|
||||||
|
sizeof(mysql_err) +
|
||||||
|
sizeof(mysql_statemsg) +
|
||||||
|
strlen(mysql_error_msg);
|
||||||
|
|
||||||
|
/* allocate memory for packet header + payload */
|
||||||
|
errbuf = gwbuf_alloc(sizeof(mysql_packet_header) + mysql_payload_size);
|
||||||
|
ss_dassert(errbuf != NULL);
|
||||||
|
|
||||||
|
if (errbuf == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
outbuf = GWBUF_DATA(errbuf);
|
||||||
|
|
||||||
|
/** write packet header and packet number */
|
||||||
|
gw_mysql_set_byte3(mysql_packet_header, mysql_payload_size);
|
||||||
|
mysql_packet_header[3] = packet_number;
|
||||||
|
|
||||||
|
/** write header */
|
||||||
|
memcpy(outbuf, mysql_packet_header, sizeof(mysql_packet_header));
|
||||||
|
|
||||||
|
mysql_payload = outbuf + sizeof(mysql_packet_header);
|
||||||
|
|
||||||
|
/** write field */
|
||||||
|
memcpy(mysql_payload, &field_count, sizeof(field_count));
|
||||||
|
mysql_payload = mysql_payload + sizeof(field_count);
|
||||||
|
|
||||||
|
/** write errno */
|
||||||
|
memcpy(mysql_payload, mysql_err, sizeof(mysql_err));
|
||||||
|
mysql_payload = mysql_payload + sizeof(mysql_err);
|
||||||
|
|
||||||
|
/** write sqlstate */
|
||||||
|
memcpy(mysql_payload, mysql_statemsg, sizeof(mysql_statemsg));
|
||||||
|
mysql_payload = mysql_payload + sizeof(mysql_statemsg);
|
||||||
|
|
||||||
|
/** write error message */
|
||||||
|
memcpy(mysql_payload, mysql_error_msg, strlen(mysql_error_msg));
|
||||||
|
|
||||||
|
return errbuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* modutil_send_mysql_err_packet
|
||||||
|
*
|
||||||
|
* Send a MySQL protocol Generic ERR message, to the dcb
|
||||||
|
*
|
||||||
|
* @param dcb The DCB to send the packet
|
||||||
|
* @param packet_number MySQL protocol sequence number in the packet
|
||||||
|
* @param in_affected_rows MySQL affected rows
|
||||||
|
* @param mysql_errno The MySQL errno
|
||||||
|
* @param sqlstate_msg The MySQL State Message
|
||||||
|
* @param mysql_message The Error Message
|
||||||
|
* @return 0 for successful dcb write or 1 on failure
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
int modutil_send_mysql_err_packet (
|
||||||
|
DCB *dcb,
|
||||||
|
int packet_number,
|
||||||
|
int in_affected_rows,
|
||||||
|
int mysql_errno,
|
||||||
|
const char *sqlstate_msg,
|
||||||
|
const char *mysql_message)
|
||||||
|
{
|
||||||
|
GWBUF* buf;
|
||||||
|
|
||||||
|
buf = modutil_create_mysql_err_msg(packet_number, in_affected_rows, mysql_errno, sqlstate_msg, mysql_message);
|
||||||
|
|
||||||
|
return dcb->func.write(dcb, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -65,6 +65,7 @@ MAXKEYS *keys;
|
|||||||
struct stat secret_stats;
|
struct stat secret_stats;
|
||||||
int fd;
|
int fd;
|
||||||
int len;
|
int len;
|
||||||
|
static int reported = 0;
|
||||||
|
|
||||||
home = getenv("MAXSCALE_HOME");
|
home = getenv("MAXSCALE_HOME");
|
||||||
|
|
||||||
@ -80,12 +81,16 @@ int len;
|
|||||||
errno = 0;
|
errno = 0;
|
||||||
if (eno == ENOENT)
|
if (eno == ENOENT)
|
||||||
{
|
{
|
||||||
LOGIF(LM, (skygw_log_write(
|
if (!reported)
|
||||||
|
{
|
||||||
|
LOGIF(LM, (skygw_log_write(
|
||||||
LOGFILE_MESSAGE,
|
LOGFILE_MESSAGE,
|
||||||
"Encrypted password file %s can't be accessed "
|
"Encrypted password file %s can't be accessed "
|
||||||
"(%s). Password encryption is not used.",
|
"(%s). Password encryption is not used.",
|
||||||
secret_file,
|
secret_file,
|
||||||
strerror(eno))));
|
strerror(eno))));
|
||||||
|
reported = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
* 23/05/14 Mark Riddoch Addition of service validation call
|
* 23/05/14 Mark Riddoch Addition of service validation call
|
||||||
* 29/05/14 Mark Riddoch Filter API implementation
|
* 29/05/14 Mark Riddoch Filter API implementation
|
||||||
* 09/09/14 Massimiliano Pinto Added service option for localhost authentication
|
* 09/09/14 Massimiliano Pinto Added service option for localhost authentication
|
||||||
|
* 13/10/14 Massimiliano Pinto Added hashtable for resources (i.e database names for MySQL services)
|
||||||
*
|
*
|
||||||
* @endverbatim
|
* @endverbatim
|
||||||
*/
|
*/
|
||||||
@ -137,6 +138,7 @@ SERVICE *service;
|
|||||||
service->filters = NULL;
|
service->filters = NULL;
|
||||||
service->n_filters = 0;
|
service->n_filters = 0;
|
||||||
service->weightby = 0;
|
service->weightby = 0;
|
||||||
|
service->resources = NULL;
|
||||||
spinlock_init(&service->spin);
|
spinlock_init(&service->spin);
|
||||||
spinlock_init(&service->users_table_spin);
|
spinlock_init(&service->users_table_spin);
|
||||||
memset(&service->rate_limit, 0, sizeof(SERVICE_REFRESH_RATE));
|
memset(&service->rate_limit, 0, sizeof(SERVICE_REFRESH_RATE));
|
||||||
@ -198,9 +200,14 @@ GWPROTOCOL *funcs;
|
|||||||
}
|
}
|
||||||
if (strcmp(port->protocol, "MySQLClient") == 0) {
|
if (strcmp(port->protocol, "MySQLClient") == 0) {
|
||||||
int loaded;
|
int loaded;
|
||||||
/* Allocate specific data for MySQL users */
|
|
||||||
|
/*
|
||||||
|
* Allocate specific data for MySQL users
|
||||||
|
* including hosts and db names
|
||||||
|
*/
|
||||||
service->users = mysql_users_alloc();
|
service->users = mysql_users_alloc();
|
||||||
loaded = load_mysql_users(service);
|
loaded = load_mysql_users(service);
|
||||||
|
|
||||||
/* At service start last update is set to USERS_REFRESH_TIME seconds earlier.
|
/* At service start last update is set to USERS_REFRESH_TIME seconds earlier.
|
||||||
* This way MaxScale could try reloading users' just after startup
|
* This way MaxScale could try reloading users' just after startup
|
||||||
*/
|
*/
|
||||||
@ -209,9 +216,9 @@ GWPROTOCOL *funcs;
|
|||||||
service->rate_limit.nloads=1;
|
service->rate_limit.nloads=1;
|
||||||
|
|
||||||
LOGIF(LM, (skygw_log_write(
|
LOGIF(LM, (skygw_log_write(
|
||||||
LOGFILE_MESSAGE,
|
LOGFILE_MESSAGE,
|
||||||
"Loaded %d MySQL Users.",
|
"Loaded %d MySQL Users for service [%s].",
|
||||||
loaded)));
|
loaded, service->name)));
|
||||||
} else {
|
} else {
|
||||||
/* Generic users table */
|
/* Generic users table */
|
||||||
service->users = users_alloc();
|
service->users = users_alloc();
|
||||||
|
@ -11,7 +11,7 @@ add_executable(test_service testservice.c)
|
|||||||
add_executable(test_server testserver.c)
|
add_executable(test_server testserver.c)
|
||||||
add_executable(test_users testusers.c)
|
add_executable(test_users testusers.c)
|
||||||
add_executable(test_adminusers testadminusers.c)
|
add_executable(test_adminusers testadminusers.c)
|
||||||
target_link_libraries(test_mysql_users fullcore MySQLClient)
|
target_link_libraries(test_mysql_users MySQLClient fullcore)
|
||||||
target_link_libraries(test_hash fullcore)
|
target_link_libraries(test_hash fullcore)
|
||||||
target_link_libraries(test_hint fullcore)
|
target_link_libraries(test_hint fullcore)
|
||||||
target_link_libraries(test_spinlock fullcore)
|
target_link_libraries(test_spinlock fullcore)
|
||||||
@ -37,4 +37,3 @@ add_test(TestService test_service)
|
|||||||
add_test(TestServer test_server)
|
add_test(TestServer test_server)
|
||||||
add_test(TestUsers test_users)
|
add_test(TestUsers test_users)
|
||||||
add_test(TestAdminUsers test_adminusers)
|
add_test(TestAdminUsers test_adminusers)
|
||||||
|
|
||||||
|
@ -42,6 +42,13 @@ testhash: testhash.c
|
|||||||
-I$(ROOT_PATH)/utils \
|
-I$(ROOT_PATH)/utils \
|
||||||
testhash.c ../hashtable.o ../atomic.o ../spinlock.o -o testhash
|
testhash.c ../hashtable.o ../atomic.o ../spinlock.o -o testhash
|
||||||
|
|
||||||
|
testmysqlusers: test_mysql_users.c
|
||||||
|
$(CC) $(CFLAGS) \
|
||||||
|
-I$(ROOT_PATH)/server/include \
|
||||||
|
-I$(ROOT_PATH)/utils \
|
||||||
|
test_mysql_users.c ../hashtable.o ../atomic.o ../modutil.o ../spinlock.o -o testmysqlusers
|
||||||
|
|
||||||
|
|
||||||
testspinlock: testspinlock.c
|
testspinlock: testspinlock.c
|
||||||
$(CC) $(CFLAGS) \
|
$(CC) $(CFLAGS) \
|
||||||
-I$(ROOT_PATH)/server/include \
|
-I$(ROOT_PATH)/server/include \
|
||||||
|
@ -40,6 +40,7 @@
|
|||||||
#include <log_manager.h>
|
#include <log_manager.h>
|
||||||
#include <secrets.h>
|
#include <secrets.h>
|
||||||
#include <dbusers.h>
|
#include <dbusers.h>
|
||||||
|
#include <mysql_client_server_protocol.h>
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
@ -52,9 +53,24 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char
|
|||||||
USERS *mysql_users;
|
USERS *mysql_users;
|
||||||
char ret_ip[200]="";
|
char ret_ip[200]="";
|
||||||
char *fetch_data;
|
char *fetch_data;
|
||||||
|
char *db="";
|
||||||
|
DCB *dcb;
|
||||||
|
SERVICE *service;
|
||||||
|
|
||||||
unsigned long fix_ipv4;
|
unsigned long fix_ipv4;
|
||||||
|
|
||||||
|
dcb = dcb_alloc(DCB_ROLE_INTERNAL);
|
||||||
|
|
||||||
|
if (dcb == NULL) {
|
||||||
|
fprintf(stderr, "dcb_alloc() failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if ((service = (SERVICE *)calloc(1, sizeof(SERVICE))) == NULL) {
|
||||||
|
fprintf(stderr, "service_alloc() failed\n");
|
||||||
|
dcb_free(dcb);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (ipv4 > UINT_MAX) {
|
if (ipv4 > UINT_MAX) {
|
||||||
fix_ipv4 = UINT_MAX;
|
fix_ipv4 = UINT_MAX;
|
||||||
} else {
|
} else {
|
||||||
@ -70,6 +86,7 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char
|
|||||||
|
|
||||||
key.user = username;
|
key.user = username;
|
||||||
memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr));
|
memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr));
|
||||||
|
key.resource = db;
|
||||||
|
|
||||||
inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN);
|
inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN);
|
||||||
|
|
||||||
@ -79,6 +96,8 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char
|
|||||||
if (!mysql_users_add(mysql_users, &key, password)) {
|
if (!mysql_users_add(mysql_users, &key, password)) {
|
||||||
fprintf(stderr, "Failed adding %s@%s(%lu)\n", username, ret_ip, fix_ipv4);
|
fprintf(stderr, "Failed adding %s@%s(%lu)\n", username, ret_ip, fix_ipv4);
|
||||||
users_free(mysql_users);
|
users_free(mysql_users);
|
||||||
|
free(service);
|
||||||
|
dcb_free(dcb);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,12 +106,15 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char
|
|||||||
|
|
||||||
find_key.user = username;
|
find_key.user = username;
|
||||||
memcpy(&(serv_addr).sin_addr.s_addr, &ipv4, sizeof(ipv4));
|
memcpy(&(serv_addr).sin_addr.s_addr, &ipv4, sizeof(ipv4));
|
||||||
|
find_key.resource = db;
|
||||||
|
|
||||||
memcpy(&find_key.ipv4, &serv_addr, sizeof(serv_addr));
|
memcpy(&find_key.ipv4, &serv_addr, sizeof(serv_addr));
|
||||||
|
|
||||||
fetch_data = mysql_users_fetch(mysql_users, &find_key);
|
fetch_data = mysql_users_fetch(mysql_users, &find_key);
|
||||||
|
|
||||||
users_free(mysql_users);
|
users_free(mysql_users);
|
||||||
|
free(service);
|
||||||
|
dcb_free(dcb);
|
||||||
|
|
||||||
if (!fetch_data)
|
if (!fetch_data)
|
||||||
return 1;
|
return 1;
|
||||||
@ -103,10 +125,10 @@ int set_and_get_single_mysql_users_ipv4(char *username, unsigned long ipv4, char
|
|||||||
int set_and_get_single_mysql_users(char *username, char *hostname, char *password) {
|
int set_and_get_single_mysql_users(char *username, char *hostname, char *password) {
|
||||||
struct sockaddr_in serv_addr;
|
struct sockaddr_in serv_addr;
|
||||||
MYSQL_USER_HOST key;
|
MYSQL_USER_HOST key;
|
||||||
MYSQL_USER_HOST find_key;
|
|
||||||
USERS *mysql_users;
|
USERS *mysql_users;
|
||||||
char ret_ip[200]="";
|
char ret_ip[200]="";
|
||||||
char *fetch_data;
|
char *fetch_data;
|
||||||
|
char *db="";
|
||||||
|
|
||||||
mysql_users = mysql_users_alloc();
|
mysql_users = mysql_users_alloc();
|
||||||
|
|
||||||
@ -125,6 +147,7 @@ int set_and_get_single_mysql_users(char *username, char *hostname, char *passwor
|
|||||||
key.user = username;
|
key.user = username;
|
||||||
|
|
||||||
memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr));
|
memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr));
|
||||||
|
key.resource = db;
|
||||||
|
|
||||||
inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN);
|
inet_ntop(AF_INET, &(serv_addr).sin_addr, ret_ip, INET_ADDRSTRLEN);
|
||||||
|
|
||||||
@ -138,7 +161,6 @@ int set_and_get_single_mysql_users(char *username, char *hostname, char *passwor
|
|||||||
}
|
}
|
||||||
|
|
||||||
memset(&serv_addr, 0, sizeof(serv_addr));
|
memset(&serv_addr, 0, sizeof(serv_addr));
|
||||||
memset(&find_key, 0, sizeof(key));
|
|
||||||
|
|
||||||
if (hostname)
|
if (hostname)
|
||||||
if(!setipaddress(&serv_addr.sin_addr, hostname)) {
|
if(!setipaddress(&serv_addr.sin_addr, hostname)) {
|
||||||
@ -148,6 +170,7 @@ int set_and_get_single_mysql_users(char *username, char *hostname, char *passwor
|
|||||||
}
|
}
|
||||||
key.user = username;
|
key.user = username;
|
||||||
memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr));
|
memcpy(&key.ipv4, &serv_addr, sizeof(serv_addr));
|
||||||
|
key.resource = db;
|
||||||
|
|
||||||
fetch_data = mysql_users_fetch(mysql_users, &key);
|
fetch_data = mysql_users_fetch(mysql_users, &key);
|
||||||
|
|
||||||
@ -159,23 +182,24 @@ int set_and_get_single_mysql_users(char *username, char *hostname, char *passwor
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *password, char *from) {
|
int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *password, char *from, char *anydb, char *db, char *db_from) {
|
||||||
USERS *mysql_users;
|
USERS *mysql_users;
|
||||||
int ret;
|
int ret = -1;
|
||||||
struct sockaddr_in client_addr;
|
struct sockaddr_in client_addr;
|
||||||
DCB *dcb;
|
DCB *dcb;
|
||||||
SERVICE *service;
|
SERVICE *service;
|
||||||
|
MYSQL_session *data;
|
||||||
|
|
||||||
dcb = dcb_alloc(DCB_ROLE_INTERNAL);
|
dcb = dcb_alloc(DCB_ROLE_INTERNAL);
|
||||||
|
|
||||||
if (dcb == NULL) {
|
if (dcb == NULL) {
|
||||||
fprintf(stderr, "dcb_alloc() failed\n");
|
fprintf(stderr, "dcb_alloc() failed\n");
|
||||||
return 1;
|
return ret;
|
||||||
}
|
}
|
||||||
if ((service = (SERVICE *)calloc(1, sizeof(SERVICE))) == NULL) {
|
if ((service = (SERVICE *)calloc(1, sizeof(SERVICE))) == NULL) {
|
||||||
fprintf(stderr, "service_alloc() failed\n");
|
fprintf(stderr, "service_alloc() failed\n");
|
||||||
dcb_free(dcb);
|
dcb_free(dcb);
|
||||||
return 1;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(&client_addr, 0, sizeof(client_addr));
|
memset(&client_addr, 0, sizeof(client_addr));
|
||||||
@ -185,10 +209,18 @@ int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *pass
|
|||||||
fprintf(stderr, "setipaddress failed for host [%s]\n", from);
|
fprintf(stderr, "setipaddress failed for host [%s]\n", from);
|
||||||
free(service);
|
free(service);
|
||||||
dcb_free(dcb);
|
dcb_free(dcb);
|
||||||
return 1;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ((data = (MYSQL_session *) calloc(1, sizeof(MYSQL_session))) == NULL) {
|
||||||
|
fprintf(stderr, "MYSQL_session alloc failed\n");
|
||||||
|
free(service);
|
||||||
|
dcb_free(dcb);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* client IPv4 in raw data*/
|
/* client IPv4 in raw data*/
|
||||||
memcpy(&dcb->ipv4, (struct sockaddr_in *)&client_addr, sizeof(struct sockaddr_in));
|
memcpy(&dcb->ipv4, (struct sockaddr_in *)&client_addr, sizeof(struct sockaddr_in));
|
||||||
|
|
||||||
@ -198,26 +230,36 @@ int set_and_get_mysql_users_wildcards(char *username, char *hostname, char *pass
|
|||||||
|
|
||||||
service->users = mysql_users;
|
service->users = mysql_users;
|
||||||
|
|
||||||
|
if (db_from != NULL)
|
||||||
|
strcpy(data->db, db_from);
|
||||||
|
else
|
||||||
|
strcpy(data->db, "");
|
||||||
|
|
||||||
|
/* freed by dcb_free(dcb) */
|
||||||
|
dcb->data = data;
|
||||||
|
|
||||||
// the routine returns 1 on success
|
// the routine returns 1 on success
|
||||||
ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password);
|
if (anydb != NULL) {
|
||||||
if (!ret) {
|
if (strcmp(anydb, "N") == 0) {
|
||||||
fprintf(stderr, "add_mysql_users_with_host_ipv4 passed(%s@%s, %s) FAILED\n", username, hostname, password);
|
ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, anydb, db);
|
||||||
users_free(mysql_users);
|
} else if (strcmp(anydb, "Y") == 0) {
|
||||||
free(service);
|
ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "Y", "");
|
||||||
dcb_free(dcb);
|
} else {
|
||||||
|
ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "N", NULL);
|
||||||
return 1;
|
}
|
||||||
} else {
|
} else {
|
||||||
char db_passwd[100]="";
|
ret = add_mysql_users_with_host_ipv4(mysql_users, username, hostname, password, "N", NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == 0) {
|
||||||
|
fprintf(stderr, "add_mysql_users_with_host_ipv4 (%s@%s, %s) FAILED\n", username, hostname, password);
|
||||||
|
} else {
|
||||||
|
unsigned char db_passwd[100]="";
|
||||||
|
|
||||||
dcb->remote=strdup(from);
|
dcb->remote=strdup(from);
|
||||||
//fprintf(stderr, "add_mysql_users_with_host_ipv4 passed(%s@%s, %s) OK\n", username, hostname, password);
|
|
||||||
|
|
||||||
fprintf(stderr, "Checking '%s' @ '%s' against (%s@%s)\n", username, from, username, hostname);
|
|
||||||
|
|
||||||
// returns 0 on success
|
// returns 0 on success
|
||||||
ret = gw_find_mysql_user_password_sha1(username, db_passwd, dcb);
|
ret = gw_find_mysql_user_password_sha1(username, db_passwd, dcb);
|
||||||
}
|
}
|
||||||
|
|
||||||
users_free(mysql_users);
|
users_free(mysql_users);
|
||||||
@ -239,6 +281,7 @@ int main() {
|
|||||||
fprintf(stderr, "%s\n", asctime(localtime(&t)));
|
fprintf(stderr, "%s\n", asctime(localtime(&t)));
|
||||||
fprintf(stderr, ">>> Started MySQL load, set & get users@host\n");
|
fprintf(stderr, ">>> Started MySQL load, set & get users@host\n");
|
||||||
|
|
||||||
|
|
||||||
ret = set_and_get_single_mysql_users("pippo", "localhost", "xyz");
|
ret = set_and_get_single_mysql_users("pippo", "localhost", "xyz");
|
||||||
assert(ret == 0);
|
assert(ret == 0);
|
||||||
ret = set_and_get_single_mysql_users("pippo", "127.0.0.2", "xyz");
|
ret = set_and_get_single_mysql_users("pippo", "127.0.0.2", "xyz");
|
||||||
@ -272,62 +315,91 @@ int main() {
|
|||||||
k++;
|
k++;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "%", "one", "127.0.0.1");
|
ret = set_and_get_mysql_users_wildcards("pippo", "%", "one", "127.0.0.1", NULL, NULL, NULL);
|
||||||
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
assert(ret == 1);
|
assert(ret == 1);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "%", "", "127.0.0.1");
|
ret = set_and_get_mysql_users_wildcards("pippo", "%", "", "127.0.0.1", NULL, NULL, NULL);
|
||||||
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
assert(ret == 1);
|
assert(ret == 1);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "%", "two", "192.168.2.2");
|
ret = set_and_get_mysql_users_wildcards("pippo", "%", "two", "192.168.2.2", NULL, NULL, NULL);
|
||||||
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
||||||
assert(ret == 0);
|
assert(ret == 0);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.168.1.%", "foo", "192.168.2.2");
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.168.4.%", "ffoo", "192.168.2.2", NULL, NULL, NULL);
|
||||||
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
assert(ret == 1);
|
assert(ret == 1);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.168.%.%", "foo", "192.168.2.2");
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.168.%.%", "foo", "192.168.2.2", NULL, NULL, NULL);
|
||||||
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
||||||
assert(ret == 0);
|
assert(ret == 0);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.68.0.2");
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.68.0.2", NULL, NULL, NULL);
|
||||||
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
||||||
assert(ret == 0);
|
assert(ret == 0);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2");
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2", "Y", NULL, "cossa");
|
||||||
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
||||||
assert(ret == 0);
|
assert(ret == 0);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.2.0.2");
|
fprintf(stderr, "Adding pippo, 192.%%.%%.%%, foo, 192.0.0.2, N, NULL, ragione\n");
|
||||||
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.0.0.2", "N", NULL, "ragione");
|
||||||
|
if (!ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
|
assert(ret == 1);
|
||||||
|
|
||||||
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.2.0.2", NULL, NULL, NULL);
|
||||||
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
assert(ret == 1);
|
assert(ret == 1);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.1", "foo", "192.0.0.2");
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.1", "foo", "192.0.0.2", NULL, NULL, NULL);
|
||||||
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
assert(ret == 1);
|
assert(ret == 1);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.1.0.2");
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.1.0.2", NULL, NULL, NULL);
|
||||||
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
assert(ret == 1);
|
assert(ret == 1);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.%", "y78764o", "192.3.2.1");
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.%", "foo", "192.3.2.1", NULL, NULL, NULL);
|
||||||
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
assert(ret == 1);
|
assert(ret == 1);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "1234567890123456789012345678901234567890", "192.3.2.1");
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.%.%", "foo", "192.3.2.1", "Y", NULL, NULL);
|
||||||
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
assert(ret == 1);
|
assert(ret == 1);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "1234567890123456789012345678901234567890f8__uuo5", "192.3.2.1");
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "N", "matto", "matto");
|
||||||
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
||||||
assert(ret == 0);
|
assert(ret == 0);
|
||||||
|
|
||||||
ret = set_and_get_mysql_users_wildcards("pippo", "192.0.0.%", "fo887778o", "192.134.0.2");
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "N", "matto", "fatto");
|
||||||
|
if (!ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
|
assert(ret == 1);
|
||||||
|
|
||||||
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", "matto", "fatto");
|
||||||
|
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
||||||
|
assert(ret == 0);
|
||||||
|
|
||||||
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", "", "fto");
|
||||||
|
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
||||||
|
assert(ret == 0);
|
||||||
|
|
||||||
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.245", "Y", NULL, "grewao");
|
||||||
|
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
||||||
|
assert(ret == 0);
|
||||||
|
|
||||||
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "foo", "192.254.254.242", NULL, NULL, NULL);
|
||||||
|
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
||||||
|
assert(ret == 0);
|
||||||
|
|
||||||
|
ret = set_and_get_mysql_users_wildcards("riccio", "192.0.0.%", "foo", "192.134.0.2", NULL, NULL, NULL);
|
||||||
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
if (ret) fprintf(stderr, "\t-- Expecting no match\n");
|
||||||
assert(ret == 1);
|
assert(ret == 1);
|
||||||
|
|
||||||
|
ret = set_and_get_mysql_users_wildcards("pippo", "192.%.%.%", "12345678901234567890123456789012345678901234", "192.254.254.245", "Y", NULL, NULL);
|
||||||
|
if (!ret) fprintf(stderr, "\t-- Expecting ok\n");
|
||||||
|
assert(ret == 0);
|
||||||
|
|
||||||
fprintf(stderr, "----------------\n");
|
fprintf(stderr, "----------------\n");
|
||||||
fprintf(stderr, "<<< Test completed\n");
|
fprintf(stderr, "<<< Test completed\n");
|
||||||
|
|
||||||
|
77
server/core/test/testsession.c
Normal file
77
server/core/test/testsession.c
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* This file is distributed as part of MaxScale. It 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,
|
||||||
|
* version 2.
|
||||||
|
*
|
||||||
|
* 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., 51
|
||||||
|
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
|
*
|
||||||
|
* Copyright MariaDB Corporation Ab 2014
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @verbatim
|
||||||
|
* Revision History
|
||||||
|
*
|
||||||
|
* Date Who Description
|
||||||
|
* 11-09-2014 Martin Brampton Initial implementation
|
||||||
|
*
|
||||||
|
* @endverbatim
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
#include <dcb.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* test1 Allocate a service and do lots of other things
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
static int
|
||||||
|
test1()
|
||||||
|
{
|
||||||
|
DCB *dcb;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
/* Poll tests */
|
||||||
|
ss_dfprintf(stderr,
|
||||||
|
"testpoll : Initialise the polling system.");
|
||||||
|
poll_init();
|
||||||
|
ss_dfprintf(stderr, "\t..done\nAdd a DCB");
|
||||||
|
dcb = dcb_alloc(DCB_ROLE_SERVICE_LISTENER);
|
||||||
|
dcb->fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
poll_add_dcb(dcb);
|
||||||
|
poll_remove_dcb(dcb);
|
||||||
|
poll_add_dcb(dcb);
|
||||||
|
ss_dfprintf(stderr, "\t..done\nStart wait for events.");
|
||||||
|
sleep(10);
|
||||||
|
poll_shutdown();
|
||||||
|
ss_dfprintf(stderr, "\t..done\nTidy up.");
|
||||||
|
dcb_free(dcb);
|
||||||
|
ss_dfprintf(stderr, "\t..done\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
|
||||||
|
result += test1();
|
||||||
|
|
||||||
|
exit(result);
|
||||||
|
}
|
||||||
|
|
@ -106,6 +106,8 @@ int add;
|
|||||||
/**
|
/**
|
||||||
* Delete a user from the user table.
|
* Delete a user from the user table.
|
||||||
*
|
*
|
||||||
|
* The last user in the table can not be deleted
|
||||||
|
*
|
||||||
* @param users The users table
|
* @param users The users table
|
||||||
* @param user The user name
|
* @param user The user name
|
||||||
* @return The number of users deleted from the table
|
* @return The number of users deleted from the table
|
||||||
@ -115,12 +117,12 @@ users_delete(USERS *users, char *user)
|
|||||||
{
|
{
|
||||||
int del;
|
int del;
|
||||||
|
|
||||||
atomic_add(&users->stats.n_deletes, 1);
|
|
||||||
if (users->stats.n_entries == 1) {
|
if (users->stats.n_entries == 1) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
atomic_add(&users->stats.n_deletes, 1);
|
||||||
del = hashtable_delete(users->data, user);
|
del = hashtable_delete(users->data, user);
|
||||||
atomic_add(&users->stats.n_entries, del * -1);
|
atomic_add(&users->stats.n_entries, -del);
|
||||||
return del;
|
return del;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
* 25/02/13 Massimiliano Pinto Added users table refresh rate default values
|
* 25/02/13 Massimiliano Pinto Added users table refresh rate default values
|
||||||
* 28/02/14 Massimiliano Pinto Added MySQL user and host data structure
|
* 28/02/14 Massimiliano Pinto Added MySQL user and host data structure
|
||||||
* 03/10/14 Massimiliano Pinto Added netmask to MySQL user and host data structure
|
* 03/10/14 Massimiliano Pinto Added netmask to MySQL user and host data structure
|
||||||
|
* 13/10/14 Massimiliano Pinto Added resource to MySQL user and host data structure
|
||||||
*
|
*
|
||||||
* @endverbatim
|
* @endverbatim
|
||||||
*/
|
*/
|
||||||
@ -54,12 +55,13 @@ typedef struct mysql_user_host_key {
|
|||||||
char *user;
|
char *user;
|
||||||
struct sockaddr_in ipv4;
|
struct sockaddr_in ipv4;
|
||||||
int netmask;
|
int netmask;
|
||||||
|
char *resource;
|
||||||
} MYSQL_USER_HOST;
|
} MYSQL_USER_HOST;
|
||||||
|
|
||||||
extern int load_mysql_users(SERVICE *service);
|
extern int load_mysql_users(SERVICE *service);
|
||||||
extern int reload_mysql_users(SERVICE *service);
|
extern int reload_mysql_users(SERVICE *service);
|
||||||
extern int mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth);
|
extern int mysql_users_add(USERS *users, MYSQL_USER_HOST *key, char *auth);
|
||||||
extern int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd);
|
extern int add_mysql_users_with_host_ipv4(USERS *users, char *user, char *host, char *passwd, char *anydb, char *db);
|
||||||
extern USERS *mysql_users_alloc();
|
extern USERS *mysql_users_alloc();
|
||||||
extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key);
|
extern char *mysql_users_fetch(USERS *users, MYSQL_USER_HOST *key);
|
||||||
extern int replace_mysql_users(SERVICE *service);
|
extern int replace_mysql_users(SERVICE *service);
|
||||||
|
@ -70,3 +70,4 @@ int gw_write(
|
|||||||
size_t nbytes);
|
size_t nbytes);
|
||||||
int gw_getsockerrno(int fd);
|
int gw_getsockerrno(int fd);
|
||||||
int parse_bindconfig(char *, unsigned short, struct sockaddr_in *);
|
int parse_bindconfig(char *, unsigned short, struct sockaddr_in *);
|
||||||
|
int setipaddress(struct in_addr *, char *);
|
||||||
|
@ -24,18 +24,21 @@
|
|||||||
* @verbatim
|
* @verbatim
|
||||||
* Revision History
|
* Revision History
|
||||||
*
|
*
|
||||||
* Date Who Description
|
* Date Who Description
|
||||||
* 04/06/14 Mark Riddoch Initial implementation
|
* 04/06/14 Mark Riddoch Initial implementation
|
||||||
* 24/06/14 Mark Riddoch Add modutil_MySQL_Query to enable multipacket queries
|
* 24/06/14 Mark Riddoch Add modutil_MySQL_Query to enable multipacket queries
|
||||||
|
* 24/10/14 Massimiliano Pinto Add modutil_send_mysql_err_packet to send a mysql ERR_Packet
|
||||||
*
|
*
|
||||||
* @endverbatim
|
* @endverbatim
|
||||||
*/
|
*/
|
||||||
#include <buffer.h>
|
#include <buffer.h>
|
||||||
|
#include <dcb.h>
|
||||||
|
|
||||||
extern int modutil_is_SQL(GWBUF *);
|
extern int modutil_is_SQL(GWBUF *);
|
||||||
extern int modutil_extract_SQL(GWBUF *, char **, int *);
|
extern int modutil_extract_SQL(GWBUF *, char **, int *);
|
||||||
extern int modutil_MySQL_Query(GWBUF *, char **, int *, int *);
|
extern int modutil_MySQL_Query(GWBUF *, char **, int *, int *);
|
||||||
extern GWBUF *modutil_replace_SQL(GWBUF *, char *);
|
extern GWBUF *modutil_replace_SQL(GWBUF *, char *);
|
||||||
char* modutil_get_query(GWBUF* buf);
|
extern char *modutil_get_query(GWBUF* buf);
|
||||||
|
extern int modutil_send_mysql_err_packet(DCB *, int, int, int, const char *, const char *);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
#include <dcb.h>
|
#include <dcb.h>
|
||||||
#include <server.h>
|
#include <server.h>
|
||||||
#include <filter.h>
|
#include <filter.h>
|
||||||
|
#include <hashtable.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,6 +46,7 @@
|
|||||||
* 29/05/14 Mark Riddoch Filter API mechanism
|
* 29/05/14 Mark Riddoch Filter API mechanism
|
||||||
* 26/06/14 Mark Riddoch Added WeightBy support
|
* 26/06/14 Mark Riddoch Added WeightBy support
|
||||||
* 09/09/14 Massimiliano Pinto Added service option for localhost authentication
|
* 09/09/14 Massimiliano Pinto Added service option for localhost authentication
|
||||||
|
* 09/10/14 Massimiliano Pinto Added service resources via hashtable
|
||||||
*
|
*
|
||||||
* @endverbatim
|
* @endverbatim
|
||||||
*/
|
*/
|
||||||
@ -124,6 +126,7 @@ typedef struct service {
|
|||||||
struct users *users; /**< The user data for this service */
|
struct users *users; /**< The user data for this service */
|
||||||
int enable_root; /**< Allow root user access */
|
int enable_root; /**< Allow root user access */
|
||||||
int localhost_match_wildcard_host; /**< Match localhost against wildcard */
|
int localhost_match_wildcard_host; /**< Match localhost against wildcard */
|
||||||
|
HASHTABLE *resources; /**< hastable for service resources, i.e. database names */
|
||||||
CONFIG_PARAMETER*
|
CONFIG_PARAMETER*
|
||||||
svc_config_param; /*< list of config params and values */
|
svc_config_param; /*< list of config params and values */
|
||||||
int svc_config_version; /*< Version number of configuration */
|
int svc_config_version; /*< Version number of configuration */
|
||||||
|
@ -1168,7 +1168,11 @@ routeQuery(FILTER *instance, void *session, GWBUF *queue)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (queue->next != NULL)
|
||||||
|
{
|
||||||
|
queue = gwbuf_make_contiguous(queue);
|
||||||
|
}
|
||||||
|
|
||||||
if(modutil_extract_SQL(queue, &ptr, &length)){
|
if(modutil_extract_SQL(queue, &ptr, &length)){
|
||||||
|
|
||||||
my_session->was_query = true;
|
my_session->was_query = true;
|
||||||
|
@ -358,24 +358,30 @@ int length;
|
|||||||
struct tm t;
|
struct tm t;
|
||||||
struct timeval tv;
|
struct timeval tv;
|
||||||
|
|
||||||
if (my_session->active && modutil_extract_SQL(queue, &ptr, &length))
|
if (my_session->active)
|
||||||
{
|
{
|
||||||
if ((my_instance->match == NULL ||
|
if (queue->next != NULL)
|
||||||
regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) &&
|
|
||||||
(my_instance->nomatch == NULL ||
|
|
||||||
regexec(&my_instance->nore,ptr,0,NULL, 0) != 0))
|
|
||||||
{
|
{
|
||||||
gettimeofday(&tv, NULL);
|
queue = gwbuf_make_contiguous(queue);
|
||||||
localtime_r(&tv.tv_sec, &t);
|
}
|
||||||
fprintf(my_session->fp,
|
if (modutil_extract_SQL(queue, &ptr, &length) != 0)
|
||||||
"%02d:%02d:%02d.%-3d %d/%02d/%d, ",
|
{
|
||||||
t.tm_hour, t.tm_min, t.tm_sec, (int)(tv.tv_usec / 1000),
|
if ((my_instance->match == NULL ||
|
||||||
t.tm_mday, t.tm_mon + 1, 1900 + t.tm_year);
|
regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) &&
|
||||||
fwrite(ptr, sizeof(char), length, my_session->fp);
|
(my_instance->nomatch == NULL ||
|
||||||
fwrite("\n", sizeof(char), 1, my_session->fp);
|
regexec(&my_instance->nore,ptr,0,NULL, 0) != 0))
|
||||||
|
{
|
||||||
|
gettimeofday(&tv, NULL);
|
||||||
|
localtime_r(&tv.tv_sec, &t);
|
||||||
|
fprintf(my_session->fp,
|
||||||
|
"%02d:%02d:%02d.%-3d %d/%02d/%d, ",
|
||||||
|
t.tm_hour, t.tm_min, t.tm_sec, (int)(tv.tv_usec / 1000),
|
||||||
|
t.tm_mday, t.tm_mon + 1, 1900 + t.tm_year);
|
||||||
|
fwrite(ptr, sizeof(char), length, my_session->fp);
|
||||||
|
fwrite("\n", sizeof(char), 1, my_session->fp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pass the query downstream */
|
/* Pass the query downstream */
|
||||||
return my_session->down.routeQuery(my_session->down.instance,
|
return my_session->down.routeQuery(my_session->down.instance,
|
||||||
my_session->down.session, queue);
|
my_session->down.session, queue);
|
||||||
|
@ -305,12 +305,17 @@ int length;
|
|||||||
|
|
||||||
if (modutil_is_SQL(queue))
|
if (modutil_is_SQL(queue))
|
||||||
{
|
{
|
||||||
|
if (queue->next != NULL)
|
||||||
|
{
|
||||||
|
queue = gwbuf_make_contiguous(queue);
|
||||||
|
}
|
||||||
modutil_extract_SQL(queue, &sql, &length);
|
modutil_extract_SQL(queue, &sql, &length);
|
||||||
newsql = regex_replace(sql, length, &my_instance->re,
|
newsql = regex_replace(sql, length, &my_instance->re,
|
||||||
my_instance->replace);
|
my_instance->replace);
|
||||||
if (newsql)
|
if (newsql)
|
||||||
{
|
{
|
||||||
queue = modutil_replace_SQL(queue, newsql);
|
queue = modutil_replace_SQL(queue, newsql);
|
||||||
|
queue = gwbuf_make_contiguous(queue);
|
||||||
free(newsql);
|
free(newsql);
|
||||||
my_session->replacements++;
|
my_session->replacements++;
|
||||||
}
|
}
|
||||||
|
@ -455,21 +455,27 @@ TOPN_SESSION *my_session = (TOPN_SESSION *)session;
|
|||||||
char *ptr;
|
char *ptr;
|
||||||
int length;
|
int length;
|
||||||
|
|
||||||
if (my_session->active && modutil_extract_SQL(queue, &ptr, &length))
|
if (my_session->active)
|
||||||
{
|
{
|
||||||
if ((my_instance->match == NULL ||
|
if (queue->next != NULL)
|
||||||
regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) &&
|
|
||||||
(my_instance->exclude == NULL ||
|
|
||||||
regexec(&my_instance->exre,ptr,0,NULL, 0) != 0))
|
|
||||||
{
|
{
|
||||||
my_session->n_statements++;
|
queue = gwbuf_make_contiguous(queue);
|
||||||
if (my_session->current)
|
}
|
||||||
free(my_session->current);
|
if (modutil_extract_SQL(queue, &ptr, &length) != 0)
|
||||||
gettimeofday(&my_session->start, NULL);
|
{
|
||||||
my_session->current = strndup(ptr, length);
|
if ((my_instance->match == NULL ||
|
||||||
|
regexec(&my_instance->re, ptr, 0, NULL, 0) == 0) &&
|
||||||
|
(my_instance->exclude == NULL ||
|
||||||
|
regexec(&my_instance->exre,ptr,0,NULL, 0) != 0))
|
||||||
|
{
|
||||||
|
my_session->n_statements++;
|
||||||
|
if (my_session->current)
|
||||||
|
free(my_session->current);
|
||||||
|
gettimeofday(&my_session->start, NULL);
|
||||||
|
my_session->current = strndup(ptr, length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Pass the query downstream */
|
/* Pass the query downstream */
|
||||||
return my_session->down.routeQuery(my_session->down.instance,
|
return my_session->down.routeQuery(my_session->down.instance,
|
||||||
my_session->down.session, queue);
|
my_session->down.session, queue);
|
||||||
|
@ -454,7 +454,7 @@ size_t nrounds = 0;
|
|||||||
* round.
|
* round.
|
||||||
*/
|
*/
|
||||||
if (nrounds != 0 &&
|
if (nrounds != 0 &&
|
||||||
((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >
|
((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >=
|
||||||
MON_BASE_INTERVAL_MS)
|
MON_BASE_INTERVAL_MS)
|
||||||
{
|
{
|
||||||
nrounds += 1;
|
nrounds += 1;
|
||||||
|
@ -618,7 +618,7 @@ size_t nrounds = 0;
|
|||||||
* round.
|
* round.
|
||||||
*/
|
*/
|
||||||
if (nrounds != 0 &&
|
if (nrounds != 0 &&
|
||||||
((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >
|
((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >=
|
||||||
MON_BASE_INTERVAL_MS)
|
MON_BASE_INTERVAL_MS)
|
||||||
{
|
{
|
||||||
nrounds += 1;
|
nrounds += 1;
|
||||||
@ -861,8 +861,8 @@ static void set_master_heartbeat(MYSQL_MONITOR *handle, MONITOR_SERVERS *databas
|
|||||||
unsigned long id = handle->id;
|
unsigned long id = handle->id;
|
||||||
time_t heartbeat;
|
time_t heartbeat;
|
||||||
time_t purge_time;
|
time_t purge_time;
|
||||||
char heartbeat_insert_query[128]="";
|
char heartbeat_insert_query[512]="";
|
||||||
char heartbeat_purge_query[128]="";
|
char heartbeat_purge_query[512]="";
|
||||||
|
|
||||||
/* create the maxscale_schema database */
|
/* create the maxscale_schema database */
|
||||||
if (mysql_query(database->con, "CREATE DATABASE IF NOT EXISTS maxscale_schema")) {
|
if (mysql_query(database->con, "CREATE DATABASE IF NOT EXISTS maxscale_schema")) {
|
||||||
|
@ -452,7 +452,7 @@ size_t nrounds = 0;
|
|||||||
* round.
|
* round.
|
||||||
*/
|
*/
|
||||||
if (nrounds != 0 &&
|
if (nrounds != 0 &&
|
||||||
((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >
|
((nrounds*MON_BASE_INTERVAL_MS)%handle->interval) >=
|
||||||
MON_BASE_INTERVAL_MS)
|
MON_BASE_INTERVAL_MS)
|
||||||
{
|
{
|
||||||
nrounds += 1;
|
nrounds += 1;
|
||||||
|
@ -20,6 +20,8 @@
|
|||||||
#include <skygw_types.h>
|
#include <skygw_types.h>
|
||||||
#include <skygw_utils.h>
|
#include <skygw_utils.h>
|
||||||
#include <log_manager.h>
|
#include <log_manager.h>
|
||||||
|
#include <modutil.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* MySQL Protocol module for handling the protocol between the gateway
|
* MySQL Protocol module for handling the protocol between the gateway
|
||||||
* and the backend MySQL database.
|
* and the backend MySQL database.
|
||||||
@ -41,6 +43,7 @@
|
|||||||
* 04/09/2013 Massimiliano Pinto Added dcb->session and dcb->session->client checks for NULL
|
* 04/09/2013 Massimiliano Pinto Added dcb->session and dcb->session->client checks for NULL
|
||||||
* 12/09/2013 Massimiliano Pinto Added checks in gw_read_backend_event() for gw_read_backend_handshake
|
* 12/09/2013 Massimiliano Pinto Added checks in gw_read_backend_event() for gw_read_backend_handshake
|
||||||
* 27/09/2013 Massimiliano Pinto Changed in gw_read_backend_event the check for dcb_read(), now is if rc < 0
|
* 27/09/2013 Massimiliano Pinto Changed in gw_read_backend_event the check for dcb_read(), now is if rc < 0
|
||||||
|
* 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include <modinfo.h>
|
#include <modinfo.h>
|
||||||
@ -66,7 +69,8 @@ static int backend_write_delayqueue(DCB *dcb);
|
|||||||
static void backend_set_delayqueue(DCB *dcb, GWBUF *queue);
|
static void backend_set_delayqueue(DCB *dcb, GWBUF *queue);
|
||||||
static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue);
|
static int gw_change_user(DCB *backend_dcb, SERVER *server, SESSION *in_session, GWBUF *queue);
|
||||||
static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process);
|
static GWBUF* process_response_data (DCB* dcb, GWBUF* readbuf, int nbytes_to_process);
|
||||||
|
extern char* create_auth_failed_msg( GWBUF* readbuf, char* hostaddr, uint8_t* sha1);
|
||||||
|
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db);
|
||||||
|
|
||||||
|
|
||||||
#if defined(NOT_USED)
|
#if defined(NOT_USED)
|
||||||
@ -1171,9 +1175,16 @@ static int backend_write_delayqueue(DCB *dcb)
|
|||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This routine handles the COM_CHANGE_USER command
|
||||||
|
*
|
||||||
|
* @param dcb The current backend DCB
|
||||||
|
* @param server The backend server pointer
|
||||||
|
* @param in_session The current session data (MYSQL_session)
|
||||||
|
* @param queue The GWBUF containing the COM_CHANGE_USER receveid
|
||||||
|
* @return 0 on success and 1 on failure
|
||||||
|
*/
|
||||||
static int gw_change_user(
|
static int gw_change_user(
|
||||||
DCB *backend,
|
DCB *backend,
|
||||||
SERVER *server,
|
SERVER *server,
|
||||||
@ -1185,6 +1196,7 @@ static int gw_change_user(
|
|||||||
MySQLProtocol *client_protocol = NULL;
|
MySQLProtocol *client_protocol = NULL;
|
||||||
char username[MYSQL_USER_MAXLEN+1]="";
|
char username[MYSQL_USER_MAXLEN+1]="";
|
||||||
char database[MYSQL_DATABASE_MAXLEN+1]="";
|
char database[MYSQL_DATABASE_MAXLEN+1]="";
|
||||||
|
char current_database[MYSQL_DATABASE_MAXLEN+1]="";
|
||||||
uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]="";
|
uint8_t client_sha1[MYSQL_SCRAMBLE_LEN]="";
|
||||||
uint8_t *client_auth_packet = GWBUF_DATA(queue);
|
uint8_t *client_auth_packet = GWBUF_DATA(queue);
|
||||||
unsigned int auth_token_len = 0;
|
unsigned int auth_token_len = 0;
|
||||||
@ -1196,18 +1208,18 @@ static int gw_change_user(
|
|||||||
backend_protocol = backend->protocol;
|
backend_protocol = backend->protocol;
|
||||||
client_protocol = in_session->client->protocol;
|
client_protocol = in_session->client->protocol;
|
||||||
|
|
||||||
// now get the user, after 4 bytes header and 1 byte command
|
/* now get the user, after 4 bytes header and 1 byte command */
|
||||||
client_auth_packet += 5;
|
client_auth_packet += 5;
|
||||||
strcpy(username, (char *)client_auth_packet);
|
strcpy(username, (char *)client_auth_packet);
|
||||||
client_auth_packet += strlen(username) + 1;
|
client_auth_packet += strlen(username) + 1;
|
||||||
|
|
||||||
// get the auth token len
|
/* get the auth token len */
|
||||||
memcpy(&auth_token_len, client_auth_packet, 1);
|
memcpy(&auth_token_len, client_auth_packet, 1);
|
||||||
ss_dassert(auth_token_len >= 0);
|
ss_dassert(auth_token_len >= 0);
|
||||||
|
|
||||||
client_auth_packet++;
|
client_auth_packet++;
|
||||||
|
|
||||||
// allocate memory for token only if auth_token_len > 0
|
/* allocate memory for token only if auth_token_len > 0 */
|
||||||
if (auth_token_len > 0) {
|
if (auth_token_len > 0) {
|
||||||
auth_token = (uint8_t *)malloc(auth_token_len);
|
auth_token = (uint8_t *)malloc(auth_token_len);
|
||||||
ss_dassert(auth_token != NULL);
|
ss_dassert(auth_token != NULL);
|
||||||
@ -1217,8 +1229,23 @@ static int gw_change_user(
|
|||||||
memcpy(auth_token, client_auth_packet, auth_token_len);
|
memcpy(auth_token, client_auth_packet, auth_token_len);
|
||||||
client_auth_packet += auth_token_len;
|
client_auth_packet += auth_token_len;
|
||||||
}
|
}
|
||||||
// decode the token and check the password
|
|
||||||
// Note: if auth_token_len == 0 && auth_token == NULL, user is without password
|
/* get new database name */
|
||||||
|
strcpy(database, (char *)client_auth_packet);
|
||||||
|
|
||||||
|
/* save current_database name */
|
||||||
|
strcpy(current_database, current_session->db);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now clear database name in dcb as we don't do local authentication on db name for change user.
|
||||||
|
* Local authentication only for user@host and if successful the database name change is sent to backend.
|
||||||
|
*/
|
||||||
|
strcpy(current_session->db, "");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* decode the token and check the password.
|
||||||
|
* Note: if auth_token_len == 0 && auth_token == NULL, user is without password
|
||||||
|
*/
|
||||||
auth_ret = gw_check_mysql_scramble_data(backend->session->client, auth_token, auth_token_len, client_protocol->scramble, sizeof(client_protocol->scramble), username, client_sha1);
|
auth_ret = gw_check_mysql_scramble_data(backend->session->client, auth_token, auth_token_len, client_protocol->scramble, sizeof(client_protocol->scramble), username, client_sha1);
|
||||||
|
|
||||||
if (auth_ret != 0) {
|
if (auth_ret != 0) {
|
||||||
@ -1229,24 +1256,37 @@ static int gw_change_user(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// let's free the auth_token now
|
/* copy back current datbase to client session */
|
||||||
|
strcpy(current_session->db, current_database);
|
||||||
|
|
||||||
|
/* let's free the auth_token now */
|
||||||
if (auth_token)
|
if (auth_token)
|
||||||
free(auth_token);
|
free(auth_token);
|
||||||
|
|
||||||
if (auth_ret != 0) {
|
if (auth_ret != 0) {
|
||||||
/*< vraa : errorHandle */
|
char *password_set = NULL;
|
||||||
|
char *message = NULL;
|
||||||
|
|
||||||
|
if (auth_token_len > 0)
|
||||||
|
password_set = (char *)client_sha1;
|
||||||
|
else
|
||||||
|
password_set = "";
|
||||||
|
|
||||||
|
message=create_auth_fail_str(username,
|
||||||
|
backend->session->client->remote,
|
||||||
|
password_set,
|
||||||
|
"");
|
||||||
|
/* send the error packet */
|
||||||
|
modutil_send_mysql_err_packet(backend->session->client, 1, 0, 1045, "28000", message);
|
||||||
|
|
||||||
|
free(message);
|
||||||
|
|
||||||
// send the error packet
|
|
||||||
mysql_send_auth_error(backend->session->client, 1, 0, "Authorization failed on change_user");
|
|
||||||
rv = 1;
|
rv = 1;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// get db name
|
|
||||||
strcpy(database, (char *)client_auth_packet);
|
|
||||||
|
|
||||||
rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol);
|
rv = gw_send_change_user_to_backend(database, username, client_sha1, backend_protocol);
|
||||||
|
|
||||||
/*<
|
/*
|
||||||
* Now copy new data into user session
|
* Now copy new data into user session
|
||||||
*/
|
*/
|
||||||
strcpy(current_session->user, username);
|
strcpy(current_session->user, username);
|
||||||
@ -1254,6 +1294,7 @@ static int gw_change_user(
|
|||||||
memcpy(current_session->client_sha1, client_sha1, sizeof(current_session->client_sha1));
|
memcpy(current_session->client_sha1, client_sha1, sizeof(current_session->client_sha1));
|
||||||
}
|
}
|
||||||
gwbuf_free(queue);
|
gwbuf_free(queue);
|
||||||
|
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@
|
|||||||
* 11/03/2014 Massimiliano Pinto Added: Unix socket support
|
* 11/03/2014 Massimiliano Pinto Added: Unix socket support
|
||||||
* 07/05/2014 Massimiliano Pinto Added: specific version string in server handshake
|
* 07/05/2014 Massimiliano Pinto Added: specific version string in server handshake
|
||||||
* 09/09/2014 Massimiliano Pinto Added: 777 permission for socket path
|
* 09/09/2014 Massimiliano Pinto Added: 777 permission for socket path
|
||||||
|
* 13/10/2014 Massimiliano Pinto Added: dbname authentication check
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
#include <skygw_utils.h>
|
#include <skygw_utils.h>
|
||||||
@ -43,6 +44,7 @@
|
|||||||
#include <gw.h>
|
#include <gw.h>
|
||||||
#include <modinfo.h>
|
#include <modinfo.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
#include <modutil.h>
|
||||||
|
|
||||||
MODULE_INFO info = {
|
MODULE_INFO info = {
|
||||||
MODULE_API_PROTOCOL,
|
MODULE_API_PROTOCOL,
|
||||||
@ -68,8 +70,9 @@ int mysql_send_ok(DCB *dcb, int packet_number, int in_affected_rows, const char*
|
|||||||
int MySQLSendHandshake(DCB* dcb);
|
int MySQLSendHandshake(DCB* dcb);
|
||||||
static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue);
|
static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue);
|
||||||
static int route_by_statement(SESSION *, GWBUF **);
|
static int route_by_statement(SESSION *, GWBUF **);
|
||||||
static char* create_auth_fail_str(GWBUF* readbuf, char* hostaddr, char* sha1);
|
extern char* get_username_from_auth(char* ptr, uint8_t* data);
|
||||||
static char* get_username_from_auth(char* ptr, uint8_t* data);
|
extern int check_db_name_after_auth(DCB *, char *, int);
|
||||||
|
extern char* create_auth_fail_str(char *username, char *hostaddr, char *sha1, char *db);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The "module object" for the mysqld client protocol module.
|
* The "module object" for the mysqld client protocol module.
|
||||||
@ -446,6 +449,9 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
|||||||
client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1,
|
client_auth_packet + 4 + 4 + 4 + 1 + 23 + strlen(username) + 1,
|
||||||
1);
|
1);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note: some clients may pass empty database, connect_with_db !=0 but database =""
|
||||||
|
*/
|
||||||
if (connect_with_db) {
|
if (connect_with_db) {
|
||||||
database = client_data->db;
|
database = client_data->db;
|
||||||
strncpy(database,
|
strncpy(database,
|
||||||
@ -461,7 +467,8 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
|||||||
auth_token_len);
|
auth_token_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* decode the token and check the password
|
/*
|
||||||
|
* Decode the token and check the password
|
||||||
* Note: if auth_token_len == 0 && auth_token == NULL, user is without password
|
* Note: if auth_token_len == 0 && auth_token == NULL, user is without password
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -472,6 +479,9 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
|||||||
username,
|
username,
|
||||||
stage1_hash);
|
stage1_hash);
|
||||||
|
|
||||||
|
/* check for database name match in resource hashtable */
|
||||||
|
auth_ret = check_db_name_after_auth(dcb, database, auth_ret);
|
||||||
|
|
||||||
/* On failed auth try to load users' table from backend database */
|
/* On failed auth try to load users' table from backend database */
|
||||||
if (auth_ret != 0) {
|
if (auth_ret != 0) {
|
||||||
if (!service_refresh_users(dcb->service)) {
|
if (!service_refresh_users(dcb->service)) {
|
||||||
@ -481,89 +491,22 @@ static int gw_mysql_do_authentication(DCB *dcb, GWBUF *queue) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* let's free the auth_token now */
|
/* Do again the database check */
|
||||||
if (auth_token)
|
auth_ret = check_db_name_after_auth(dcb, database, auth_ret);
|
||||||
free(auth_token);
|
|
||||||
|
|
||||||
if (auth_ret == 0)
|
/* on succesful auth set user into dcb field */
|
||||||
{
|
if (auth_ret == 0) {
|
||||||
dcb->user = strdup(client_data->user);
|
dcb->user = strdup(client_data->user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* let's free the auth_token now */
|
||||||
|
if (auth_token) {
|
||||||
|
free(auth_token);
|
||||||
|
}
|
||||||
|
|
||||||
return auth_ret;
|
return auth_ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Read username from MySQL authentication packet.
|
|
||||||
*
|
|
||||||
* @param ptr address where to write the result or NULL if memory
|
|
||||||
* is allocated here.
|
|
||||||
* @param data Address of MySQL packet.
|
|
||||||
*
|
|
||||||
* @return Pointer to a copy of the username. NULL if memory allocation
|
|
||||||
* failed or if username was empty.
|
|
||||||
*/
|
|
||||||
static char* get_username_from_auth(
|
|
||||||
char* ptr,
|
|
||||||
uint8_t* data)
|
|
||||||
{
|
|
||||||
char* first_letter;
|
|
||||||
char* rval;
|
|
||||||
|
|
||||||
first_letter = (char *)(data + 4 + 4 + 4 + 1 + 23);
|
|
||||||
|
|
||||||
if (first_letter == '\0')
|
|
||||||
{
|
|
||||||
rval = NULL;
|
|
||||||
goto retblock;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ptr == NULL)
|
|
||||||
{
|
|
||||||
if ((rval = (char *)malloc(MYSQL_USER_MAXLEN+1)) == NULL)
|
|
||||||
{
|
|
||||||
goto retblock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rval = ptr;
|
|
||||||
}
|
|
||||||
snprintf(rval, MYSQL_USER_MAXLEN+1, "%s", first_letter);
|
|
||||||
|
|
||||||
retblock:
|
|
||||||
|
|
||||||
return rval;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static char* create_auth_fail_str(
|
|
||||||
GWBUF* readbuf,
|
|
||||||
char* hostaddr,
|
|
||||||
char* sha1)
|
|
||||||
{
|
|
||||||
char* errstr;
|
|
||||||
char* uname;
|
|
||||||
const char* ferrstr = "Access denied for user '%s'@'%s' (using password: %s)";
|
|
||||||
|
|
||||||
if ( (uname = get_username_from_auth(NULL, (uint8_t *)GWBUF_DATA(readbuf))) == NULL)
|
|
||||||
{
|
|
||||||
errstr = NULL;
|
|
||||||
goto retblock;
|
|
||||||
}
|
|
||||||
/** -4 comes from 2X'%s' minus terminating char */
|
|
||||||
errstr = (char *)malloc(strlen(uname)+strlen(ferrstr)+strlen(hostaddr)+strlen("YES")-6+1);
|
|
||||||
|
|
||||||
if (errstr != NULL)
|
|
||||||
{
|
|
||||||
sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES"));
|
|
||||||
}
|
|
||||||
free(uname);
|
|
||||||
|
|
||||||
retblock:
|
|
||||||
return errstr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write function for client DCB: writes data from MaxScale to Client
|
* Write function for client DCB: writes data from MaxScale to Client
|
||||||
*
|
*
|
||||||
@ -715,19 +658,28 @@ int gw_read_client_event(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
char* fail_str;
|
char* fail_str = NULL;
|
||||||
|
|
||||||
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
|
protocol->protocol_auth_state = MYSQL_AUTH_FAILED;
|
||||||
fail_str = create_auth_fail_str(read_buffer,
|
|
||||||
|
if (auth_val == 2) {
|
||||||
|
/** Send error 1049 to client */
|
||||||
|
int message_len = 25 + MYSQL_DATABASE_MAXLEN;
|
||||||
|
|
||||||
|
fail_str = calloc(1, message_len+1);
|
||||||
|
snprintf(fail_str, message_len, "Unknown database '%s'", (char*)((MYSQL_session *)dcb->data)->db);
|
||||||
|
|
||||||
|
modutil_send_mysql_err_packet(dcb, 2, 0, 1049, "42000", fail_str);
|
||||||
|
} else {
|
||||||
|
/** Send error 1045 to client */
|
||||||
|
fail_str = create_auth_fail_str((char *)((MYSQL_session *)dcb->data)->user,
|
||||||
dcb->remote,
|
dcb->remote,
|
||||||
(char*)((MYSQL_session *)dcb->data)->client_sha1);
|
(char*)((MYSQL_session *)dcb->data)->client_sha1,
|
||||||
|
(char*)((MYSQL_session *)dcb->data)->db);
|
||||||
/** Send error 1045 to client */
|
modutil_send_mysql_err_packet(dcb, 2, 0, 1045, "28000", fail_str);
|
||||||
mysql_send_auth_error(
|
}
|
||||||
dcb,
|
if (fail_str)
|
||||||
2,
|
free(fail_str);
|
||||||
0,
|
|
||||||
fail_str);
|
|
||||||
|
|
||||||
LOGIF(LD, (skygw_log_write(
|
LOGIF(LD, (skygw_log_write(
|
||||||
LOGFILE_DEBUG,
|
LOGFILE_DEBUG,
|
||||||
@ -736,7 +688,7 @@ int gw_read_client_event(
|
|||||||
"state = MYSQL_AUTH_FAILED.",
|
"state = MYSQL_AUTH_FAILED.",
|
||||||
protocol->owner_dcb->fd,
|
protocol->owner_dcb->fd,
|
||||||
pthread_self())));
|
pthread_self())));
|
||||||
free(fail_str);
|
|
||||||
dcb_close(dcb);
|
dcb_close(dcb);
|
||||||
}
|
}
|
||||||
read_buffer = gwbuf_consume(read_buffer, nbytes_read);
|
read_buffer = gwbuf_consume(read_buffer, nbytes_read);
|
||||||
@ -748,7 +700,7 @@ int gw_read_client_event(
|
|||||||
uint8_t cap = 0;
|
uint8_t cap = 0;
|
||||||
uint8_t* payload = NULL;
|
uint8_t* payload = NULL;
|
||||||
bool stmt_input; /*< router input type */
|
bool stmt_input; /*< router input type */
|
||||||
|
|
||||||
ss_dassert(nbytes_read >= 5);
|
ss_dassert(nbytes_read >= 5);
|
||||||
|
|
||||||
session = dcb->session;
|
session = dcb->session;
|
||||||
@ -1617,4 +1569,3 @@ return_str:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,8 +32,9 @@
|
|||||||
* Setting to 1 allow localhost (127.0.0.1 or socket) to match the any host grant via
|
* Setting to 1 allow localhost (127.0.0.1 or socket) to match the any host grant via
|
||||||
* user@%
|
* user@%
|
||||||
* 29/09/2014 Massimiliano Pinto Added Mysql user@host authentication with wildcard in IPv4 hosts:
|
* 29/09/2014 Massimiliano Pinto Added Mysql user@host authentication with wildcard in IPv4 hosts:
|
||||||
* x.y.z.%, x.y.%.%, x.%.%.%
|
* x.y.z.%, x.y.%.%, x.%.%.%
|
||||||
* 03/10/2014 Massimiliano Pinto Added netmask for wildcard in IPv4 hosts.
|
* 03/10/2014 Massimiliano Pinto Added netmask for wildcard in IPv4 hosts.
|
||||||
|
* 24/10/2014 Massimiliano Pinto Added Mysql user@host @db authentication support
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -49,6 +50,7 @@ extern int gw_read_backend_event(DCB* dcb);
|
|||||||
extern int gw_write_backend_event(DCB *dcb);
|
extern int gw_write_backend_event(DCB *dcb);
|
||||||
extern int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue);
|
extern int gw_MySQLWrite_backend(DCB *dcb, GWBUF *queue);
|
||||||
extern int gw_error_backend_event(DCB *dcb);
|
extern int gw_error_backend_event(DCB *dcb);
|
||||||
|
char* get_username_from_auth(char* ptr, uint8_t* data);
|
||||||
|
|
||||||
static server_command_t* server_command_init(server_command_t* srvcmd,
|
static server_command_t* server_command_init(server_command_t* srvcmd,
|
||||||
mysql_server_cmd_t cmd);
|
mysql_server_cmd_t cmd);
|
||||||
@ -1002,7 +1004,7 @@ GWBUF* mysql_create_custom_error(
|
|||||||
* @param packet_number
|
* @param packet_number
|
||||||
* @param in_affected_rows
|
* @param in_affected_rows
|
||||||
* @param mysql_message
|
* @param mysql_message
|
||||||
* @return packet length
|
* @return 1 Non-zero if data was sent
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
int mysql_send_custom_error (
|
int mysql_send_custom_error (
|
||||||
@ -1015,9 +1017,7 @@ int mysql_send_custom_error (
|
|||||||
|
|
||||||
buf = mysql_create_custom_error(packet_number, in_affected_rows, mysql_message);
|
buf = mysql_create_custom_error(packet_number, in_affected_rows, mysql_message);
|
||||||
|
|
||||||
dcb->func.write(dcb, buf);
|
return dcb->func.write(dcb, buf);
|
||||||
|
|
||||||
return GWBUF_LENGTH(buf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1137,6 +1137,11 @@ int gw_send_change_user_to_backend(
|
|||||||
|
|
||||||
// allocating the GWBUF
|
// allocating the GWBUF
|
||||||
buffer = gwbuf_alloc(bytes);
|
buffer = gwbuf_alloc(bytes);
|
||||||
|
/**
|
||||||
|
* Set correct type to GWBUF so that it will be handled like session
|
||||||
|
* commands should
|
||||||
|
*/
|
||||||
|
buffer->gwbuf_type = GWBUF_TYPE_MYSQL|GWBUF_TYPE_SINGLE_STMT|GWBUF_TYPE_SESCMD;
|
||||||
payload = GWBUF_DATA(buffer);
|
payload = GWBUF_DATA(buffer);
|
||||||
|
|
||||||
// clearing data
|
// clearing data
|
||||||
@ -1177,7 +1182,10 @@ int gw_send_change_user_to_backend(
|
|||||||
memcpy(payload, curr_db, strlen(curr_db));
|
memcpy(payload, curr_db, strlen(curr_db));
|
||||||
payload += strlen(curr_db);
|
payload += strlen(curr_db);
|
||||||
payload++;
|
payload++;
|
||||||
}
|
} else {
|
||||||
|
// skip the NULL
|
||||||
|
payload++;
|
||||||
|
}
|
||||||
|
|
||||||
// set the charset, 2 bytes!!!!
|
// set the charset, 2 bytes!!!!
|
||||||
*payload = '\x08';
|
*payload = '\x08';
|
||||||
@ -1236,6 +1244,12 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le
|
|||||||
ret_val = gw_find_mysql_user_password_sha1(username, password, dcb);
|
ret_val = gw_find_mysql_user_password_sha1(username, password, dcb);
|
||||||
|
|
||||||
if (ret_val) {
|
if (ret_val) {
|
||||||
|
/* if password was sent, fill stage1_hash with at least 1 byte in order
|
||||||
|
* to create rigth error message: (using password: YES|NO)
|
||||||
|
*/
|
||||||
|
if (token_len)
|
||||||
|
memcpy(stage1_hash, (char *)"_", 1);
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1313,8 +1327,9 @@ int gw_check_mysql_scramble_data(DCB *dcb, uint8_t *token, unsigned int token_le
|
|||||||
/**
|
/**
|
||||||
* gw_find_mysql_user_password_sha1
|
* gw_find_mysql_user_password_sha1
|
||||||
*
|
*
|
||||||
* The routine fetches look for an user int the MaxScale users' table
|
* The routine fetches an user from the MaxScale users' table
|
||||||
* The users' table is dcb->service->users or a different one specified with void *repository
|
* The users' table is dcb->service->users or a different one specified with void *repository
|
||||||
|
* The user lookup uses username,host and db name (if passed in connection or change user)
|
||||||
*
|
*
|
||||||
* If found the HEX password, representing sha1(sha1(password)), is converted in binary data and
|
* If found the HEX password, representing sha1(sha1(password)), is converted in binary data and
|
||||||
* copied into gateway_password
|
* copied into gateway_password
|
||||||
@ -1331,13 +1346,16 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password,
|
|||||||
struct sockaddr_in *client;
|
struct sockaddr_in *client;
|
||||||
char *user_password = NULL;
|
char *user_password = NULL;
|
||||||
MYSQL_USER_HOST key;
|
MYSQL_USER_HOST key;
|
||||||
|
MYSQL_session *client_data = NULL;
|
||||||
|
|
||||||
|
client_data = (MYSQL_session *) dcb->data;
|
||||||
service = (SERVICE *) dcb->service;
|
service = (SERVICE *) dcb->service;
|
||||||
client = (struct sockaddr_in *) &dcb->ipv4;
|
client = (struct sockaddr_in *) &dcb->ipv4;
|
||||||
|
|
||||||
key.user = username;
|
key.user = username;
|
||||||
memcpy(&key.ipv4, client, sizeof(struct sockaddr_in));
|
memcpy(&key.ipv4, client, sizeof(struct sockaddr_in));
|
||||||
key.netmask = 32;
|
key.netmask = 32;
|
||||||
|
key.resource = client_data->db;
|
||||||
|
|
||||||
LOGIF(LD,
|
LOGIF(LD,
|
||||||
(skygw_log_write_flush(
|
(skygw_log_write_flush(
|
||||||
@ -1382,7 +1400,7 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password,
|
|||||||
key.netmask -= 8;
|
key.netmask -= 8;
|
||||||
|
|
||||||
user_password = mysql_users_fetch(service->users, &key);
|
user_password = mysql_users_fetch(service->users, &key);
|
||||||
|
|
||||||
if (user_password) {
|
if (user_password) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1396,7 +1414,7 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password,
|
|||||||
if (user_password) {
|
if (user_password) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Class A check */
|
/* Class A check */
|
||||||
key.ipv4.sin_addr.s_addr &= 0x000000FF;
|
key.ipv4.sin_addr.s_addr &= 0x000000FF;
|
||||||
key.netmask -= 8;
|
key.netmask -= 8;
|
||||||
@ -1426,7 +1444,7 @@ int gw_find_mysql_user_password_sha1(char *username, uint8_t *gateway_password,
|
|||||||
|
|
||||||
if (!user_password) {
|
if (!user_password) {
|
||||||
/*
|
/*
|
||||||
* the user@% has not been found.
|
* user@% not found.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
LOGIF(LD,
|
LOGIF(LD,
|
||||||
@ -1969,3 +1987,138 @@ void protocol_set_response_status (
|
|||||||
spinlock_release(&p->protocol_lock);
|
spinlock_release(&p->protocol_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
char* create_auth_failed_msg(
|
||||||
|
GWBUF* readbuf,
|
||||||
|
char* hostaddr,
|
||||||
|
uint8_t* sha1)
|
||||||
|
{
|
||||||
|
char* errstr;
|
||||||
|
char* uname=(char *)GWBUF_DATA(readbuf) + 5;
|
||||||
|
const char* ferrstr = "Access denied for user '%s'@'%s' (using password: %s)";
|
||||||
|
|
||||||
|
/** -4 comes from 2X'%s' minus terminating char */
|
||||||
|
errstr = (char *)malloc(strlen(uname)+strlen(ferrstr)+strlen(hostaddr)+strlen("YES")-6 + 1);
|
||||||
|
|
||||||
|
if (errstr != NULL)
|
||||||
|
{
|
||||||
|
sprintf(errstr, ferrstr, uname, hostaddr, (*sha1 == '\0' ? "NO" : "YES"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return errstr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read username from MySQL authentication packet.
|
||||||
|
*
|
||||||
|
* Only for client to server packet, COM_CHANGE_USER packet has different format.
|
||||||
|
*
|
||||||
|
* @param ptr address where to write the result or NULL if memory
|
||||||
|
* is allocated here.
|
||||||
|
* @param data Address of MySQL packet.
|
||||||
|
*
|
||||||
|
* @return Pointer to a copy of the username. NULL if memory allocation
|
||||||
|
* failed or if username was empty.
|
||||||
|
*/
|
||||||
|
char* get_username_from_auth(
|
||||||
|
char* ptr,
|
||||||
|
uint8_t* data)
|
||||||
|
{
|
||||||
|
char* first_letter;
|
||||||
|
char* rval;
|
||||||
|
|
||||||
|
first_letter = (char *)(data + 4 + 4 + 4 + 1 + 23);
|
||||||
|
|
||||||
|
if (first_letter == '\0')
|
||||||
|
{
|
||||||
|
rval = NULL;
|
||||||
|
goto retblock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr == NULL)
|
||||||
|
{
|
||||||
|
if ((rval = (char *)malloc(MYSQL_USER_MAXLEN+1)) == NULL)
|
||||||
|
{
|
||||||
|
goto retblock;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rval = ptr;
|
||||||
|
}
|
||||||
|
snprintf(rval, MYSQL_USER_MAXLEN+1, "%s", first_letter);
|
||||||
|
|
||||||
|
retblock:
|
||||||
|
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
int check_db_name_after_auth(DCB *dcb, char *database, int auth_ret) {
|
||||||
|
int db_exists = -1;
|
||||||
|
|
||||||
|
/* check for dabase name and possible match in resource hashtable */
|
||||||
|
if (database && strlen(database)) {
|
||||||
|
/* if database names are loaded we can check if db name exists */
|
||||||
|
if (dcb->service->resources != NULL) {
|
||||||
|
if (hashtable_fetch(dcb->service->resources, database)) {
|
||||||
|
db_exists = 1;
|
||||||
|
} else {
|
||||||
|
db_exists = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* if database names are not loaded we don't allow connection with db name*/
|
||||||
|
db_exists = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db_exists == 0 && auth_ret == 0) {
|
||||||
|
auth_ret = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (db_exists < 0 && auth_ret == 0) {
|
||||||
|
auth_ret = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return auth_ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a message error string to send via MySQL ERR packet.
|
||||||
|
*
|
||||||
|
* @param username the MySQL user
|
||||||
|
* @param hostaddr the client IP
|
||||||
|
* @param sha1 authentication scramble data
|
||||||
|
* @param db the MySQL db to connect to
|
||||||
|
*
|
||||||
|
* @return Pointer to the allocated string or NULL on failure
|
||||||
|
*/
|
||||||
|
char *create_auth_fail_str(
|
||||||
|
char *username,
|
||||||
|
char *hostaddr,
|
||||||
|
char *sha1,
|
||||||
|
char *db)
|
||||||
|
{
|
||||||
|
char* errstr;
|
||||||
|
const char* ferrstr;
|
||||||
|
int db_len;
|
||||||
|
|
||||||
|
if (db != NULL)
|
||||||
|
db_len = strlen(db);
|
||||||
|
else
|
||||||
|
db_len = 0;
|
||||||
|
|
||||||
|
if (db_len>0)
|
||||||
|
ferrstr = "Access denied for user '%s'@'%s' (using password: %s) to database '%s'";
|
||||||
|
else
|
||||||
|
ferrstr = "Access denied for user '%s'@'%s' (using password: %s)";
|
||||||
|
|
||||||
|
errstr = (char *)malloc(strlen(username)+strlen(ferrstr)+strlen(hostaddr)+strlen("YES")-6 + db_len + ((db_len > 0) ? (strlen(" to database ") +2) : 0) + 1);
|
||||||
|
|
||||||
|
if (errstr != NULL) {
|
||||||
|
if (db_len>0)
|
||||||
|
sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"), db);
|
||||||
|
else
|
||||||
|
sprintf(errstr, ferrstr, username, hostaddr, (*sha1 == '\0' ? "NO" : "YES"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return errstr;
|
||||||
|
}
|
||||||
|
@ -29,7 +29,7 @@ UTILSPATH := $(ROOT_PATH)/utils
|
|||||||
|
|
||||||
CC=cc
|
CC=cc
|
||||||
CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \
|
CFLAGS=-c -fPIC -I/usr/include -I../include -I../../include -I$(LOGPATH) \
|
||||||
-I$(UTILSPATH) -Wall -g
|
-I$(UTILSPATH) -I$(MYSQL_HEADERS) -Wall -g
|
||||||
|
|
||||||
include ../../../makefile.inc
|
include ../../../makefile.inc
|
||||||
|
|
||||||
|
@ -1155,15 +1155,13 @@ static bool get_dcb(
|
|||||||
rses->router->available_slaves = false;
|
rses->router->available_slaves = false;
|
||||||
LOGIF(LE, (skygw_log_write_flush(
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
LOGFILE_ERROR,
|
LOGFILE_ERROR,
|
||||||
"Warning : No slaves available "
|
"Warning : No slaves available "
|
||||||
"for the service %s.",
|
"for the service %s.",
|
||||||
rses->router->service->name)));
|
rses->router->service->name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
btype = BE_MASTER;
|
btype = BE_MASTER;
|
||||||
|
|
||||||
|
|
||||||
if (BREF_IS_IN_USE(master_bref))
|
if (BREF_IS_IN_USE(master_bref))
|
||||||
{
|
{
|
||||||
*p_dcb = master_bref->bref_dcb;
|
*p_dcb = master_bref->bref_dcb;
|
||||||
@ -1199,7 +1197,7 @@ static bool get_dcb(
|
|||||||
LOGFILE_ERROR,
|
LOGFILE_ERROR,
|
||||||
"At least one slave has become available for "
|
"At least one slave has become available for "
|
||||||
"the service %s.",
|
"the service %s.",
|
||||||
rses->router->service->name)));
|
rses->router->service->name)));
|
||||||
}
|
}
|
||||||
ss_dassert(succp);
|
ss_dassert(succp);
|
||||||
}
|
}
|
||||||
@ -1920,8 +1918,8 @@ static int routeQuery(
|
|||||||
}
|
}
|
||||||
else if (hint->type == HINT_PARAMETER &&
|
else if (hint->type == HINT_PARAMETER &&
|
||||||
(strncasecmp((char *)hint->data,
|
(strncasecmp((char *)hint->data,
|
||||||
"max_slave_replication_lag",
|
"max_slave_replication_lag",
|
||||||
strlen("max_slave_replication_lag")) == 0))
|
strlen("max_slave_replication_lag")) == 0))
|
||||||
{
|
{
|
||||||
int val = (int) strtol((char *)hint->value,
|
int val = (int) strtol((char *)hint->value,
|
||||||
(char **)NULL, 10);
|
(char **)NULL, 10);
|
||||||
@ -2047,8 +2045,7 @@ static int routeQuery(
|
|||||||
}
|
}
|
||||||
succp = false;
|
succp = false;
|
||||||
ret = 0;
|
ret = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (succp) /*< Have DCB of the target backend */
|
if (succp) /*< Have DCB of the target backend */
|
||||||
@ -2340,21 +2337,28 @@ static void clientReply (
|
|||||||
{
|
{
|
||||||
uint8_t* buf =
|
uint8_t* buf =
|
||||||
(uint8_t *)GWBUF_DATA((scur->scmd_cur_cmd->my_sescmd_buf));
|
(uint8_t *)GWBUF_DATA((scur->scmd_cur_cmd->my_sescmd_buf));
|
||||||
size_t len = MYSQL_GET_PACKET_LEN(buf);
|
uint8_t* replybuf = (uint8_t *)GWBUF_DATA(writebuf);
|
||||||
char* cmdstr = (char *)malloc(len+1);
|
size_t len = MYSQL_GET_PACKET_LEN(buf);
|
||||||
/** data+termination character == len */
|
size_t replylen = MYSQL_GET_PACKET_LEN(replybuf);
|
||||||
snprintf(cmdstr, len, "%s", &buf[5]);
|
char* cmdstr = strndup(&((char *)buf)[5], len-4);
|
||||||
|
char* err = strndup(&((char *)replybuf)[8], 5);
|
||||||
|
char* replystr = strndup(&((char *)replybuf)[13],
|
||||||
|
replylen-4-5);
|
||||||
|
|
||||||
ss_dassert(len+4 == GWBUF_LENGTH(scur->scmd_cur_cmd->my_sescmd_buf));
|
ss_dassert(len+4 == GWBUF_LENGTH(scur->scmd_cur_cmd->my_sescmd_buf));
|
||||||
|
|
||||||
LOGIF(LE, (skygw_log_write_flush(
|
LOGIF(LE, (skygw_log_write_flush(
|
||||||
LOGFILE_ERROR,
|
LOGFILE_ERROR,
|
||||||
"Error : Failed to execute %s in %s:%d.",
|
"Error : Failed to execute %s in %s:%d. %s %s",
|
||||||
cmdstr,
|
cmdstr,
|
||||||
bref->bref_backend->backend_server->name,
|
bref->bref_backend->backend_server->name,
|
||||||
bref->bref_backend->backend_server->port)));
|
bref->bref_backend->backend_server->port,
|
||||||
|
err,
|
||||||
|
replystr)));
|
||||||
|
|
||||||
free(cmdstr);
|
free(cmdstr);
|
||||||
|
free(err);
|
||||||
|
free(replystr);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GWBUF_IS_TYPE_SESCMD_RESPONSE(writebuf))
|
if (GWBUF_IS_TYPE_SESCMD_RESPONSE(writebuf))
|
||||||
@ -3518,7 +3522,9 @@ static bool execute_sescmd_in_backend(
|
|||||||
#endif /*< SS_DEBUG */
|
#endif /*< SS_DEBUG */
|
||||||
switch (scur->scmd_cur_cmd->my_sescmd_packet_type) {
|
switch (scur->scmd_cur_cmd->my_sescmd_packet_type) {
|
||||||
case MYSQL_COM_CHANGE_USER:
|
case MYSQL_COM_CHANGE_USER:
|
||||||
rc = dcb->func.auth(
|
/** This makes it possible to handle replies correctly */
|
||||||
|
gwbuf_set_type(scur->scmd_cur_cmd->my_sescmd_buf, GWBUF_TYPE_SESCMD);
|
||||||
|
rc = dcb->func.auth(
|
||||||
dcb,
|
dcb,
|
||||||
NULL,
|
NULL,
|
||||||
dcb->session,
|
dcb->session,
|
||||||
|
@ -27,9 +27,9 @@ find_path(MySQL_INCLUDE_DIR mysql.h
|
|||||||
include_directories(${MySQL_INCLUDE_DIR})
|
include_directories(${MySQL_INCLUDE_DIR})
|
||||||
|
|
||||||
#MariaDB Corporation
|
#MariaDB Corporation
|
||||||
find_path(MariaDB Corporation_INCLUDE_DIR skygw_debug.h
|
find_path(MariaDB_Corporation_INCLUDE_DIR skygw_debug.h
|
||||||
/usr/local/include /usr/include ../utils)
|
/usr/local/include /usr/include ../utils)
|
||||||
include_directories(${MariaDB Corporation_INCLUDE_DIR})
|
include_directories(${MariaDB_Corporation_INCLUDE_DIR})
|
||||||
include_directories(../replication_listener)
|
include_directories(../replication_listener)
|
||||||
|
|
||||||
#log_manager
|
#log_manager
|
||||||
|
@ -15,9 +15,9 @@ find_path(MySQL_INCLUDE_DIR mysql.h
|
|||||||
include_directories(${MySQL_INCLUDE_DIR})
|
include_directories(${MySQL_INCLUDE_DIR})
|
||||||
|
|
||||||
#MariaDB Corporation
|
#MariaDB Corporation
|
||||||
find_path(MariaDB Corporation_INCLUDE_DIR skygw_debug.h
|
find_path(MariaDB_Corporation_INCLUDE_DIR skygw_debug.h
|
||||||
/usr/local/include /usr/include ../../utils)
|
/usr/local/include /usr/include ../../utils)
|
||||||
include_directories(${MariaDB Corporation_INCLUDE_DIR})
|
include_directories(${MariaDB_Corporation_INCLUDE_DIR})
|
||||||
|
|
||||||
find_path(TRC_INCLUDE_DIR table_replication_consistency.h
|
find_path(TRC_INCLUDE_DIR table_replication_consistency.h
|
||||||
../ /usr/include /usr/local/include)
|
../ /usr/include /usr/local/include)
|
||||||
|
2
test.inc
2
test.inc
@ -43,5 +43,5 @@ TMASTER_ID :=
|
|||||||
# Global test log where all log is gathered
|
# Global test log where all log is gathered
|
||||||
# TEST_MAXSCALE_LOG := $(ROOT_PATH)/test/test_maxscale.log
|
# TEST_MAXSCALE_LOG := $(ROOT_PATH)/test/test_maxscale.log
|
||||||
#
|
#
|
||||||
TEST_MAXSCALE_LOG := /home/mbrampton/Dropbox/skygit/MaxScale/test/testserver.log
|
TEST_MAXSCALE_LOG :=
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user