Allow socket and address/port to be used with maxadmin

It's now possible to use both a Unix domain socket and host/port
when connecting with MaxAdmin to MaxScale.

By default MaxAdmin will attempt to use the default Unix domain
socket, but if host and/or port has been specified, then an inet
socket will be used.

maxscaled will authenticate the connection attempt differently
depending on whether a Unix domain socket is used or not. If
a Unix domain socket is used, then the Linux user id will be
used for the authorization, otherwise the 1.4.3 username/password
handshake will be performed.

adminusers has now been extended so that there is one set of
functions for local users (connecting locally over a Unix socket)
and one set of functions for remote users (connecting locally
or remotely over an Inet socket).

The local users are stored in the new .../maxscale-users and the
remote users in .../passwd. That is, the old users of a 1.4
installation will work as such in 2.0.

One difference is that there will be *no* default remote user.
That is, remote users will always have to be added manually using
a local user.

The implementation is shared; the local and remote alternatives
use common functions to which the hashtable and filename to be
used are forwarded.

The commands "[add|remove] user" behave now exactly like they did
in 1.4.3, and also all existing users work out of the box.

In addition there is now the commands "[enable|disable] account"
using which Linux accounts can be enabled for MaxAdmin usage.
This commit is contained in:
Johan Wikman 2016-08-30 14:33:00 +03:00
parent d337aa0476
commit a9b0a5550c
20 changed files with 1175 additions and 451 deletions

View File

