
Admin users can now be created via the REST API. This allows remote management of the administrative interface itself.
614 lines
15 KiB
C++
614 lines
15 KiB
C++
/*
|
|
* Copyright (c) 2016 MariaDB Corporation Ab
|
|
*
|
|
* Use of this software is governed by the Business Source License included
|
|
* in the LICENSE.TXT file and at www.mariadb.com/bsl11.
|
|
*
|
|
* Change Date: 2019-07-01
|
|
*
|
|
* On the date above, in accordance with the Business Source License, use
|
|
* of this software will be governed by version 2 or later of the General
|
|
* Public License.
|
|
*/
|
|
#include <maxscale/cdefs.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <crypt.h>
|
|
#include <sys/stat.h>
|
|
#include <string>
|
|
|
|
#include <maxscale/users.h>
|
|
#include <maxscale/adminusers.h>
|
|
#include <maxscale/log_manager.h>
|
|
#include <maxscale/paths.h>
|
|
#include <maxscale/json_api.h>
|
|
|
|
/**
|
|
* @file adminusers.c - Administration user account management
|
|
*/
|
|
|
|
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 const int LINELEN = 80;
|
|
|
|
static const char LINUX_USERS_FILE_NAME[] = "maxadmin-users";
|
|
static const char INET_USERS_FILE_NAME[] = "passwd";
|
|
|
|
/**
|
|
* Admin Users initialisation
|
|
*/
|
|
void admin_users_init()
|
|
{
|
|
linux_users = loadLinuxUsers();
|
|
inet_users = loadInetUsers();
|
|
}
|
|
|
|
static const char *admin_add_user(USERS** pusers, const char* fname,
|
|
const char* uname, const char* password)
|
|
{
|
|
FILE *fp;
|
|
char path[PATH_MAX], *home;
|
|
|
|
if (access(get_datadir(), F_OK) != 0)
|
|
{
|
|
if (mkdir(get_datadir(), S_IRWXU) != 0 && errno != EEXIST)
|
|
{
|
|
return ADMIN_ERR_PWDFILEOPEN;
|
|
}
|
|
}
|
|
|
|
snprintf(path, sizeof(path), "%s/%s", get_datadir(), fname);
|
|
if (*pusers == NULL)
|
|
{
|
|
MXS_NOTICE("Create initial password file.");
|
|
|
|
if ((*pusers = users_alloc()) == NULL)
|
|
{
|
|
return ADMIN_ERR_NOMEM;
|
|
}
|
|
if ((fp = fopen(path, "w")) == NULL)
|
|
{
|
|
MXS_ERROR("Unable to create password file %s.", path);
|
|
return ADMIN_ERR_PWDFILEOPEN;
|
|
}
|
|
fclose(fp);
|
|
}
|
|
if (users_fetch(*pusers, (char*)uname) != NULL) // TODO: Make users const correct.
|
|
{
|
|
return ADMIN_ERR_DUPLICATE;
|
|
}
|
|
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.", path);
|
|
return ADMIN_ERR_FILEAPPEND;
|
|
}
|
|
if (password)
|
|
{
|
|
fprintf(fp, "%s:%s\n", uname, password);
|
|
}
|
|
else
|
|
{
|
|
fprintf(fp, "%s\n", uname);
|
|
}
|
|
fclose(fp);
|
|
return ADMIN_SUCCESS;
|
|
}
|
|
|
|
static const char* admin_remove_user(USERS *users, const char* fname,
|
|
const char *uname, const char *passwd)
|
|
{
|
|
FILE* fp;
|
|
FILE* fp_tmp;
|
|
char path[PATH_MAX];
|
|
char path_tmp[PATH_MAX];
|
|
char* home;
|
|
char fusr[LINELEN];
|
|
char fpwd[LINELEN];
|
|
char line[LINELEN];
|
|
fpos_t rpos;
|
|
|
|
if (strcmp(uname, DEFAULT_ADMIN_USER) == 0)
|
|
{
|
|
MXS_WARNING("Attempt to delete the default admin user '%s'.", uname);
|
|
return ADMIN_ERR_DELROOT;
|
|
}
|
|
|
|
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(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(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.",
|
|
path,
|
|
err);
|
|
return ADMIN_ERR_PWDFILEOPEN;
|
|
}
|
|
/**
|
|
* Open temporary passwd file.
|
|
*/
|
|
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.",
|
|
path_tmp,
|
|
err);
|
|
fclose(fp);
|
|
return ADMIN_ERR_TMPFILEOPEN;
|
|
}
|
|
|
|
/**
|
|
* Scan passwd and copy all but matching lines to temp file.
|
|
*/
|
|
if (fgetpos(fp, &rpos) != 0)
|
|
{
|
|
int err = errno;
|
|
MXS_ERROR("Unable to process passwd file %s : errno %d.\n"
|
|
"Removing user from file failed, and must be done "
|
|
"manually.",
|
|
path,
|
|
err);
|
|
fclose(fp);
|
|
fclose(fp_tmp);
|
|
unlink(path_tmp);
|
|
return ADMIN_ERR_PWDFILEACCESS;
|
|
}
|
|
|
|
while (fgets(fusr, sizeof(fusr), fp))
|
|
{
|
|
char *nl = strchr(fusr, '\n');
|
|
|
|
if (nl)
|
|
{
|
|
*nl = '\0';
|
|
}
|
|
else if (!feof(fp))
|
|
{
|
|
MXS_ERROR("Line length exceeds %d characters, possible corrupted "
|
|
"'passwd' file in: %s", LINELEN, path);
|
|
fclose(fp);
|
|
fclose(fp_tmp);
|
|
return ADMIN_ERR_PWDFILEACCESS;
|
|
}
|
|
|
|
/**
|
|
* Compare username what was found from passwd file.
|
|
* Unmatching lines are copied to tmp file.
|
|
*/
|
|
if (strncmp(uname, fusr, strlen(uname) + 1) != 0)
|
|
{
|
|
if (fsetpos(fp, &rpos) != 0)
|
|
{
|
|
/** one step back */
|
|
MXS_ERROR("Unable to set stream position. ");
|
|
}
|
|
if (fgets(line, LINELEN, fp))
|
|
{
|
|
fputs(line, fp_tmp);
|
|
}
|
|
else
|
|
{
|
|
MXS_ERROR("Failed to read line from admin users file");
|
|
}
|
|
}
|
|
|
|
if (fgetpos(fp, &rpos) != 0)
|
|
{
|
|
int err = errno;
|
|
MXS_ERROR("Unable to process passwd file %s : "
|
|
"errno %d.\n"
|
|
"Removing user from file failed, and must be "
|
|
"done manually.",
|
|
path,
|
|
err);
|
|
fclose(fp);
|
|
fclose(fp_tmp);
|
|
unlink(path_tmp);
|
|
return ADMIN_ERR_PWDFILEACCESS;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
/**
|
|
* Replace original passwd file with new.
|
|
*/
|
|
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.",
|
|
path_tmp,
|
|
err,
|
|
path);
|
|
unlink(path_tmp);
|
|
fclose(fp_tmp);
|
|
return ADMIN_ERR_PWDFILEACCESS;
|
|
}
|
|
fclose(fp_tmp);
|
|
return ADMIN_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Check for existance of the user
|
|
*
|
|
* @param uname The user name to test
|
|
* @return True if the user exists
|
|
*/
|
|
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)
|
|
{
|
|
const char *sep = "";
|
|
const char *user;
|
|
|
|
while ((user = (const char*)hashtable_next(iter)) != NULL)
|
|
{
|
|
dcb_printf(dcb, "%s%s", sep, user);
|
|
sep = ", ";
|
|
}
|
|
|
|
hashtable_iterator_free(iter);
|
|
}
|
|
}
|
|
|
|
dcb_printf(dcb, "%s", "\n");
|
|
}
|
|
|
|
static json_t* admin_user_json_data(const char* host, const char* user, enum user_type user_type)
|
|
{
|
|
ss_dassert(user_type != USER_TYPE_ALL);
|
|
const char* type = user_type == USER_TYPE_INET ? CN_INET : CN_UNIX;
|
|
|
|
json_t* entry = json_object();
|
|
json_object_set_new(entry, CN_ID, json_string(user));
|
|
json_object_set_new(entry, CN_TYPE, json_string(type));
|
|
|
|
std::string self = MXS_JSON_API_USERS;
|
|
self += type;
|
|
json_object_set_new(entry, CN_RELATIONSHIPS, mxs_json_self_link(host, self.c_str(), user));
|
|
|
|
return entry;
|
|
}
|
|
|
|
static void user_types_to_json(USERS* users, json_t* arr, const char* host, enum user_type type)
|
|
{
|
|
const char* user;
|
|
HASHITERATOR *iter = hashtable_iterator(users->data);
|
|
|
|
while ((user = (const char*)hashtable_next(iter)))
|
|
{
|
|
json_array_append_new(arr, admin_user_json_data(host, user, type));
|
|
}
|
|
|
|
hashtable_iterator_free(iter);
|
|
}
|
|
|
|
static std::string path_from_type(enum user_type type)
|
|
{
|
|
std::string path = MXS_JSON_API_USERS;
|
|
|
|
if (type == USER_TYPE_INET)
|
|
{
|
|
path += CN_INET;
|
|
}
|
|
else if (type == USER_TYPE_UNIX)
|
|
{
|
|
path += CN_UNIX;
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
json_t* admin_user_to_json(const char* host, const char* user, enum user_type type)
|
|
{
|
|
std::string path = path_from_type(type);
|
|
path += "/";
|
|
path += user;
|
|
|
|
return mxs_json_resource(host, path.c_str(), admin_user_json_data(host, user, type));
|
|
}
|
|
|
|
json_t* admin_all_users_to_json(const char* host, enum user_type type)
|
|
{
|
|
json_t* arr = json_array();
|
|
std::string path = path_from_type(type);
|
|
|
|
if (inet_users && (type == USER_TYPE_ALL || type == USER_TYPE_INET))
|
|
{
|
|
user_types_to_json(inet_users, arr, host, USER_TYPE_INET);
|
|
}
|
|
|
|
if (linux_users && (type == USER_TYPE_ALL || type == USER_TYPE_UNIX))
|
|
{
|
|
user_types_to_json(linux_users, arr, host, USER_TYPE_UNIX);
|
|
}
|
|
|
|
return mxs_json_resource(host, path.c_str(), arr);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
|
|
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;
|
|
}
|
|
|
|
const 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)
|
|
{
|
|
return admin_add_user(&linux_users, LINUX_USERS_FILE_NAME, uname, NULL);
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
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)
|
|
{
|
|
bool rv = false;
|
|
|
|
if (strcmp(uname, DEFAULT_ADMIN_USER) == 0)
|
|
{
|
|
rv = true;
|
|
}
|
|
else if (linux_users)
|
|
{
|
|
rv = admin_search_user(linux_users, uname);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
/**
|
|
* Add insecure remote (network) user.
|
|
*
|
|
* @param uname Name of the new user.
|
|
* @param password Password of the new user.
|
|
*
|
|
* @return NULL on success or an error string on failure.
|
|
*/
|
|
const char *admin_add_inet_user(const char *uname, const char* password)
|
|
{
|
|
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)
|
|
{
|
|
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)
|
|
{
|
|
bool rv = false;
|
|
|
|
if (inet_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;
|
|
|
|
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
|
|
{
|
|
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);
|
|
}
|