Update PAM auth module for UDS logins

An extra method auth_uds() is added to the PAM module to
allow a 'struct auth_info' to be created for a UDS login. The PAM stack
is used to check the UDS user can be authorized.

Also, an error code is returned from the auth module rather than a
simple boolean. This allows a more complete status to be communicated
to the user. See https://github.com/neutrinolabs/xrdp/discussions/1921
and also #909 and #642
This commit is contained in:
matt335672 2022-09-13 10:55:47 +01:00
parent 4c4bdc9782
commit 2a3cec4125
2 changed files with 169 additions and 82 deletions

View File

@ -27,11 +27,11 @@
#ifndef AUTH_H #ifndef AUTH_H
#define AUTH_H #define AUTH_H
/** /**
* Opaque type used to represent an authentication handle * Opaque type used to represent an authentication handle
*/ */
struct auth_info; struct auth_info;
#include "scp_application_types.h"
/** /**
* *
@ -39,13 +39,25 @@ struct auth_info;
* @param user user's login name * @param user user's login name
* @param pass user's password * @param pass user's password
* @param client_ip IP address of connecting client (or ""/NULL if not known) * @param client_ip IP address of connecting client (or ""/NULL if not known)
* @param[out] errorcode from result * @param[out] Error code for the operation. E_SCP_LOGIN_OK on success.
* @return auth handle on success, NULL on failure * @return auth handle on success, NULL on failure
* *
*/ */
struct auth_info * struct auth_info *
auth_userpass(const char *user, const char *pass, auth_userpass(const char *user, const char *pass,
const char *client_ip, int *errorcode); const char *client_ip, enum scp_login_status *errorcode);
/**
*
* @brief Gets an auth handle for a UDS login
*
* @param uid User ID
* @param[out] Error code for the operation. E_SCP_LOGIN_OK on success.
* @return auth handle on success, NULL on failure
*
*/
struct auth_info *
auth_uds(const char *user, enum scp_login_status *errorcode);
/** /**
* *

View File

@ -37,10 +37,9 @@
#include <stdio.h> #include <stdio.h>
#include <security/pam_appl.h> #include <security/pam_appl.h>
/* Allows the conversation function to find the username and password */ /* Allows the conversation function to find required items */
struct t_user_pass struct conv_func_data
{ {
const char *user;
const char *pass; const char *pass;
}; };
@ -100,7 +99,7 @@ msg_style_to_str(int msg_style, char *buff, unsigned int bufflen)
* @param[in] num_msg Count of messages in the msg array * @param[in] num_msg Count of messages in the msg array
* @param[in] msg Messages from the PAM stack to the application * @param[in] msg Messages from the PAM stack to the application
* @param[out] resp Message replies from the application to the PAM stack * @param[out] resp Message replies from the application to the PAM stack
* @param[in] appdata_ptr Used to pass in a struct t_user_pass pointer * @param[in] appdata_ptr Used to pass in a struct conv_func_data pointer
* *
* @result PAM_SUCCESS if the messages were all processed successfully. * @result PAM_SUCCESS if the messages were all processed successfully.
* *
@ -120,7 +119,7 @@ verify_pam_conv(int num_msg, const struct pam_message **msg,
{ {
int i; int i;
struct pam_response *reply = NULL; struct pam_response *reply = NULL;
struct t_user_pass *user_pass; struct conv_func_data *conv_func_data;
char sb[64]; char sb[64];
int rv = PAM_SUCCESS; int rv = PAM_SUCCESS;
@ -144,10 +143,10 @@ verify_pam_conv(int num_msg, const struct pam_message **msg,
switch (msg[i]->msg_style) switch (msg[i]->msg_style)
{ {
case PAM_PROMPT_ECHO_OFF: /* password */ case PAM_PROMPT_ECHO_OFF: /* password */
user_pass = (struct t_user_pass *) appdata_ptr; conv_func_data = (struct conv_func_data *) appdata_ptr;
/* Check this function isn't being called /* Check this function isn't being called
* later than we expected */ * later than we expected */
if (user_pass == NULL) if (conv_func_data == NULL || conv_func_data->pass == NULL)
{ {
LOG(LOG_LEVEL_ERROR, LOG(LOG_LEVEL_ERROR,
"verify_pam_conv: Password unavailable"); "verify_pam_conv: Password unavailable");
@ -155,7 +154,7 @@ verify_pam_conv(int num_msg, const struct pam_message **msg,
} }
else else
{ {
reply[i].resp = g_strdup(user_pass->pass); reply[i].resp = g_strdup(conv_func_data->pass);
} }
break; break;
@ -216,73 +215,79 @@ get_service_name(char *service_name)
} }
/******************************************************************************/ /******************************************************************************/
/* returns non-NULL for success
* Detailed error code is in the errorcode variable */
struct auth_info * /** Performs PAM operations common to login methods
auth_userpass(const char *user, const char *pass, *
const char *client_ip, int *errorcode) * @param auth_info Module auth_info structure
* @param user User name
* @param pass Password, if needed for authentication.
* @param client_ip Client IP if known, or NULL
* @param authentication_required True if user must be authenticated
*
* For a UDS connection, the user can be assumed to be authenticated,
* so in this instance authentication_required can be false.
*
* @return Code describing the success of the operation
*/
static enum scp_login_status
common_pam_login(struct auth_info *auth_info,
const char *user,
const char *pass,
const char *client_ip,
int authentication_required)
{ {
int error; int perror;
struct auth_info *auth_info;
char service_name[256]; char service_name[256];
struct t_user_pass user_pass = {user, pass}; struct conv_func_data conv_func_data;
struct pam_conv pamc = {verify_pam_conv, (void *) &user_pass}; struct pam_conv pamc;
auth_info = g_new0(struct auth_info, 1); /*
if (auth_info == NULL) * Set up the data required by the conversation function, and the
{ * structure which allows us to pass this to pam_start()
LOG(LOG_LEVEL_ERROR, "auth_userpass: No memory"); */
error = PAM_BUF_ERR; conv_func_data.pass = (authentication_required) ? pass : NULL;
return NULL; pamc.conv = verify_pam_conv;
} pamc.appdata_ptr = (void *) &conv_func_data;
get_service_name(service_name); get_service_name(service_name);
error = pam_start(service_name, user, &pamc, &(auth_info->ph)); perror = pam_start(service_name, user, &pamc, &(auth_info->ph));
if (error != PAM_SUCCESS) if (perror != PAM_SUCCESS)
{ {
if (errorcode != NULL)
{
*errorcode = error;
}
LOG(LOG_LEVEL_ERROR, "pam_start failed: %s", LOG(LOG_LEVEL_ERROR, "pam_start failed: %s",
pam_strerror(auth_info->ph, error)); pam_strerror(auth_info->ph, perror));
pam_end(auth_info->ph, error); pam_end(auth_info->ph, perror);
g_free(auth_info); return E_SCP_LOGIN_GENERAL_ERROR;
return NULL;
} }
if (client_ip != NULL && client_ip[0] != '\0') if (client_ip != NULL && client_ip[0] != '\0')
{ {
error = pam_set_item(auth_info->ph, PAM_RHOST, client_ip); perror = pam_set_item(auth_info->ph, PAM_RHOST, client_ip);
if (error != PAM_SUCCESS) if (perror != PAM_SUCCESS)
{ {
LOG(LOG_LEVEL_ERROR, "pam_set_item(PAM_RHOST) failed: %s", LOG(LOG_LEVEL_ERROR, "pam_set_item(PAM_RHOST) failed: %s",
pam_strerror(auth_info->ph, error)); pam_strerror(auth_info->ph, perror));
} }
} }
error = pam_set_item(auth_info->ph, PAM_TTY, service_name); perror = pam_set_item(auth_info->ph, PAM_TTY, service_name);
if (error != PAM_SUCCESS) if (perror != PAM_SUCCESS)
{ {
LOG(LOG_LEVEL_ERROR, "pam_set_item(PAM_TTY) failed: %s", LOG(LOG_LEVEL_ERROR, "pam_set_item(PAM_TTY) failed: %s",
pam_strerror(auth_info->ph, error)); pam_strerror(auth_info->ph, perror));
} }
error = pam_authenticate(auth_info->ph, 0); if (authentication_required)
{
perror = pam_authenticate(auth_info->ph, 0);
if (error != PAM_SUCCESS) if (perror != PAM_SUCCESS)
{ {
if (errorcode != NULL)
{
*errorcode = error;
}
LOG(LOG_LEVEL_ERROR, "pam_authenticate failed: %s", LOG(LOG_LEVEL_ERROR, "pam_authenticate failed: %s",
pam_strerror(auth_info->ph, error)); pam_strerror(auth_info->ph, perror));
pam_end(auth_info->ph, error); pam_end(auth_info->ph, perror);
g_free(auth_info); return E_SCP_LOGIN_NOT_AUTHENTICATED;
return NULL; }
} }
/* From man page: /* From man page:
The pam_acct_mgmt function is used to determine if the users account is The pam_acct_mgmt function is used to determine if the users account is
@ -290,35 +295,99 @@ auth_userpass(const char *user, const char *pass,
verifies access restrictions. It is typically called after the user has verifies access restrictions. It is typically called after the user has
been authenticated. been authenticated.
*/ */
error = pam_acct_mgmt(auth_info->ph, 0); perror = pam_acct_mgmt(auth_info->ph, 0);
if (error != PAM_SUCCESS) if (perror != PAM_SUCCESS)
{ {
if (errorcode != NULL)
{
*errorcode = error;
}
LOG(LOG_LEVEL_ERROR, "pam_acct_mgmt failed: %s", LOG(LOG_LEVEL_ERROR, "pam_acct_mgmt failed: %s",
pam_strerror(auth_info->ph, error)); pam_strerror(auth_info->ph, perror));
pam_end(auth_info->ph, error); pam_end(auth_info->ph, perror);
g_free(auth_info); return E_SCP_LOGIN_NOT_AUTHORIZED;
return NULL;
} }
/* Set the appdata_ptr passed to the conversation function to /* Set the appdata_ptr passed to the conversation function to
* NULL, as the existing value is going out of scope */ * NULL, as the existing value is going out of scope */
pamc.appdata_ptr = NULL; pamc.appdata_ptr = NULL;
error = pam_set_item(auth_info->ph, PAM_CONV, &pamc); perror = pam_set_item(auth_info->ph, PAM_CONV, &pamc);
if (error != PAM_SUCCESS) if (perror != PAM_SUCCESS)
{ {
LOG(LOG_LEVEL_ERROR, "pam_set_item(PAM_CONV) failed: %s", LOG(LOG_LEVEL_ERROR, "pam_set_item(PAM_CONV) failed: %s",
pam_strerror(auth_info->ph, error)); pam_strerror(auth_info->ph, perror));
}
return E_SCP_LOGIN_OK;
}
/******************************************************************************/
/* returns non-NULL for success
* Detailed error code is in the errorcode variable */
struct auth_info *
auth_userpass(const char *user, const char *pass,
const char *client_ip, enum scp_login_status *errorcode)
{
struct auth_info *auth_info;
enum scp_login_status status;
auth_info = g_new0(struct auth_info, 1);
if (auth_info == NULL)
{
status = E_SCP_LOGIN_NO_MEMORY;
}
else
{
status = common_pam_login(auth_info, user, pass, client_ip, 1);
if (status != E_SCP_LOGIN_OK)
{
g_free(auth_info);
auth_info = NULL;
}
}
if (errorcode != NULL)
{
*errorcode = status;
} }
return auth_info; return auth_info;
} }
/******************************************************************************/ /******************************************************************************/
struct auth_info *
auth_uds(const char *user, enum scp_login_status *errorcode)
{
struct auth_info *auth_info;
enum scp_login_status status;
auth_info = g_new0(struct auth_info, 1);
if (auth_info == NULL)
{
status = E_SCP_LOGIN_NO_MEMORY;
}
else
{
status = common_pam_login(auth_info, user, NULL, NULL, 0);
if (status != E_SCP_LOGIN_OK)
{
g_free(auth_info);
auth_info = NULL;
}
}
if (errorcode != NULL)
{
*errorcode = status;
}
return auth_info;
}
/******************************************************************************/
/* returns error */ /* returns error */
int int
auth_start_session(struct auth_info *auth_info, int display_num) auth_start_session(struct auth_info *auth_info, int display_num)
@ -364,17 +433,31 @@ auth_start_session(struct auth_info *auth_info, int display_num)
int int
auth_stop_session(struct auth_info *auth_info) auth_stop_session(struct auth_info *auth_info)
{ {
int rv = 0;
int error; int error;
if (auth_info->session_opened)
{
error = pam_close_session(auth_info->ph, 0); error = pam_close_session(auth_info->ph, 0);
if (error != PAM_SUCCESS) if (error != PAM_SUCCESS)
{ {
LOG(LOG_LEVEL_ERROR, "pam_close_session failed: %s", LOG(LOG_LEVEL_ERROR, "pam_close_session failed: %s",
pam_strerror(auth_info->ph, error)); pam_strerror(auth_info->ph, error));
return 1; rv = 1;
} }
else
{
auth_info->session_opened = 0; auth_info->session_opened = 0;
return 0; }
}
if (auth_info->did_setcred)
{
pam_setcred(auth_info->ph, PAM_DELETE_CRED);
auth_info->did_setcred = 0;
}
return rv;
} }
/******************************************************************************/ /******************************************************************************/
@ -387,15 +470,7 @@ auth_end(struct auth_info *auth_info)
{ {
if (auth_info->ph != 0) if (auth_info->ph != 0)
{ {
if (auth_info->session_opened) auth_stop_session(auth_info);
{
pam_close_session(auth_info->ph, 0);
}
if (auth_info->did_setcred)
{
pam_setcred(auth_info->ph, PAM_DELETE_CRED);
}
pam_end(auth_info->ph, PAM_SUCCESS); pam_end(auth_info->ph, PAM_SUCCESS);
auth_info->ph = 0; auth_info->ph = 0;