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:
@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user