mirror of https://github.com/neutrinolabs/xrdp
359 lines
10 KiB
C
359 lines
10 KiB
C
/**
|
|
* xrdp: A Remote Desktop Protocol server.
|
|
*
|
|
* Copyright (C) Jay Sorg 2004-2023
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
/**
|
|
*
|
|
* @file login_info.c
|
|
* @brief Define functionality associated with user logins for sesexec
|
|
* @author Matt Burt
|
|
*
|
|
*/
|
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
#include <config_ac.h>
|
|
#endif
|
|
|
|
#include "login_info.h"
|
|
|
|
#include "trans.h"
|
|
|
|
#include "sesman_auth.h"
|
|
#include "sesman_access.h"
|
|
#include "sesman_config.h"
|
|
#include "login_info.h"
|
|
#include "os_calls.h"
|
|
#include "scp.h"
|
|
#include "sesexec.h"
|
|
#include "string_calls.h"
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
* Logs an authentication failure message
|
|
*
|
|
* @param username Username
|
|
* @param ip_addr IP address, if known
|
|
*
|
|
* The message is intended for use by fail2ban. Make changes with care.
|
|
*/
|
|
static void
|
|
log_authfail_message(const char *username, const char *ip_addr)
|
|
{
|
|
if (ip_addr == NULL || ip_addr[0] == '\0')
|
|
{
|
|
ip_addr = "unknown";
|
|
}
|
|
LOG(LOG_LEVEL_INFO, "AUTHFAIL: user=%s ip=%s time=%d",
|
|
username, ip_addr, g_time1());
|
|
}
|
|
|
|
/******************************************************************************/
|
|
/**
|
|
* Authenticate and authorize the connection
|
|
*
|
|
* @param username Name for user
|
|
* @param password Password
|
|
* @param ip_addr Remote IP address
|
|
* @param login_info Structure to fill in for a successful login
|
|
* @return Status for the operation
|
|
*
|
|
* @post If E_SCP_LOGIN_OK is returned, g_login_info is filled in
|
|
*
|
|
*/
|
|
static enum scp_login_status
|
|
authenticate_and_authorize_connection(const char *supplied_username,
|
|
const char *password,
|
|
const char *ip_addr,
|
|
struct login_info *login_info)
|
|
{
|
|
int uid;
|
|
char *username; // From reverse-looking up the UID
|
|
enum scp_login_status status;
|
|
struct auth_info *auth_info;
|
|
|
|
if (g_getuser_info_by_name(supplied_username,
|
|
&uid, NULL, NULL, NULL, NULL) != 0)
|
|
{
|
|
/* we can't get a UID for the user */
|
|
LOG(LOG_LEVEL_ERROR, "Can't get UID for user %s",
|
|
supplied_username);
|
|
log_authfail_message(supplied_username, ip_addr);
|
|
status = E_SCP_LOGIN_NOT_AUTHENTICATED;
|
|
}
|
|
else if (g_getuser_info_by_uid(uid,
|
|
&username,
|
|
NULL, NULL, NULL, NULL) != 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Can't reverse lookup UID %d", uid);
|
|
status = E_SCP_LOGIN_NOT_AUTHENTICATED;
|
|
}
|
|
else
|
|
{
|
|
if (g_strcmp(username, supplied_username) != 0)
|
|
{
|
|
/*
|
|
* If using a federated naming service (e.g. AD), the username
|
|
* supplied may not match that name mapped to by the UID. We
|
|
* will generate a warning in this instance so the user can see
|
|
* what is being used
|
|
*/
|
|
LOG(LOG_LEVEL_WARNING,
|
|
"Using username %s for the session (from UID %d)",
|
|
username, uid);
|
|
}
|
|
|
|
auth_info = auth_userpass(username, password, ip_addr, &status);
|
|
|
|
/* Sanity check on result of call */
|
|
if ((auth_info != NULL && status != E_SCP_LOGIN_OK) ||
|
|
(auth_info == NULL && status == E_SCP_LOGIN_OK))
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Bugcheck; inconsistent auth result. "
|
|
"info = %p, status = %d", (void *)auth_info, (int)status);
|
|
status = E_SCP_LOGIN_GENERAL_ERROR;
|
|
auth_end(auth_info);
|
|
auth_info = NULL;
|
|
}
|
|
|
|
/* Group access allowed? */
|
|
if (status == E_SCP_LOGIN_OK &&
|
|
!access_login_allowed(&g_cfg->sec, username))
|
|
{
|
|
LOG(LOG_LEVEL_INFO, "Username okay but group problem for "
|
|
"user: %s", username);
|
|
status = E_SCP_LOGIN_NOT_AUTHORIZED;
|
|
auth_end(auth_info);
|
|
auth_info = NULL;
|
|
}
|
|
|
|
switch (status)
|
|
{
|
|
case E_SCP_LOGIN_OK:
|
|
{
|
|
char *dup_username = g_strdup(username);
|
|
char *dup_ip_addr = g_strdup(ip_addr);
|
|
|
|
if (dup_username == NULL || dup_ip_addr == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "%s : Memory allocation failed",
|
|
__func__);
|
|
g_free(dup_username);
|
|
g_free(dup_ip_addr);
|
|
status = E_SCP_LOGIN_NO_MEMORY;
|
|
auth_end(auth_info);
|
|
auth_info = NULL;
|
|
}
|
|
else
|
|
{
|
|
LOG(LOG_LEVEL_INFO, "Access permitted for user: %s",
|
|
username);
|
|
login_info->uid = uid;
|
|
login_info->username = dup_username;
|
|
login_info->ip_addr = dup_ip_addr;
|
|
login_info->auth_info = auth_info;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case E_SCP_LOGIN_NOT_AUTHENTICATED:
|
|
log_authfail_message(username, ip_addr);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
g_free(username);
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
static int
|
|
get_scp_client_retry(struct trans *scp_trans,
|
|
const char **username, const char **password,
|
|
const char **ip_addr)
|
|
{
|
|
int got_message = 0;
|
|
|
|
// Wait for an SCP message
|
|
enum scp_msg_code msgno;
|
|
|
|
scp_msg_in_reset(scp_trans);
|
|
|
|
if (scp_msg_in_wait_available(scp_trans) == 0)
|
|
{
|
|
msgno = scp_msg_in_get_msgno(scp_trans);
|
|
switch (msgno)
|
|
{
|
|
case E_SCP_SYS_LOGIN_REQUEST:
|
|
if (scp_get_sys_login_request(scp_trans, username,
|
|
password, ip_addr) == 0)
|
|
{
|
|
got_message = 1;
|
|
}
|
|
break;
|
|
|
|
case E_SCP_CLOSE_CONNECTION_REQUEST:
|
|
break;
|
|
|
|
default:
|
|
{
|
|
char buff[64];
|
|
scp_msgno_to_str(msgno, buff, sizeof(buff));
|
|
LOG(LOG_LEVEL_ERROR, "unexpected message %s from SCP client",
|
|
buff);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return got_message;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
struct login_info *
|
|
login_info_sys_login_user(struct trans *scp_trans,
|
|
const char *username,
|
|
const char *password,
|
|
const char *ip_addr)
|
|
{
|
|
struct login_info *result;
|
|
enum scp_login_status status = E_SCP_LOGIN_GENERAL_ERROR;
|
|
int server_closed = 0;
|
|
|
|
if ((result = g_new0(struct login_info, 1)) == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Allocation failure logging in user");
|
|
}
|
|
else
|
|
{
|
|
int first_time = 1;
|
|
unsigned int retry_count = g_cfg->sec.login_retry;
|
|
|
|
result->uid = (uid_t) -1;
|
|
|
|
while (status != E_SCP_LOGIN_OK && !server_closed)
|
|
{
|
|
// First time round, we have credentials supplied by the
|
|
// caller. On subsequent trips, we have to wait for the
|
|
// SCP client to send us more.
|
|
if (first_time)
|
|
{
|
|
first_time = 0;
|
|
}
|
|
else if (!get_scp_client_retry(scp_trans, &username,
|
|
&password, &ip_addr))
|
|
{
|
|
status = E_SCP_LOGIN_GENERAL_ERROR;
|
|
break;
|
|
}
|
|
|
|
status = authenticate_and_authorize_connection(username,
|
|
password,
|
|
ip_addr,
|
|
result);
|
|
|
|
if (status != E_SCP_LOGIN_OK)
|
|
{
|
|
if (retry_count > 0)
|
|
{
|
|
--retry_count;
|
|
}
|
|
else
|
|
{
|
|
server_closed = 1;
|
|
}
|
|
}
|
|
|
|
if (scp_send_login_response(scp_trans, status,
|
|
server_closed, result->uid) != 0)
|
|
{
|
|
status = E_SCP_LOGIN_GENERAL_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (status != E_SCP_LOGIN_OK)
|
|
{
|
|
login_info_free(result);
|
|
result = NULL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/******************************************************************************/
|
|
struct login_info *
|
|
login_info_uds_login_user(struct trans *scp_trans)
|
|
{
|
|
struct login_info *result;
|
|
int uid; // Needed as g_sck_get_peer_cred() doesn't use uid_t
|
|
|
|
// Allocate a struct for the result, with the IP address set to ""
|
|
if ((result = g_new0(struct login_info, 1)) == NULL ||
|
|
(result->ip_addr = g_new0(char, 1)) == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Allocation failure logging in user");
|
|
}
|
|
else if (g_sck_get_peer_cred(scp_trans->sck, NULL, &uid, NULL) != 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Unable to get peer credentials for SCP socket");
|
|
}
|
|
else if (g_getuser_info_by_uid(uid, &result->username,
|
|
NULL, NULL, NULL, NULL) != 0)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Can't reverse lookup UID %d", result->uid);
|
|
}
|
|
else if ((result->auth_info = auth_uds(result->username, NULL)) == NULL)
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Can't authorize user %s over UDS",
|
|
result->username);
|
|
}
|
|
else if (!access_login_allowed(&g_cfg->sec, result->username))
|
|
{
|
|
LOG(LOG_LEVEL_ERROR, "Access denied for user %s by your system admin",
|
|
result->username);
|
|
}
|
|
else
|
|
{
|
|
result->uid = (uid_t)uid;
|
|
return result;
|
|
}
|
|
|
|
login_info_free(result);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/******************************************************************************/
|
|
void
|
|
login_info_free(struct login_info *self)
|
|
{
|
|
if (self != NULL)
|
|
{
|
|
g_free(self->username);
|
|
g_free(self->ip_addr);
|
|
if (self->auth_info != NULL)
|
|
{
|
|
auth_end(self->auth_info);
|
|
}
|
|
g_free(self);
|
|
}
|
|
}
|