@ -3,6 +3,7 @@
# The Maxscale Administrative & Monitoring Client Application
- [Overview](#overview)
- [Configuring MariaDB MaxScale for MaxAdmin](#configuring)
- [Running MaxAdmin](#running)
- [Working With Administration Interface Users](#interface)
- [Getting Help](#help)
@ -14,7 +15,6 @@
- [Working with Monitors](#monitors)
- [MariaDB MaxScale Status Commands](#statuscommands)
- [Administration Commands](#admincommands)
- [Configuring MariaDB MaxScale to Accept MaxAdmin Connections](#connections)
- [Tuning MariaDB MaxScale](#tuning)
<a name="overview"></a>
@ -30,53 +30,145 @@ MaxAdmin supports
* Execution of command scripts
<a name="configuring"></a>
# Configuring MariaDB MaxScale for MaxAdmin
In order to be able to use MaxAdmin, MariaDB MaxScale must be configured for it.
There are two ways MaxAdmin can connect to to MaxScale.
* Using a Unix domain socket.
* Using a hostname and port.
The first alternative is introduced in MaxScale 2.0 and is the secure and
recommended way. The second alternative is available for backward compatibility,
but is _insecure_ and **deprecated** and _will be removed in a future version of
MaxScale_.
An example configuration looks as follows:
```
[MaxAdmin]
type=service
router=cli
[MaxAdmin Unix Listener]
type=listener
service=MaxAdmin
protocol=maxscaled
socket=default
[MaxAdmin Inet Listener]
type=listener
service=MaxAdmin
protocol=maxscaled
address=localhost
port=6603
```
In the configuration above, two listeners are created; one listening on the default
Unix domain socket and one listening on the default port.
Which approach is used has other implications than just how the communication between
MaxAdmin and MariaDB MaxScale is handled. In the former case, the authorization is
based upon the Linux identity and in the latter case on explicitly created user
accounts that have **no** relationship to the Linux accounts.
Note that if the socket path or port are changed, then MaxAdmin has to be invoked
with `-S` or `-P` respectively.
<a name="running"></a>
# Running MaxAdmin
Depending on whether MariaDB MaxScale has been configured to use Unix domain sockets
or internet sockets, MaxAdmin needs to be invoked slightly differently.
If Unix domain sockets are used, then MaxAdmin needs no additional arguments:
alice@host$ maxadmin
MaxAdmin>
The above implies that the Linux user _alice_ has been enabled to use MaxAdmin.
If internet sockets are used, then either the host, port, user or password has
to be specified explicitly:
alice@host$ maxadmin -u maxscale-admin
Password:
MaxScale>
If Unix domain sockets are used, then initially only `root` has access. MaxAdmin
usage can subsequently be enabled for other Linux users.
The MaxAdmin client application may be run in two different modes, either as an interactive command shell for executing commands against MariaDB MaxScale or by passing commands on the MaxAdmin command line itself.
<a name="interface"></a>
# Working With Administration Interface Users
MaxScale communicates with MariaDB MaxScale using UNIX domain sockets, which means that it can only be used on the very host where MariaDB MaxScale is running. Initially MaxAdmin can connect only when run as `root`. Other Linux users can subsequently be allowed access.
**NOTE**: Remote access with -h and -P options is no longer supported.
## What Users Have Been Defined?
In order to see the current users (UNIX users) that have been defined for the administration interface use the command _show users_.
In order to see the Linux users for whom MaxAdmin usage has been enabled and
any explicitly created accounts, use the command _show users_.
MaxScale> show users
Administration interface users:
User names: vilho, dba, massi, mark
Enabled Linux accounts (secure) : alice, bob, cecil
Created network accounts (insecure): maxscale-admin
MaxScale>
Please note `root` will not be shown.
Please note that `root` will not be shown.
MaxScale> show users
Administration interface users:
No administration users have been defined.
MaxScale>
## Enabling a Linux account
To enable MaxAdmin usage for a particular Linux account, use the command _enable account_.
This command is passed a user name, which should be the same as that of an existing Linux user.
MaxScale> enable account bob
Note that it is not checked that the provided name indeed corresponds to an existing
Linux account, so it is possible to enable an account that does not exist yet.
Note also that it is possible to enable a Linux account irrespective of how MaxAdmin
has connected to MariaDB MaxScale. That is, the command is not restricted to MaxAdmin
users connecting over a Unix domain socket.
## Disabling a Linux account
To disable MaxAdmin usage for a particular Linux account, use the command _disable account_.
This command is passed a user name, which should be a Linux user for whom MaxAdmin usage
earlier has been enabled.
MaxScale> disable account bob
Note also that it is possible to disable a Linux account irrespective of how MaxAdmin
has connected to MariaDB MaxScale. That is, the command is not restricted to MaxAdmin
users connecting over a Unix domain socket.
Note that it is possible to disable the current user, but that will only affect the
next attempt to use MaxAdmin. `root` cannot be removed.
## Add A New User
To add a new administrative user to the MariaDB MaxScale server use the command _add user_. This command is passed a user name, which should be the same as that of an existing Linux user.
To add a new MaxAdmin user to be used when MaxAdmin connects over an internet socket,
use the command _add user_. This command is passed a user name and a password.
MaxScale> add user maria
User maria has been successfully added.
MaxScale> add user maxscale-admin secretpwd
User maxscale-admin has been successfully added.
MaxScale>
Note that a user that is given the rights to use MaxAdmin, has the very same rights `root` has, including adding and removing users.
Note that there is no difference in rights between an enabled Linux account and an
explicitly created user.
## Delete A User
To remove a user the command _remove user_ is used and it is simply invoked with the user to be removed.
To remove a user the command _remove user_ is used and it is invoked with the
username and password.
MaxScale> remove user maria
User maria has been successfully removed.
MaxScale> remove user maxscale-admin secretpwd
User maxscale-admin has been successfully removed.
MaxScale>
Note that it is possible to remove the current user, but that will only affect the next attempt to use MaxAdmin. `root` cannot be removed.
Note that it is possible to remove the current user, but that will only affect the
next attempt to use MaxAdmin.
# Command Line Switches
@ -93,6 +185,26 @@ The MaxAdmin command accepts a number of switches
<td>--socket=...</td>
<td>The UNIX domain socket path that MaxAdmin will use to connect to the MariaDB MaxScale server. If no -S option is given then the default socket path /tmp/maxadmin.sock will be used.</td>
</tr>
<tr>
<td>-u user</td>
<td>--user=...</td>
<td>Sets the username that will be used for the MaxScale connection. If no -u option is passed on the MaxAdmin command line then the default username of ‘admin’ will be used.</td>
</tr>
<tr>
<td>-p password</td>
<td>--password=...</td>
<td>Sets the user password that will be used. If no -p option is passed on the command line then MaxAdmin will prompt for interactive entry of the password.</td>
</tr>
<tr>
<td>-h hostname</td>
<td>--hostname=...</td>
<td>The hostname of the MaxScale server to connect to. If no -h option is passed on the command line then MaxAdmin will attempt to connect to the host ‘localhost’.</td>
</tr>
<tr>
<td>-P port</td>
<td>--port=...</td>
<td>The port that MaxAdmin will use to connect to the MaxScale server. if no -P option is given then the default port of 6603 will be used.</td>
</tr>
<tr>
<td>-?</td>
<td>--help</td>
@ -835,28 +947,6 @@ A command, _reload config_, is available that will cause MariaDB MaxScale to rel
The MariaDB MaxScale server may be shutdown using the _shutdown maxscale_ command.
<a name="connections"></a>
# Configuring MariaDB MaxScale to Accept MaxAdmin Connections
In order to enable the use of MaxAdmin, the service CLI with an accompanying listener must be added to the MariaDB MaxScale configuration file.
The default entries required are shown below.
[CLI]
type=service
router=cli
[CLI Listener]
type=listener
service=CLI
protocol=maxscaled
socket=default
#socket=/somepath/maxadmin.socket
**NOTE**: As the protocol maxscaled only supports UNIX domain sockets it is thus not possible to connect remotely to MariaDB MaxScale.
In the example, the default socket path (/tmp/maxadmin.sock) is used. It can be changed to an specific path and in that case MaxAdmin must be invoked with the -S option.
<a name="tuning"></a>
# Tuning MariaDB MaxScale

View File

@ -5,16 +5,12 @@ Release 2.0.1 is a GA release.
This document describes the changes in release 2.0.1, when compared to
[release 2.0.0](MaxScale-2.0.0-Release-Notes.md).
If you are upgrading from 1.4.3, please also read the release notes
of [2.0.0](./MaxScale-2.0.0-Release-Notes.md).
For any problems you encounter, please consider submitting a bug
report at [Jira](https://jira.mariadb.org).
## License
The license of MaxScale has been changed from GPLv2 to MariaDB BSL.
For more information about MariaDB BSL, please refer to
[MariaDB BSL](https://www.mariadb.com/bsl).
## Updated Features
### Routing hint priority change
@ -34,6 +30,42 @@ would be routed to the slave.
[ReadWriteSplit](../Routers/ReadWriteSplit.md) documentation before using
routing hints.
### MaxAdmin Usage
In 2.0.0 (Beta), the authentication mechanism of MaxAdmin was completely
changed, so that MaxAdmin could only connect to MaxScale using a Unix domain
socket, thus _only when run on the same host_, and authorization was based
on the Unix identity. Remote access was no longer supported.
To the user this was visible so that while you in 1.4.3 had to provide
a password when starting _maxadmin_ and when adding a user
```
user@host $ maxadmin -p password
MaxAdmin> add user john johns-password
```
in 2.0.0 (Beta), where only Unix domain sockets could be used, you did not
have to provide a password neither when starting _maxadmin_, nor when adding
users
```
user@host $ maxadmin
MaxAdmin> add user john
```
as the MaxScale user corresponded to a Unix user, provided the Linux user
had been added as a user of MaxScale.
In 2.0.1 (GA) this has been changed so that the 1.4.3 behaviour is intact
but _deprecated_, and the 2.0.0 (Beta) behaviour is exposed using a new set
of commands:
```
MaxAdmin> enable account alice
MaxAdmin> disable account alice
```
Note that the way you need to invoke _maxadmin_ depends upon how MariaDB
MaxScale has been configued.
Please consult
[MaxAdmin documentation](../Reference/MaxAdmin.md) for more details.
## Bug fixes
[Here is a list of bugs fixed since the release of MaxScale 2.0.1.](https://jira.mariadb.org/issues/?jql=project%20%3D%20MXS%20AND%20issuetype%20%3D%20Bug%20AND%20status%20%3D%20Closed%20AND%20fixVersion%20in%20(2.0.0%2C%202.0.1)%20AND%20resolved%20%3E%3D%20-21d%20ORDER%20BY%20priority%20DESC%2C%20updated%20DESC)
@ -43,12 +75,10 @@ would be routed to the slave.
* [MXS-842](https://jira.mariadb.org/browse/MXS-842): Unexpected / undocumented behaviour when multiple available masters from mmmon monitor
* [MXS-846](https://jira.mariadb.org/browse/MXS-846): MMMon: Maintenance mode on slave logs error message every second
## Known Issues and Limitations
There are some limitations and known issues within this version of MaxScale.
For more information, please refer to the [Limitations](../About/Limitations.md)
document.
For more information, please refer to the [Limitations](../About/Limitations.md) document.
## Packaging

View File

@ -1,10 +1,13 @@
# 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.
The command line interface as used by `maxadmin`. The _CLI_ router requires the use
of the `maxscaled` protocol.
## 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.
Two components are required in order to run the command line interface for use with
_maxadmin_; a service and a listener. The listener may either use a Unix domain socket
or an internet socket.
The default entries required are shown below.
@ -13,9 +16,34 @@ The default entries required are shown below.
type=service
router=cli
[CLI Listener]
# Unix Domain Socket
[CLI Unix Listener]
type=listener
service=CLI
protocol=maxscaled
socket=default
# Internet Socket
[CLI Inet Listener]
type=listener
service=CLI
protocol=maxscaled
address=localhost
port=6603
```
In the example above, two listeners have been specified; one that listens on the
default Unix domain socket and one that listens on the default port. In the latter
case, if the `address=` entry is removed, connections are allowed from any machine
on your network.
In the former case, if the value of `socket` is changed and in the latter case,
if the value of `port` is changed, _maxadmin_ must be invoked with the `-S` and
`-P` options respectively.
Note that if Unix domain sockets are used, the connection is secure, but _maxadmin_
can only be used on the same host where MariaDB MaxScale runs. If internet sockets
are used, the connection is _inherently insecure_ but _maxadmin_ can be used from
another host than the one where MariaDB MaxScale runs.
Note that the latter approach is **deprecated** and will be removed in a future
version of MariaDB MaxScale.

View File

@ -117,9 +117,6 @@ The final stage in the configuration is to add the option service which is used
protocol=maxscaled
socket=default
**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set.
Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled.
## Starting MariaDB MaxScale
Upon completion of the configuration process MariaDB MaxScale is ready to be started for the first time. This may either be done manually by running the maxscale command or via the service interface.

View File

@ -147,9 +147,6 @@ protocol=maxscaled
socket=default
```
**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set.
Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled.
## Starting MariaDB MaxScale
Upon completion of the configuration process MariaDB MaxScale is ready to be started for the first time. This may either be done manually by running the maxscale command or via the service interface.

View File

@ -174,8 +174,6 @@ service=CLI
protocol=maxscaled
socket=default
```
**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set.
Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled.
# Starting MariaDB MaxScale

View File

@ -133,8 +133,6 @@ service=CLI
protocol=maxscaled
socket=default
```
**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set.
Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled.
# Starting MariaDB MaxScale

View File

@ -62,8 +62,7 @@ service=AdminInterface
protocol=maxscaled
socket=default
```
**Note**: maxscaled protocol supports only UNIX domain sockets and in the example default is set.
Changing it requires maxadmin to use -S with the new path. Default /tmp/maxadmin.sock is for both maxadmin and maxscaled.
## Prepare Nagios configuration files.
Assuming Nagios installed on a separated server and the plugins are in /usr/lib64/nagios/plugins and configuration files are in /etc/nagios:

View File

@ -208,7 +208,7 @@ router=cli
type=listener
service=MaxAdmin Service
protocol=maxscaled
port=6603
socket=default
```
Now we have created the MariaDB MaxScale configuration file and all we need to do is to save

View File

@ -12,37 +12,18 @@ configuration file.
## MaxAdmin
The way a user of MaxAdmin is authenticated has been completely changed.
In 2.0, MaxAdmin can only connect to MariaDB MaxScale using a domain socket, thus
_only when run on the same host_, and authorization is based upon the UNIX
identity. Remote access is no longer supported.
The default way the communication between MaxAdmin and MariaDB MaxScale is
handled has been changed from an internet socket to a Unix domain socket.
The former alternative is still available but has been _deprecated_.
When 2.0 has been installed, MaxAdmin can only be used by `root` and
other users must be added anew. Please consult
[MaxAdmin documentation](../Reference/MaxAdmin.md) for more details.
If no arguments are given to MaxAdmin, it will attempt to connect to
MariaDB MaxScale using a Unix domain socket. After the upgrade you will
need to provide at least one internet socket related flag - `-h`, `-P`,
`-u` or `-p` - to force MaxAdmin to use the internet socket approach.
This change requires the _maxscaled_ protocol listener entry in the
MaxScale configuration file to be updated; address and port information
must be replaced with socket information. For instance, an entry like
```
[MaxAdmin Listener]
type=listener
protocol=maxscaled
address=localhost
port=6603
```
should be updated to
```
[MaxAdmin Listener]
type=listener
protocol=maxscaled
socket=default
```
where `default` corresponds to `/tmp/maxadmin.sock`.
E.g.
Note that if this update is *not* made, maxscaled will log a warning
and use the default socket path. This behaviour may change in later
releases of MaxScale.
user@host $ maxadmin -u admin
## MySQL Monitor

View File

@ -27,6 +27,7 @@
* @endverbatim
*/
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
@ -45,6 +46,7 @@
#include <locale.h>
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <version.h>
@ -60,15 +62,21 @@
#define STRERROR_BUFLEN 512
#endif
static int connectMaxScale(char *socket);
static int setipaddress(struct in_addr *a, char *p);
static int authMaxScale(int so);
#define MAX_PASSWORD_LEN 80
static int connectUsingUnixSocket(const char *socket);
static int connectUsingInetSocket(const char *hostname, const char *port,
const char *user, const char* password);
static int setipaddress(struct in_addr *a, const char *p);
static bool authUnixSocket(int so);
static bool authInetSocket(int so, const char *user, const char *password);
static int sendCommand(int so, char *cmd);
static void DoSource(int so, char *cmd);
static void DoUsage(const char*);
static int isquit(char *buf);
static void PrintVersion(const char *progname);
static void read_inifile(char **, int*);
static bool getPassword(char *password, size_t length);
#ifdef HISTORY
@ -79,10 +87,15 @@ prompt(EditLine *el __attribute__((__unused__)))
return prompt;
}
#endif
static struct option long_options[] =
{
{"host", required_argument, 0, 'h'},
{"user", required_argument, 0, 'u'},
{"password", required_argument, 0, 'p'},
{"port", required_argument, 0, 'P'},
{"socket", required_argument, 0, 'S'},
{"version", no_argument, 0, 'v'},
{"help", no_argument, 0, '?'},
@ -90,6 +103,10 @@ static struct option long_options[] =
{0, 0, 0, 0}
};
#define MAXADMIN_DEFAULT_HOST "localhost"
#define MAXADMIN_DEFAULT_PORT "6603"
#define MAXADMIN_DEFAULT_USER "admin"
/**
* The main for the maxadmin client
*
@ -112,7 +129,12 @@ main(int argc, char **argv)
#else
char buf[1024];
#endif
char *conn_socket = MAXADMIN_DEFAULT_SOCKET;
char *hostname = NULL;
char *port = NULL;
char *user = NULL;
char *passwd = NULL;
char *conn_socket = NULL;
char *default_socket = MAXADMIN_DEFAULT_SOCKET;
int use_emacs = 0;
int so;
int option_index = 0;
@ -120,11 +142,28 @@ main(int argc, char **argv)
read_inifile(&conn_socket, &use_emacs);
while ((c = getopt_long(argc, argv, "S:v?e",
while ((c = getopt_long(argc, argv, "h:p:P:u:S:v?e",
long_options, &option_index)) >= 0)
{
switch (c)
{
case 'h':
hostname = strdup(optarg);
break;
case 'p':
passwd = strdup(optarg);
memset(optarg, '\0', strlen(optarg));
break;
case 'P':
port = strdup(optarg);
break;
case 'u':
user = strdup(optarg);
break;
case 'S':
conn_socket = strdup(optarg);
break;
@ -138,22 +177,72 @@ main(int argc, char **argv)
break;
case '?':
DoUsage(*argv);
DoUsage(argv[0]);
exit(optopt ? EXIT_FAILURE : EXIT_SUCCESS);
}
}
/* Connet to MaxScale using UNIX domain socket */
if ((so = connectMaxScale(conn_socket)) == -1)
if ((hostname || port || user || passwd) && (conn_socket))
{
exit(1);
// Either socket or any parameters related to hostname/port.
DoUsage(argv[0]);
exit(EXIT_FAILURE);
}
/* Check for successful authentication */
if (!authMaxScale(so))
if (hostname || port || user || passwd)
{
fprintf(stderr, "Failed to connect to MaxScale, incorrect username.\n");
exit(1);
assert(!conn_socket);
if (!hostname)
{
hostname = MAXADMIN_DEFAULT_HOST;
}
if (!port)
{
port = MAXADMIN_DEFAULT_PORT;
}
if (!user)
{
user = MAXADMIN_DEFAULT_USER;
}
}
else
{
if (!conn_socket)
{
conn_socket = MAXADMIN_DEFAULT_SOCKET;
}
}
assert(!((hostname || port) && conn_socket));
if (conn_socket)
{
if ((so = connectUsingUnixSocket(conn_socket)) == -1)
{
exit(EXIT_FAILURE);
}
}
else
{
char password[MAX_PASSWORD_LEN];
if (passwd == NULL)
{
if (!getPassword(password, MAX_PASSWORD_LEN))
{
exit(EXIT_FAILURE);
}
passwd = password;
}
if ((so = connectUsingInetSocket(hostname, port, user, passwd)) == -1)
{
exit(EXIT_FAILURE);
}
}
if (optind < argc)
@ -309,44 +398,114 @@ main(int argc, char **argv)
* @return The connected socket or -1 on error
*/
static int
connectMaxScale(char *conn_socket)
connectUsingUnixSocket(const char *conn_socket)
{
struct sockaddr_un local_addr;
int so;
int keepalive = 1;
int optval = 1;
int so = -1;
if ((so = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
if ((so = socket(AF_UNIX, SOCK_STREAM, 0)) != -1)
{
struct sockaddr_un local_addr;
memset(&local_addr, 0, sizeof local_addr);
local_addr.sun_family = AF_UNIX;
strncpy(local_addr.sun_path, conn_socket, sizeof(local_addr.sun_path) - 1);
if (connect(so, (struct sockaddr *) &local_addr, sizeof(local_addr)) == 0)
{
int keepalive = 1;
if (setsockopt(so, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)))
{
fprintf(stderr, "Warning: Could not set keepalive.\n");
}
/* Client is sending connection credentials (Pid, User, Group) */
int optval = 1;
if (setsockopt(so, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) == 0)
{
if (!authUnixSocket(so))
{
close(so);
so = -1;
}
}
else
{
char errbuf[STRERROR_BUFLEN];
fprintf(stderr, "Could not set SO_PASSCRED: %s\n",
strerror_r(errno, errbuf, sizeof(errbuf)));
close(so);
so = -1;
}
}
else
{
char errbuf[STRERROR_BUFLEN];
fprintf(stderr, "Unable to connect to MaxScale at %s: %s\n",
conn_socket, strerror_r(errno, errbuf, sizeof(errbuf)));
close(so);
so = -1;
}
}
else
{
char errbuf[STRERROR_BUFLEN];
fprintf(stderr, "Unable to create socket: %s\n",
strerror_r(errno, errbuf, sizeof(errbuf)));
return -1;
}
memset(&local_addr, 0, sizeof local_addr);
local_addr.sun_family = AF_UNIX;
strncpy(local_addr.sun_path, conn_socket, sizeof(local_addr.sun_path)-1);
return so;
}
if (connect(so, (struct sockaddr *) &local_addr, sizeof(local_addr)) < 0)
/**
* Connect to the MaxScale server
*
* @param hostname The hostname to connect to
* @param port The port to use for the connection
* @return The connected socket or -1 on error
*/
static int
connectUsingInetSocket(const char *hostname, const char *port,
const char *user, const char *passwd)
{
int so;
if ((so = socket(AF_INET, SOCK_STREAM, 0)) != -1)
{
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
setipaddress(&addr.sin_addr, hostname);
addr.sin_port = htons(atoi(port));
if (connect(so, (struct sockaddr *) &addr, sizeof(addr)) == 0)
{
int keepalive = 1;
if (setsockopt(so, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)))
{
fprintf(stderr, "Warning: Could not set keepalive.\n");
}
if (!authInetSocket(so, user, passwd))
{
close(so);
so = -1;
}
}
else
{
char errbuf[STRERROR_BUFLEN];
fprintf(stderr, "Unable to connect to MaxScale at %s, %s: %s\n",
hostname, port, strerror_r(errno, errbuf, sizeof(errbuf)));
close(so);
so = -1;
}
}
else
{
char errbuf[STRERROR_BUFLEN];
fprintf(stderr, "Unable to connect to MaxScale at %s: %s\n",
conn_socket, strerror_r(errno, errbuf, sizeof(errbuf)));
close(so);
return -1;
}
if (setsockopt(so, SOL_SOCKET, SO_KEEPALIVE, &keepalive, sizeof(keepalive)))
{
perror("setsockopt");
}
/* Client is sending connection credentials (Pid, User, Group) */
if (setsockopt(so, SOL_SOCKET, SO_PASSCRED, &optval, sizeof(optval)) != 0)
{
fprintf(stderr, "SO_PASSCRED failed\n");
return -1;
fprintf(stderr, "Unable to create socket: %s\n",
strerror_r(errno, errbuf, sizeof(errbuf)));
}
return so;
@ -360,7 +519,7 @@ connectMaxScale(char *conn_socket)
* @return 1 on success, 0 on failure
*/
static int
setipaddress(struct in_addr *a, char *p)
setipaddress(struct in_addr *a, const char *p)
{
#ifdef __USE_POSIX
struct addrinfo *ai = NULL, hint;
@ -418,17 +577,84 @@ setipaddress(struct in_addr *a, char *p)
* @param so The socket connected to MaxScale
* @return Non-zero of succesful authentication
*/
static int
authMaxScale(int so)
static bool
authUnixSocket(int so)
{
char buf[MAXADMIN_AUTH_REPLY_LEN];
if (read(so, buf, MAXADMIN_AUTH_REPLY_LEN) != MAXADMIN_AUTH_REPLY_LEN)
{
fprintf(stderr, "Could not read authentication response from MaxScale.\n");
return 0;
}
return strncmp(buf, MAXADMIN_FAILED_AUTH_MESSAGE, MAXADMIN_AUTH_REPLY_LEN);
bool authenticated = (strncmp(buf, MAXADMIN_AUTH_SUCCESS_REPLY, MAXADMIN_AUTH_REPLY_LEN) == 0);
if (!authenticated)
{
fprintf(stderr, "Could connect to MaxScale, but was not authorized.\n");
}
return authenticated;
}
/**
* Perform authentication using the maxscaled protocol conventions
*
* @param so The socket connected to MaxScale
* @param user The username to authenticate
* @param password The password to authenticate with
* @return Non-zero of succesful authentication
*/
static bool
authInetSocket(int so, const char *user, const char *password)
{
char buf[20];
size_t len;
len = MAXADMIN_AUTH_USER_PROMPT_LEN;
if (read(so, buf, len) != len)
{
fprintf(stderr, "Could not read user prompt from MaxScale.\n");
return false;
}
len = strlen(user);
if (write(so, user, len) != len)
{
fprintf(stderr, "Could not write user to MaxScale.\n");
return false;
}
len = MAXADMIN_AUTH_PASSWORD_PROMPT_LEN;
if (read(so, buf, len) != len)
{
fprintf(stderr, "Could not read password prompt from MaxScale.\n");
return false;
}
len = strlen(password);
if (write(so, password, len) != len)
{
fprintf(stderr, "Could not write password to MaxScale.\n");
return false;
}
len = MAXADMIN_AUTH_REPLY_LEN;
if (read(so, buf, len) != len)
{
fprintf(stderr, "Could not read authentication response from MaxScale.\n");
return false;
}
bool authenticated = (strncmp(buf, MAXADMIN_AUTH_SUCCESS_REPLY, MAXADMIN_AUTH_REPLY_LEN) == 0);
if (!authenticated)
{
fprintf(stderr, "Could connect to MaxScale, but was not authorized.\n");
}
return authenticated;
}
/**
@ -550,13 +776,27 @@ DoUsage(const char *progname)
{
PrintVersion(progname);
printf("The MaxScale administrative and monitor client.\n\n");
printf("Usage: %s [-S socket] [<command file> | <command>]\n\n", progname);
printf(" -S|--socket=... The UNIX socket to connect to, The default is\n");
printf(" /tmp/maxadmin.sock\n");
printf(" -v|--version print version information and exit\n");
printf(" -?|--help Print this help text.\n");
printf("Usage: %s [(-S socket)|([-u user] [-p password] [-h hostname] [-P port])]"
"[<command file> | <command>]\n\n", progname);
printf(" -S|--socket=... The UNIX domain socket to connect to, The default is\n");
printf(" %s\n", MAXADMIN_DEFAULT_SOCKET);
printf(" -u|--user=... The user name to use for the connection, default\n");
printf(" is %s.\n", MAXADMIN_DEFAULT_USER);
printf(" -p|--password=... The user password, if not given the password will\n");
printf(" be prompted for interactively\n");
printf(" -h|--host=... The maxscale host to connecto to. The default is\n");
printf(" %s\n", MAXADMIN_DEFAULT_HOST);
printf(" -P|--port=... The port to use for the connection, the default\n");
printf(" port is %s.\n", MAXADMIN_DEFAULT_PORT);
printf(" -v|--version Print version information and exit\n");
printf(" -?|--help Print this help text.\n");
printf("\n");
printf("Any remaining arguments are treated as MaxScale commands or a file\n");
printf("containing commands to execute.\n");
printf("\n");
printf("Either a socket or a hostname/port combination should be provided.\n");
printf("If a port or hostname is provided, but not the other, then the default\n"
"value is used.\n");
}
/**
@ -687,3 +927,55 @@ read_inifile(char **conn_socket, int* editor)
}
fclose(fp);
}
/**
* Get password
*
* @param password Buffer for password.
* @param len The size of the buffer.
*
* @return Whether the password was obtained.
*/
bool getPassword(char *passwd, size_t len)
{
bool gotten = false;
struct termios tty_attr;
tcflag_t c_lflag;
if (tcgetattr(STDIN_FILENO, &tty_attr) == 0)
{
c_lflag = tty_attr.c_lflag;
tty_attr.c_lflag &= ~ICANON;
tty_attr.c_lflag &= ~ECHO;
if (tcsetattr(STDIN_FILENO, 0, &tty_attr) == 0)
{
printf("Password: ");
fgets(passwd, len, stdin);
tty_attr.c_lflag = c_lflag;
if (tcsetattr(STDIN_FILENO, 0, &tty_attr) == 0)
{
int i = strlen(passwd);
if (i > 1)
{
passwd[i - 1] = '\0';
}
printf("\n");
gotten = true;
}
}
}
if (!gotten)
{
fprintf(stderr, "Could not configure terminal.\n");
}
return gotten;
}

View File

@ -37,13 +37,25 @@
* 23/07/13 Mark Riddoch Addition of error mechanism to add user
* 23/05/16 Massimiliano Pinto admin_add_user and admin_remove_user
* no longer accept password parameter
* 02/09/16 Johan Wikman Enabled Linux accounts and MaxScale users
*
* @endverbatim
*/
static USERS *loadUsers();
static void initialise();
static USERS *users = NULL;
static USERS *loadLinuxUsers();
static USERS *loadInetUsers();
static const char *admin_add_user(USERS** pusers, const char* fname,
const char* uname, const char* password);
static const char* admin_remove_user(USERS *users, const char* fname,
const char *uname, const char *passwd);
static bool admin_search_user(USERS *users, const char *uname);
static USERS *linux_users = NULL;
static USERS *inet_users = NULL;
static int admin_init = 0;
static char *ADMIN_ERR_NOMEM = "Out of memory";
@ -61,7 +73,11 @@ static char *ADMIN_SUCCESS = NULL;
static const int LINELEN = 80;
static const char USERS_FILE_NAME[] = "maxadmin-users";
static const char LINUX_USERS_FILE_NAME[] = "maxadmin-users";
static const char INET_USERS_FILE_NAME[] = "passwd";
static const char INET_DEFAULT_USERNAME[] = "admin";
static const char INET_DEFAULT_PASSWORD[] = "mariadb";
/**
* Admin Users initialisation
@ -75,124 +91,15 @@ initialise()
}
admin_init = 1;
users = loadUsers();
linux_users = loadLinuxUsers();
inet_users = loadInetUsers();
}
/**
* Verify a username and password
*
* @param username Username to verify
* @param password Password to verify
* @return Non-zero if the username/password combination is valid
*/
bool
admin_remote_verify(const char *username, const char *password)
{
char *pw;
initialise();
if (users == NULL)
{
if (strcmp(username, "admin") == 0 && strcmp(password, "mariadb") == 0)
{
return true;
}
}
else
{
if ((pw = users_fetch(users, (char*)username)) == NULL) // TODO: Make users const-correct.
{
return false;
}
struct crypt_data cdata;
cdata.initialized = 0;
if (strcmp(pw, crypt_r(password, ADMIN_SALT, &cdata)) == 0)
{
return true;
}
}
return false;
}
/**
* Load the admin users
*
* @return Table of users
*/
static USERS *
loadUsers()
{
USERS *rval;
FILE *fp;
char fname[PATH_MAX], *home;
char uname[80];
int added_users = 0;
initialise();
snprintf(fname, sizeof(fname), "%s/%s", get_datadir(), USERS_FILE_NAME);
if ((fp = fopen(fname, "r")) == NULL)
{
return NULL;
}
if ((rval = users_alloc()) == NULL)
{
fclose(fp);
return NULL;
}
while (fgets(uname, sizeof(uname), fp))
{
char *nl = strchr(uname, '\n');
if (nl)
{
*nl = '\0';
}
else if (!feof(fp))
{
MXS_ERROR("Line length exceeds %d characters, possible corrupted "
"'passwd' file in: %s", LINELEN, fname);
users_free(rval);
rval = NULL;
break;
}
char *tmp_ptr = strchr(uname, ':');
if (tmp_ptr)
{
*tmp_ptr = '\0';
MXS_WARNING("Found user '%s' with password. "
"This user might not be compatible with new maxadmin in MaxScale 2.0. "
"Remove it with \"remove user %s\" through MaxAdmin", uname, uname);
}
if (users_add(rval, uname, ""))
{
added_users++;
}
}
fclose(fp);
if (!added_users)
{
users_free(rval);
rval = NULL;
}
return rval;
}
/**
* Add user
*
* @param uname Name of the new user
* @return NULL on success or an error string on failure
*/
const char *admin_local_add_user(const char *uname)
static const char *admin_add_user(USERS** pusers, const char* fname,
const char* uname, const char* password)
{
FILE *fp;
char fname[PATH_MAX], *home;
initialise();
char path[PATH_MAX], *home;
if (access(get_datadir(), F_OK) != 0)
{
@ -202,50 +109,51 @@ const char *admin_local_add_user(const char *uname)
}
}
snprintf(fname, sizeof(fname), "%s/%s", get_datadir(), USERS_FILE_NAME);
if (users == NULL)
snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname);
if (*pusers == NULL)
{
MXS_NOTICE("Create initial password file.");
if ((users = users_alloc()) == NULL)
if ((*pusers = users_alloc()) == NULL)
{
return ADMIN_ERR_NOMEM;
}
if ((fp = fopen(fname, "w")) == NULL)
if ((fp = fopen(path, "w")) == NULL)
{
MXS_ERROR("Unable to create password file %s.", fname);
MXS_ERROR("Unable to create password file %s.", path);
return ADMIN_ERR_PWDFILEOPEN;
}
fclose(fp);
}
if (users_fetch(users, (char*)uname) != NULL) // TODO: Make users const correct.
if (users_fetch(*pusers, (char*)uname) != NULL) // TODO: Make users const correct.
{
return ADMIN_ERR_DUPLICATE;
}
users_add(users, (char*)uname, ""); // TODO: Make users const correct.
if ((fp = fopen(fname, "a")) == NULL)
users_add(*pusers, (char*)uname, password ? (char*)password : ""); // TODO: Make users const correct.
if ((fp = fopen(path, "a")) == NULL)
{
MXS_ERROR("Unable to append to password file %s.", fname);
MXS_ERROR("Unable to append to password file %s.", path);
return ADMIN_ERR_FILEAPPEND;
}
fprintf(fp, "%s\n", uname);
if (password)
{
fprintf(fp, "%s:%s\n", uname, password);
}
else
{
fprintf(fp, "%s\n", uname);
}
fclose(fp);
return ADMIN_SUCCESS;
}
/**
* Remove maxscale user from in-memory structure and from password file
*
* @param uname Name of the new user
* @return NULL on success or an error string on failure
*/
const char* admin_local_remove_user(const char* uname)
static const char* admin_remove_user(USERS *users, const char* fname,
const char *uname, const char *passwd)
{
FILE* fp;
FILE* fp_tmp;
char fname[PATH_MAX];
char fname_tmp[PATH_MAX];
char path[PATH_MAX];
char path_tmp[PATH_MAX];
char* home;
char fusr[LINELEN];
char fpwd[LINELEN];
@ -258,43 +166,53 @@ const char* admin_local_remove_user(const char* uname)
return ADMIN_ERR_DELROOT;
}
if (!admin_local_search_user(uname))
if (!admin_search_user(users, uname))
{
MXS_ERROR("Couldn't find user %s. Removing user failed.", uname);
return ADMIN_ERR_USERNOTFOUND;
}
if (passwd)
{
if (admin_verify_inet_user(uname, passwd) == 0)
{
MXS_ERROR("Authentication failed, wrong user/password "
"combination. Removing user failed.");
return ADMIN_ERR_AUTHENTICATION;
}
}
/** Remove user from in-memory structure */
users_delete(users, (char*)uname); // TODO: Make users const correct.
/**
* Open passwd file and remove user from the file.
*/
snprintf(fname, sizeof(fname), "%s/%s", get_datadir(), USERS_FILE_NAME);
snprintf(fname_tmp, sizeof(fname_tmp), "%s/%s_tmp", get_datadir(), USERS_FILE_NAME);
snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname);
snprintf(path_tmp, sizeof(path_tmp), "%s/%s_tmp", get_datadir(), fname);
/**
* Rewrite passwd file from memory.
*/
if ((fp = fopen(fname, "r")) == NULL)
if ((fp = fopen(path, "r")) == NULL)
{
int err = errno;
MXS_ERROR("Unable to open password file %s : errno %d.\n"
"Removing user from file failed; it must be done "
"manually.",
fname,
path,
err);
return ADMIN_ERR_PWDFILEOPEN;
}
/**
* Open temporary passwd file.
*/
if ((fp_tmp = fopen(fname_tmp, "w")) == NULL)
if ((fp_tmp = fopen(path_tmp, "w")) == NULL)
{
int err = errno;
MXS_ERROR("Unable to open tmp file %s : errno %d.\n"
"Removing user from passwd file failed; it must be done "
"manually.",
fname_tmp,
path_tmp,
err);
fclose(fp);
return ADMIN_ERR_TMPFILEOPEN;
@ -309,11 +227,11 @@ const char* admin_local_remove_user(const char* uname)
MXS_ERROR("Unable to process passwd file %s : errno %d.\n"
"Removing user from file failed, and must be done "
"manually.",
fname,
path,
err);
fclose(fp);
fclose(fp_tmp);
unlink(fname_tmp);
unlink(path_tmp);
return ADMIN_ERR_PWDFILEACCESS;
}
@ -328,7 +246,7 @@ const char* admin_local_remove_user(const char* uname)
else if (!feof(fp))
{
MXS_ERROR("Line length exceeds %d characters, possible corrupted "
"'passwd' file in: %s", LINELEN, fname);
"'passwd' file in: %s", LINELEN, path);
fclose(fp);
fclose(fp_tmp);
return ADMIN_ERR_PWDFILEACCESS;
@ -356,11 +274,11 @@ const char* admin_local_remove_user(const char* uname)
"errno %d.\n"
"Removing user from file failed, and must be "
"done manually.",
fname,
path,
err);
fclose(fp);
fclose(fp_tmp);
unlink(fname_tmp);
unlink(path_tmp);
return ADMIN_ERR_PWDFILEACCESS;
}
}
@ -368,16 +286,16 @@ const char* admin_local_remove_user(const char* uname)
/**
* Replace original passwd file with new.
*/
if (rename(fname_tmp, fname))
if (rename(path_tmp, path))
{
int err = errno;
MXS_ERROR("Unable to rename new passwd file %s : errno "
"%d.\n"
"Rename it to %s manually.",
fname_tmp,
path_tmp,
err,
fname);
unlink(fname_tmp);
path);
unlink(path_tmp);
fclose(fp_tmp);
return ADMIN_ERR_PWDFILEACCESS;
}
@ -385,46 +303,284 @@ const char* admin_local_remove_user(const char* uname)
return ADMIN_SUCCESS;
}
/**
* Check for existance of the user
*
* @param user The user name to test
* @param uname The user name to test
* @return True if the user exists
*/
bool admin_local_search_user(const char *user)
static bool admin_search_user(USERS *users, const char *uname)
{
return (users_fetch(users, (char*)uname) != NULL); // TODO: Make users const correct.
}
/**
*/
void dcb_print_users(DCB *dcb, const char* heading, USERS *users)
{
dcb_printf(dcb, "%s", heading);
if (users)
{
HASHITERATOR *iter = hashtable_iterator(users->data);
if (iter)
{
char *sep = "";
const char *user;
while ((user = hashtable_next(iter)) != NULL)
{
dcb_printf(dcb, "%s%s", sep, user);
sep = ", ";
}
hashtable_iterator_free(iter);
}
}
dcb_printf(dcb, "%s", "\n");
}
/**
* Load the admin users
*
* @return Table of users
*/
static USERS *
loadUsers(const char *fname)
{
USERS *rval;
FILE *fp;
char path[PATH_MAX], *home;
char uname[80];
int added_users = 0;
initialise();
snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname);
if ((fp = fopen(path, "r")) == NULL)
{
return NULL;
}
if ((rval = users_alloc()) == NULL)
{
fclose(fp);
return NULL;
}
while (fgets(uname, sizeof(uname), fp))
{
char *nl = strchr(uname, '\n');
if (nl)
{
*nl = '\0';
}
else if (!feof(fp))
{
MXS_ERROR("Line length exceeds %d characters, possibly corrupted "
"'passwd' file in: %s", LINELEN, path);
users_free(rval);
rval = NULL;
break;
}
char *password;
char *colon = strchr(uname, ':');
if (colon)
{
// Inet case
*colon = 0;
password = colon + 1;
}
else
{
// Linux case.
password = "";
}
if (users_add(rval, uname, password))
{
added_users++;
}
}
fclose(fp);
if (!added_users)
{
users_free(rval);
rval = NULL;
}
return rval;
}
static USERS *loadLinuxUsers()
{
return loadUsers(LINUX_USERS_FILE_NAME);
}
static USERS *loadInetUsers()
{
return loadUsers(INET_USERS_FILE_NAME);
}
/**
* Enable Linux account
*
* @param uname Name of Linux user
*
* @return NULL on success or an error string on failure.
*/
const char *admin_enable_linux_account(const char *uname)
{
initialise();
int rv = 0;
return admin_add_user(&linux_users, LINUX_USERS_FILE_NAME, uname, NULL);
}
if (strcmp(user, DEFAULT_ADMIN_USER) == 0)
/**
* Disable Linux account
*
* @param uname Name of Linux user
*
* @return NULL on success or an error string on failure.
*/
const char* admin_disable_linux_account(const char* uname)
{
initialise();
return admin_remove_user(linux_users, LINUX_USERS_FILE_NAME, uname, NULL);
}
/**
* Check whether Linux account is enabled
*
* @param uname The user name
*
* @return True if the account is enabled, false otherwise.
*/
bool admin_linux_account_enabled(const char *uname)
{
initialise();
bool rv = false;
if (strcmp(uname, DEFAULT_ADMIN_USER) == 0)
{
rv = true;
}
else if (users)
else if (linux_users)
{
rv = (users_fetch(users, (char*)user) != NULL); // TODO: Make users const correct.
rv = admin_search_user(linux_users, uname);
}
return rv;
}
/**
* Print the statistics and user names of the administration users
* Add insecure remote (network) user.
*
* @param dcb A DCB to send the output to
* @param uname Name of the new user.
* @param password Password of the new user.
*
* @return NULL on success or an error string on failure.
*/
void
dcb_PrintAdminUsers(DCB *dcb)
const char *admin_add_inet_user(const char *uname, const char* password)
{
if (users)
initialise();
struct crypt_data cdata;
cdata.initialized = 0;
char *cpassword = crypt_r(password, ADMIN_SALT, &cdata);
return admin_add_user(&inet_users, INET_USERS_FILE_NAME, uname, cpassword);
}
/**
* Remove insecure remote (network) user
*
* @param uname Name of user to be removed.
* @param password Password of user to be removed.
*
* @return NULL on success or an error string on failure.
*/
const char* admin_remove_inet_user(const char* uname, const char *password)
{
initialise();
return admin_remove_user(inet_users, INET_USERS_FILE_NAME, uname, password);
}
/**
* Check for existance of remote user.
*
* @param user The user name to test.
*
* @return True if the user exists, false otherwise.
*/
bool admin_inet_user_exists(const char *uname)
{
initialise();
bool rv = false;
if (inet_users)
{
dcb_usersPrint(dcb, users);
rv = admin_search_user(inet_users, uname);
}
return rv;
}
/**
* Verify a remote user name and password
*
* @param username Username to verify
* @param password Password to verify
*
* @return True if the username/password combination is valid
*/
bool
admin_verify_inet_user(const char *username, const char *password)
{
bool rv = false;
initialise();
if (inet_users)
{
const char* pw = users_fetch(inet_users, (char*)username); // TODO: Make users const-correct.
if (pw)
{
struct crypt_data cdata;
cdata.initialized = 0;
if (strcmp(pw, crypt_r(password, ADMIN_SALT, &cdata)) == 0)
{
rv = true;
}
}
}
else
{
dcb_printf(dcb, "No administration users have been defined.\n");
if (strcmp(username, INET_DEFAULT_USERNAME) == 0
&& strcmp(password, INET_DEFAULT_PASSWORD) == 0)
{
rv = true;
}
}
return rv;
}
/**
* Print Linux and and inet users
*
* @param dcb A DCB to send the output to.
*/
void dcb_PrintAdminUsers(DCB *dcb)
{
dcb_print_users(dcb, "Enabled Linux accounts (secure) : ", linux_users);
dcb_print_users(dcb, "Created network accounts (insecure): ", inet_users);
}

View File

@ -49,12 +49,12 @@
static int
test1()
{
if (admin_remote_verify("admin", "mariadb") == 0)
if (admin_verify_inet_user("admin", "mariadb") == 0)
{
fprintf(stderr, "admin_verify: test 1.1 (default user) failed.\n");
return 1;
}
if (admin_remote_verify("bad", "user"))
if (admin_verify_inet_user("bad", "user"))
{
fprintf(stderr, "admin_verify: test 1.2 (wrong user) failed.\n");
return 1;
@ -75,13 +75,13 @@ test2()
{
const char *err;
if ((err = admin_local_add_user("user0")) != NULL)
if ((err = admin_enable_linux_account("user0")) != NULL)
{
fprintf(stderr, "admin_add_user: test 2.1 (add user) failed, %s.\n", err);
return 1;
}
if (admin_local_add_user("user0") == NULL)
if (admin_enable_linux_account("user0") == NULL)
{
fprintf(stderr, "admin_add_user: test 2.2 (add user) failed, duplicate.\n");
@ -89,7 +89,7 @@ test2()
}
/* Deleting the last user is not forbidden so we expect this to succeed */
if ((err = admin_local_remove_user("user0")) != NULL)
if ((err = admin_disable_linux_account("user0")) != NULL)
{
fprintf(stderr, "admin_remove_user: test 2.3 (add user) failed, %s.\n", err);
@ -97,7 +97,7 @@ test2()
}
/* Add the user back, for test5. */
if ((err = admin_local_add_user("user0")) != NULL)
if ((err = admin_enable_linux_account("user0")) != NULL)
{
fprintf(stderr, "admin_add_user: test 2.4 (add user) failed, %s.\n", err);
@ -121,35 +121,35 @@ test3()
{
const char *err;
if ((err = admin_local_add_user("user1")) != NULL)
if ((err = admin_enable_linux_account("user1")) != NULL)
{
fprintf(stderr, "admin_add_user: test 3.1 (add user) failed, %s.\n", err);
return 1;
}
if (admin_local_search_user("user1") == 0)
if (admin_linux_account_enabled("user1") == 0)
{
fprintf(stderr, "admin_search_user: test 3.2 (search user) failed.\n");
return 1;
}
if (admin_local_search_user("user2") != 0)
if (admin_linux_account_enabled("user2") != 0)
{
fprintf(stderr, "admin_search_user: test 3.3 (search user) failed, unexpeted user found.\n");
return 1;
}
if ((err = admin_local_remove_user("user1")) != NULL)
if ((err = admin_disable_linux_account("user1")) != NULL)
{
fprintf(stderr, "admin_remove_user: test 3.4 (add user) failed, %s.\n", err);
return 1;
}
if (admin_local_search_user("user1"))
if (admin_linux_account_enabled("user1"))
{
fprintf(stderr, "admin_search_user: test 3.5 (search user) failed - user was deleted.\n");
@ -179,7 +179,7 @@ test4()
for (i = 1; i < n_users; i++)
{
sprintf(user, "user%d", i);
if ((err = admin_local_add_user(user)) != NULL)
if ((err = admin_enable_linux_account(user)) != NULL)
{
fprintf(stderr, "admin_add_user: test 4.1 (add user) failed, %s.\n", err);
@ -190,7 +190,7 @@ test4()
for (i = 1; i < n_users; i++)
{
sprintf(user, "user%d", i);
if (admin_local_search_user(user) == 0)
if (admin_linux_account_enabled(user) == 0)
{
fprintf(stderr, "admin_search_user: test 4.2 (search user) failed.\n");
@ -201,7 +201,7 @@ test4()
for (i = 1; i < n_users; i++)
{
sprintf(user, "user%d", i);
if ((err = admin_local_remove_user(user)) != NULL)
if ((err = admin_disable_linux_account(user)) != NULL)
{
fprintf(stderr, "admin_remove_user: test 4.3 (add user) failed, %s.\n", err);
@ -223,14 +223,14 @@ test5()
{
const char *err;
if ((err = admin_local_add_user("user")) != NULL)
if ((err = admin_enable_linux_account("user")) != NULL)
{
fprintf(stderr, "admin_add_user: test 5.1 (add user) failed, %s.\n", err);
return 1;
}
if ((err = admin_local_remove_user("user0")) != NULL)
if ((err = admin_disable_linux_account("user0")) != NULL)
{
fprintf(stderr, "admin_remove_user: test 5.2 (add user) failed, %s.\n", err);

View File

@ -51,11 +51,15 @@ typedef struct admin_session
#endif
} ADMIN_session;
extern const char *admin_local_add_user(const char *uname);
extern const char *admin_local_remove_user(const char *uname);
extern bool admin_local_search_user(const char *uname);
extern const char *admin_enable_linux_account(const char *uname);
extern const char *admin_disable_linux_account(const char *uname);
extern bool admin_linux_account_enabled(const char *uname);
extern bool admin_remote_verify(const char *uname, const char *password);
extern const char *admin_add_inet_user(const char *uname, const char *password);
extern const char *admin_remove_inet_user(const char *uname, const char *password);
extern bool admin_inet_user_exists(const char *uname);
extern bool admin_verify_inet_user(const char *uname, const char *password);
extern void dcb_PrintAdminUsers(DCB *dcb);

View File

@ -142,7 +142,7 @@ max_admin_auth_set_protocol_data(DCB *dcb, GWBUF *buf)
dcb->data = (void *)session_data;
/* Check for existance of the user */
if (admin_local_search_user(session_data->user))
if (admin_linux_account_enabled(session_data->user))
{
session_data->validated = true;
return 0;

View File

@ -13,12 +13,19 @@
* Public License.
*/
#define MAXADMIN_DEFAULT_SOCKET "/tmp/maxadmin.sock"
#define MAXADMIN_DEFAULT_SOCKET "/tmp/maxadmin.sock"
#define MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG_LEN 7
#define MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG "default"
#define MAXADMIN_GETPWUID_BUF_LEN 255
#define MAXADMIN_AUTH_REPLY_LEN 6
#define MAXADMIN_FAILED_AUTH_MESSAGE "FAILED"
#define MAXADMIN_CONFIG_DEFAULT_SOCKET_TAG "default"
#define MAXADMIN_AUTH_REPLY_LEN 6
#define MAXADMIN_AUTH_FAILED_REPLY "FAILED"
#define MAXADMIN_AUTH_SUCCESS_REPLY "OK----"
#define MAXADMIN_AUTH_USER_PROMPT "USER"
#define MAXADMIN_AUTH_USER_PROMPT_LEN 4
#define MAXADMIN_AUTH_PASSWORD_PROMPT "PASSWORD"
#define MAXADMIN_AUTH_PASSWORD_PROMPT_LEN 8
#endif

View File

@ -28,16 +28,17 @@
#include <spinlock.h>
#include <housekeeper.h>
/**
* The telnetd specific protocol structure to put in the DCB.
* The maxscaled specific protocol structure to put in the DCB.
*/
typedef struct maxscaled
typedef struct maxscaled
{
SPINLOCK lock; /**< Protocol structure lock */
int state; /**< The connection state */
char *username; /**< The login name of the user */
SPINLOCK lock; /**< Protocol structure lock */
int state; /**< The connection state */
char *username; /**< The login name of the user */
} MAXSCALED;
#define MAXSCALED_STATE_LOGIN 1 /**< Waiting for credentials */
#define MAXSCALED_STATE_DATA 2 /**< User logged in */
#define MAXSCALED_STATE_LOGIN 1 /**< Waiting for user */
#define MAXSCALED_STATE_PASSWD 2 /**< Waiting for password */
#define MAXSCALED_STATE_DATA 3 /**< User logged in */
#endif

View File

@ -65,6 +65,8 @@ MODULE_INFO info =
static char *version_str = "V2.0.0";
#define GETPWUID_BUF_LEN 255
static int maxscaled_read_event(DCB* dcb);
static int maxscaled_write_event(DCB *dcb);
static int maxscaled_write(DCB *dcb, GWBUF *queue);
@ -75,6 +77,98 @@ static int maxscaled_close(DCB *dcb);
static int maxscaled_listen(DCB *dcb, char *config);
static char *mxsd_default_auth();
static bool authenticate_unix_socket(MAXSCALED *protocol, DCB *dcb)
{
bool authenticated = false;
struct ucred ucred;
socklen_t len = sizeof(struct ucred);
/* Get UNIX client credentials from socket*/
if (getsockopt(dcb->fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == 0)
{
struct passwd pw_entry;
struct passwd *pw_tmp;
char buf[GETPWUID_BUF_LEN];
/* Fetch username from UID */
if (getpwuid_r(ucred.uid, &pw_entry, buf, sizeof(buf), &pw_tmp) == 0)
{
GWBUF *username;
/* Set user in protocol */
protocol->username = strdup(pw_entry.pw_name);
username = gwbuf_alloc(strlen(protocol->username) + 1);
strcpy(GWBUF_DATA(username), protocol->username);
/* Authenticate the user */
if (dcb->authfunc.extract(dcb, username) == 0 &&
dcb->authfunc.authenticate(dcb) == 0)
{
dcb_printf(dcb, MAXADMIN_AUTH_SUCCESS_REPLY);
protocol->state = MAXSCALED_STATE_DATA;
dcb->user = strdup(protocol->username);
}
else
{
dcb_printf(dcb, MAXADMIN_AUTH_FAILED_REPLY);
}
authenticated = true;
}
else
{
MXS_ERROR("Failed to get UNIX user %ld details for 'MaxScale Admin'",
(unsigned long)ucred.uid);
}
}
else
{
MXS_ERROR("Failed to get UNIX domain socket credentials for 'MaxScale Admin'.");
}
return authenticated;
}
static bool authenticate_inet_socket(MAXSCALED *protocol, DCB *dcb)
{
dcb_printf(dcb, MAXADMIN_AUTH_USER_PROMPT);
return true;
}
static bool authenticate_socket(MAXSCALED *protocol, DCB *dcb)
{
bool authenticated = false;
struct sockaddr address;
socklen_t address_len = sizeof(address);
if (getsockname(dcb->fd, &address, &address_len) == 0)
{
if (address.sa_family == AF_UNIX)
{
authenticated = authenticate_unix_socket(protocol, dcb);
}
else
{
authenticated = authenticate_inet_socket(protocol, dcb);
}
}
else
{
char errbuf[STRERROR_BUFLEN];
MXS_ERROR("Could not get socket family of client connection: %s",
strerror_r(errno, errbuf, sizeof(errbuf)));
}
return authenticated;
}
/**
* The "module object" for the maxscaled protocol module.
*/
@ -160,10 +254,41 @@ static int maxscaled_read_event(DCB* dcb)
{
if (GWBUF_LENGTH(head))
{
if (maxscaled->state == MAXSCALED_STATE_DATA)
switch (maxscaled->state)
{
SESSION_ROUTE_QUERY(dcb->session, head);
dcb_printf(dcb, "OK");
case MAXSCALED_STATE_LOGIN:
{
maxscaled->username = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head));
maxscaled->state = MAXSCALED_STATE_PASSWD;
dcb_printf(dcb, MAXADMIN_AUTH_PASSWORD_PROMPT);
gwbuf_free(head);
}
break;
case MAXSCALED_STATE_PASSWD:
{
char *password = strndup(GWBUF_DATA(head), GWBUF_LENGTH(head));
if (admin_verify_inet_user(maxscaled->username, password))
{
dcb_printf(dcb, MAXADMIN_AUTH_SUCCESS_REPLY);
maxscaled->state = MAXSCALED_STATE_DATA;
}
else
{
dcb_printf(dcb, MAXADMIN_AUTH_FAILED_REPLY);
maxscaled->state = MAXSCALED_STATE_LOGIN;
}
gwbuf_free(head);
free(password);
}
break;
case MAXSCALED_STATE_DATA:
{
SESSION_ROUTE_QUERY(dcb->session, head);
dcb_printf(dcb, "OK");
}
break;
}
}
else
@ -240,9 +365,9 @@ static int maxscaled_accept(DCB *listener)
while ((client_dcb = dcb_accept(listener, &MyObject)) != NULL)
{
MAXSCALED *maxscaled_protocol = NULL;
MAXSCALED *maxscaled_protocol = (MAXSCALED *)calloc(1, sizeof(MAXSCALED));
if ((maxscaled_protocol = (MAXSCALED *)calloc(1, sizeof(MAXSCALED))) == NULL)
if (!maxscaled_protocol)
{
dcb_close(client_dcb);
continue;
@ -251,52 +376,14 @@ static int maxscaled_accept(DCB *listener)
maxscaled_protocol->username = NULL;
maxscaled_protocol->state = MAXSCALED_STATE_LOGIN;
/* Get UNIX client credentials from socket*/
if (getsockopt(client_dcb->fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) == -1)
bool authenticated = false;
if (!authenticate_socket(maxscaled_protocol, client_dcb))
{
MXS_ERROR("Failed to get UNIX socket credentials for 'MaxScale Admin'");
dcb_close(client_dcb);
free(maxscaled_protocol);
continue;
}
else
{
struct passwd pw_entry;
struct passwd *pw_tmp;
char buf[MAXADMIN_GETPWUID_BUF_LEN];
/* Fetch username from UID */
if (!getpwuid_r(ucred.uid, &pw_entry, buf, sizeof(buf), &pw_tmp))
{
GWBUF *username;
/* Set user in protocol */
maxscaled_protocol->username = strdup(pw_entry.pw_name);
username = gwbuf_alloc(strlen(maxscaled_protocol->username) + 1);
strcpy(GWBUF_DATA(username), maxscaled_protocol->username);
/* Authenticate the user */
if (client_dcb->authfunc.extract(client_dcb, username) == 0 &&
client_dcb->authfunc.authenticate(client_dcb) == 0)
{
dcb_printf(client_dcb, "OK----");
maxscaled_protocol->state = MAXSCALED_STATE_DATA;
client_dcb->user = strdup(maxscaled_protocol->username);
}
else
{
dcb_printf(client_dcb, "FAILED");
}
}
else
{
MXS_ERROR("Failed to get UNIX user %ld details for 'MaxScale Admin'",
(unsigned long)ucred.uid);
dcb_close(client_dcb);
continue;
}
}
spinlock_init(&maxscaled_protocol->lock);
client_dcb->protocol = (void *)maxscaled_protocol;
@ -358,27 +445,7 @@ static int maxscaled_listen(DCB *listener, char *config)
}
else
{
const char* colon = strchr(config, ':');
ss_dassert(colon);
const char* port = colon + 1;
if (*port == '0')
{
// It seems "socket=socket-path" has been specified.
// The colon etc. will be stripped away in dcb_listen_create_socket_unix.
socket_path = config;
}
else
{
MXS_WARNING("The 'maxscaled' protocol can only be used with a Unix domain socket, but "
"it seems to have been configured with an address and port: %s. "
"Using the default socket path instead: %s. "
"Remove all 'address' and 'port' entries from maxscaled protocol "
"listeners and replace with 'socket=default' or 'socket=path-to-socket'.",
config, MAXADMIN_DEFAULT_SOCKET);
socket_path = MAXADMIN_DEFAULT_SOCKET;
}
socket_path = config;
}
return (dcb_listen(listener, socket_path, "MaxScale Admin") < 0) ? 0 : 1;

View File

@ -202,7 +202,7 @@ static int telnetd_read_event(DCB* dcb)
{
*t = 0;
}
if (admin_remote_verify(telnetd->username, password))
if (admin_verify_inet_user(telnetd->username, password))
{
telnetd_echo(dcb, 1);
telnetd->state = TELNETD_STATE_DATA;

View File

@ -205,8 +205,8 @@ struct subcommand showoptions[] = {
"Show the status of the polling threads in MaxScale",
{0, 0, 0} },
{ "users", 0, telnetdShowUsers,
"Show statistics and user names for the debug interface",
"Show statistics and user names for the debug interface",
"Show all maxadmin enabled Linux accounts and created maxadmin users",
"Show all maxadmin enabled Linux accounts and created maxadmin users",
{0, 0, 0} },
{ NULL, 0, NULL, NULL, NULL,
{0, 0, 0} }
@ -427,6 +427,8 @@ static void enable_syslog();
static void disable_syslog();
static void enable_maxlog();
static void disable_maxlog();
static void enable_account(DCB *, char *user);
static void disable_account(DCB *, char *user);
/**
* * The subcommands of the enable command
@ -514,6 +516,16 @@ struct subcommand enableoptions[] = {
"Enable maxlog logging",
{0, 0, 0}
},
{
"account",
1,
enable_account,
"Enable maxadmin usage for Linux user. E.g.:\n"
" MaxScale> enable account alice",
"Enable maxadmin usage for Linux user. E.g.:\n"
" MaxScale> enable account alice",
{ARG_TYPE_STRING, 0, 0}
},
{
NULL,
0,
@ -612,6 +624,16 @@ struct subcommand disableoptions[] = {
"Disable maxlog logging",
{0, 0, 0}
},
{
"account",
1,
disable_account,
"Disable maxadmin usage for Linux user. E.g.:\n"
" MaxScale> disable account alice",
"Disable maxadmin usage for Linux user. E.g.:\n"
" MaxScale> disable account alice",
{ARG_TYPE_STRING, 0, 0}
},
{
NULL,
0,
@ -666,32 +688,38 @@ struct subcommand failoptions[] = {
};
#endif /* FAKE_CODE */
static void telnetdAddUser(DCB *, char *);
static void telnetdAddUser(DCB *, char *user, char *password);
/**
* The subcommands of the add command
*/
struct subcommand addoptions[] = {
{ "user", 1, telnetdAddUser,
"Add a new user for the debug interface. E.g. add user john",
"Add a new user for the debug interface. E.g. add user john",
{ARG_TYPE_STRING, 0, 0} },
{ "user", 2, telnetdAddUser,
"Add insecure account for using maxadmin over the network. E.g.:\n"
" MaxScale> add user bob somepass",
"Add insecure account for using maxadmin over the network. E.g.:\n"
" MaxScale> add user bob somepass",
{ARG_TYPE_STRING, ARG_TYPE_STRING, 0} },
{ NULL, 0, NULL, NULL, NULL,
{0, 0, 0} }
};
static void telnetdRemoveUser(DCB *, char *);
static void telnetdRemoveUser(DCB *, char *user, char *password);
/**
* The subcommands of the remove command
*/
struct subcommand removeoptions[] = {
{
"user",
1,
2,
telnetdRemoveUser,
"Remove existing maxscale user. Example : remove user john",
"Remove existing maxscale user. Example : remove user john",
{ARG_TYPE_STRING, 0, 0}
"Remove account for using maxadmin over the network. E.g.:\n"
" MaxAdmin> remove user bob somepass",
"Remove account for using maxadmin over the network. E.g.:\n"
" MaxAdmin> remove user bob somepass",
{ARG_TYPE_STRING, ARG_TYPE_STRING, 0}
},
{
NULL, 0, NULL, NULL, NULL, {0, 0, 0}
@ -1274,63 +1302,61 @@ reload_config(DCB *dcb)
}
/**
* Add a new maxscale admin user
* Add a new remote (insecure, over the network) maxscale admin user
*
* @param dcb The DCB for messages
* @param user The user name
* @param dcb The DCB for messages
* @param user The user name
* @param user The user password
*/
static void
telnetdAddUser(DCB *dcb, char *user)
telnetdAddUser(DCB *dcb, char *user, char *password)
{
const char *err;
if (admin_local_search_user(user))
if (admin_inet_user_exists(user))
{
dcb_printf(dcb, "User %s already exists.\n", user);
dcb_printf(dcb, "Account %s for remote (network) usage already exists.\n", user);
return;
}
if ((err = admin_local_add_user(user)) == NULL)
if ((err = admin_add_inet_user(user, password)) == NULL)
{
dcb_printf(dcb, "User %s has been successfully added.\n", user);
dcb_printf(dcb, "Account %s for remote (network) usage has been successfully added.\n", user);
}
else
{
dcb_printf(dcb, "Failed to add new user. %s\n", err);
dcb_printf(dcb, "Failed to add new remote account %s: %s.\n", user, err);
}
}
/**
* Remove a maxscale admin user
* Remove a remote (insecure, over the network) maxscale admin user
*
* @param dcb The DCB for messages
* @param user The user name
* @param dcb The DCB for messages
* @param user The user name
* @param user The user password
*/
static void telnetdRemoveUser(
DCB* dcb,
char* user)
static void telnetdRemoveUser(DCB *dcb, char *user, char *password)
{
const char* err;
if (!admin_local_search_user(user))
if (!admin_inet_user_exists(user))
{
dcb_printf(dcb, "User %s doesn't exist.\n", user);
dcb_printf(dcb, "Account %s for remote (network) usage does not exist.\n", user);
return;
}
if ((err = admin_local_remove_user(user)) == NULL)
if ((err = admin_remove_inet_user(user, password)) == NULL)
{
dcb_printf(dcb, "User %s has been successfully removed.\n", user);
dcb_printf(dcb, "Account %s for remote (network) usage has been successfully removed.\n", user);
}
else
{
dcb_printf(dcb, "Failed to remove user %s. %s\n", user, err);
dcb_printf(dcb, "Failed to remove remote account %s: %s\n", user, err);
}
}
/**
* Print the adminsitration users
*
@ -1339,7 +1365,6 @@ static void telnetdRemoveUser(
static void
telnetdShowUsers(DCB *dcb)
{
dcb_printf(dcb, "Administration interface users:\n");
dcb_PrintAdminUsers(dcb);
}
@ -1829,6 +1854,60 @@ disable_maxlog()
mxs_log_set_maxlog_enabled(false);
}
/**
* Enable a Linux account
*
* @param dcb The DCB for messages
* @param user The Linux user name
*/
static void
enable_account(DCB *dcb, char *user)
{
const char *err;
if (admin_linux_account_enabled(user))
{
dcb_printf(dcb, "The Linux user %s has already been enabled.\n", user);
return;
}
if ((err = admin_enable_linux_account(user)) == NULL)
{
dcb_printf(dcb, "The Linux user %s has successfully been enabled.\n", user);
}
else
{
dcb_printf(dcb, "Failed to enable the Linux user %s: %s\n", user, err);
}
}
/**
* Disable a Linux account
*
* @param dcb The DCB for messages
* @param user The Linux user name
*/
static void
disable_account(DCB *dcb, char *user)
{
const char* err;
if (!admin_linux_account_enabled(user))
{
dcb_printf(dcb, "The Linux user %s has not been enabled.\n", user);
return;
}
if ((err = admin_disable_linux_account(user)) == NULL)
{
dcb_printf(dcb, "The Linux user %s has successfully been disabled.\n", user);
}
else
{
dcb_printf(dcb, "Failed to disable the Linux user %s: %s\n", user, err);
}
}
#if defined(FAKE_CODE)
static void fail_backendfd(void)
{