diff --git a/conf/daemon.conf b/conf/daemon.conf index a177dd0..ff850b6 100644 --- a/conf/daemon.conf +++ b/conf/daemon.conf @@ -5,6 +5,10 @@ # or it can be specified using the -c option # example: diya-session-manager -c /path/to/daemon.conf +# PAM service +# the pam service used for authentication +pam_service = diya + # Login session command # The command to run to start a login session # this command will handle the user input and send user diff --git a/meson.build b/meson.build index a5d0f08..8e0e74b 100644 --- a/meson.build +++ b/meson.build @@ -11,6 +11,7 @@ glib = dependency('glib-2.0') gobject = dependency('gobject-2.0') gio = dependency('gio-2.0') crypt = dependency('libcrypt') +pam = dependency('pam') add_project_arguments( [ '-Wno-pedantic', @@ -29,10 +30,11 @@ src = [ 'src/configuration.c', 'src/manager.c', 'src/base.c', + 'src/authenticator.c', resources ] executable( 'diya-session-manager', src, - dependencies: [glib, gobject, gio, crypt]) \ No newline at end of file + dependencies: [glib, gobject, gio, crypt, pam]) \ No newline at end of file diff --git a/src/authenticator.c b/src/authenticator.c new file mode 100644 index 0000000..ec6b4ea --- /dev/null +++ b/src/authenticator.c @@ -0,0 +1,293 @@ +#include +#include +#include + +#include "authenticator.h" + +struct _SessionAuthenticator +{ + BaseObject parent_object; + gchar* name; + pam_handle_t *pam; +}; + +G_DEFINE_FINAL_TYPE(SessionAuthenticator, session_authenticator, BASE_TYPE_OBJECT); + +static void session_authenticator_finalize(GObject *object) +{ + g_debug("session_authenticator_finalize: %s", base_object_to_string(object)); + G_OBJECT_CLASS(session_authenticator_parent_class)->finalize(object); + SessionAuthenticator *self = SESSION_AUTHENTICATOR(object); + if(self->name) + { + g_free(self->name); + } + self->name = NULL; + if(self->pam) + { + session_authenticator_close_session(self); + } +} + +static void session_authenticator_dispose(GObject *object) +{ + g_debug("session_authenticator_dispose: %s", base_object_to_string(object)); + G_OBJECT_CLASS(session_authenticator_parent_class)->dispose(object); +} + +static const gchar* session_authenticator_to_string(BaseObject *object) +{ + (void)object; + return "SessionAuthenticator"; +} + +static void session_authenticator_class_init(SessionAuthenticatorClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + gobject_class->finalize = session_authenticator_finalize; + gobject_class->dispose = session_authenticator_dispose; + + BaseObjectClass *base_class = BASE_OBJECT_CLASS(class); + base_class->to_string = session_authenticator_to_string; +} + +static void session_authenticator_init(SessionAuthenticator *self) +{ + self->name = NULL; + self->pam = NULL; +} + +static int pam_conversation(int num, const struct pam_message **messages, struct pam_response **responses, void *data) +{ + g_debug("Initiating conversation between PAM and application"); + + /* Check number of responses */ + if ((num <= 0) || (num > PAM_MAX_NUM_MSG)) + { + g_warning("Invalid number of responses '%d'.", num); + return PAM_CONV_ERR; + } + + /* Allocate memory for response */ + struct pam_response *resp; + + if (!(resp = calloc(num, sizeof(*resp)))) + { + return PAM_BUF_ERR; + } + + /* Iterate over PAM messages */ + char **credentials = (char **)data; + char *username; + char *password; + int i; + + for (i = 0; i < num; i++) + { + switch (messages[i]->msg_style) + { + case PAM_PROMPT_ECHO_ON: + username = credentials[0]; + resp[i].resp = strdup(username); + + if (!(resp[i].resp)) + { + goto fail; + } + + break; + + case PAM_PROMPT_ECHO_OFF: + password = credentials[1]; + resp[i].resp = strdup(password); + + if (!(resp[i].resp)) + { + goto fail; + } + + break; + + case PAM_TEXT_INFO: + printf("PAM_TEXT_INFO: %s", messages[i]->msg); + break; + + case PAM_ERROR_MSG: + printf("PAM_ERROR_MSG: %s", messages[i]->msg); + break; + + default: + goto fail; + } + } + + *responses = resp; + + g_debug("PAM conversation completed."); + + return PAM_SUCCESS; + +/* Goto failure */ +fail: + g_debug("PAM conversation failed."); + + for (i = 0; i < num; i++) + { + if (resp[i].resp) + { + memset(resp[i].resp, 0, strlen(resp[i].resp)); + free(resp[i].resp); + } + } + + memset(resp, 0, num * sizeof(*resp)); + *responses = 0; + + return PAM_CONV_ERR; +} + +SessionAuthenticator* session_authenticator_new(const char* name) +{ + SessionAuthenticator *self = g_object_new(SESSION_TYPE_AUTHENTICATOR, NULL); + self->name = g_strdup(name); + return self; +} + +gboolean session_authenticator_authenticate(SessionAuthenticator* self, const char* username, const char* password) +{ + if(self->pam) + { + g_critical("PAM session is already opened."); + return FALSE; + } + + const char *data[2] = {username, password}; + struct pam_conv conversation = {pam_conversation, data}; + + /* Start pam */ + g_debug("Starting PAM transaction for user %s", username); + + int result = pam_start(self->name, username, &conversation, &self->pam); + + if (result != PAM_SUCCESS) + { + g_critical("Failed to start PAM transaction: %s", pam_strerror(self->pam, result)); + session_authenticator_close_session(self); + return FALSE; + } + + /* Set DISPLAY */ + result = pam_set_item(self->pam, PAM_XDISPLAY, getenv("DISPLAY")); + + if (result != PAM_SUCCESS) + { + g_warning("Failed to set PAM_XDISPLAY item: %s", pam_strerror(self->pam, result)); + } + + /* Set TTY */ + result = pam_set_item(self->pam, PAM_TTY, getenv("TTY")); + + if (result != PAM_SUCCESS) + { + g_warning("Failed to set PAM_TTY item: %s", pam_strerror(self->pam, result)); + } + + /* Set USER */ + result = pam_set_item(self->pam, PAM_USER, username); + + if (result != PAM_SUCCESS) + { + g_warning("Failed to set PAM_USER item: %s", pam_strerror(self->pam, result)); + } + + /* Authenticate pam user */ + g_debug("Authenticating username %s", username); + + result = pam_authenticate(self->pam, 0); + + if (result != PAM_SUCCESS) + { + g_critical("Failed to authenticate user: %s", pam_strerror(self->pam, result)); + session_authenticator_close_session(self); + return FALSE; + } + + /* Check if user account is valid */ + result = pam_acct_mgmt(self->pam, 0); + + if (result != PAM_SUCCESS) + { + g_critical("Failed to manage user account: %s", pam_strerror(self->pam, result)); + session_authenticator_close_session(self); + return FALSE; + } + + /* Establish credentials */ + result = pam_setcred(self->pam, PAM_ESTABLISH_CRED); + + if (result != PAM_SUCCESS) + { + g_critical("Failed to establish PAM credentials: %s", pam_strerror(self->pam, result)); + session_authenticator_close_session(self); + return FALSE; + } + return TRUE; +} + +gboolean session_authenticator_is_authenticated(SessionAuthenticator* self) +{ + return self->pam != NULL; +} + +gboolean session_authenticator_open_session(SessionAuthenticator* self) +{ + g_debug("Preparing to open PAM session."); + if(!self->pam) + { + g_critical("User unauthorized."); + return FALSE; + } + + int result = pam_open_session(self->pam, 0); + + if (result != PAM_SUCCESS) { + g_critical("Failed to open PAM session: %s", pam_strerror(self->pam, result)); + session_authenticator_close_session(self); + return FALSE; + } + return TRUE; +} + +void session_authenticator_close_session(SessionAuthenticator* self) +{ + if(!self->pam) + { + g_debug("PAM session is not opened."); + return; + } + g_debug("Ending PAM login session"); + + /* Close pam session */ + int result = pam_close_session(self->pam, 0); + if (result != PAM_SUCCESS) + { + g_debug("Failed to close PAM session: %s", pam_strerror(self->pam, result)); + } + + /* Remove credentials */ + result = pam_setcred(self->pam, PAM_DELETE_CRED); + + if (result != PAM_SUCCESS) + { + g_debug("Failed to delete PAM credentials: %s", pam_strerror(self->pam, result)); + } + + /* End pam session */ + result = pam_end(self->pam, result); + + if (result != PAM_SUCCESS) + { + g_debug("Failed to end PAM session: %s", pam_strerror(self->pam, result)); + } + self->pam = NULL; +} diff --git a/src/authenticator.h b/src/authenticator.h new file mode 100644 index 0000000..8f1399e --- /dev/null +++ b/src/authenticator.h @@ -0,0 +1,14 @@ +#ifndef AUTHENTICATOR_H +#define AUTHENTICATOR_H + +#include "base.h" + +#define SESSION_TYPE_AUTHENTICATOR (session_authenticator_get_type()) +G_DECLARE_FINAL_TYPE(SessionAuthenticator, session_authenticator, SESSION, AUTHENTICATOR, BaseObject) + +SessionAuthenticator* session_authenticator_new(const char* name); +gboolean session_authenticator_authenticate(SessionAuthenticator* self, const char* username, const char* password); +gboolean session_authenticator_is_authenticated(SessionAuthenticator* self); +gboolean session_authenticator_open_session(SessionAuthenticator* self); +void session_authenticator_close_session(SessionAuthenticator* self); +#endif \ No newline at end of file diff --git a/src/base.c b/src/base.c index e92c8f3..b93c748 100644 --- a/src/base.c +++ b/src/base.c @@ -11,6 +11,12 @@ static void base_object_dispose(GObject* object) G_OBJECT_CLASS (base_object_parent_class)->dispose(object); } +static void base_object_finalize(GObject* object) +{ + g_debug("base_object_finalize"); + G_OBJECT_CLASS (base_object_parent_class)->finalize(object); +} + /** * @brief Class initialization */ @@ -19,6 +25,7 @@ static void base_object_class_init(BaseObjectClass *class) GObjectClass *gobject_class = G_OBJECT_CLASS(class); class->to_string = NULL; gobject_class->dispose = base_object_dispose; + gobject_class->finalize = base_object_finalize; } /** diff --git a/src/configuration.c b/src/configuration.c index 6a9cca1..6119d50 100644 --- a/src/configuration.c +++ b/src/configuration.c @@ -27,18 +27,16 @@ struct _DaemonConfiguration gchar* login_session_command; gchar* login_session_user; gchar* session_command; + gchar* pam_service; gchar* string; }; G_DEFINE_FINAL_TYPE(DaemonConfiguration, daemon_configuration, BASE_TYPE_OBJECT) -/** - * @brief Dispose the object - */ -static void daemon_configuration_dispose(GObject* object) +static void daemon_configuration_finalize(GObject* object) { - DaemonConfiguration * self = DAEMON_CONFIGURATION(object); - g_debug("daemon_configuration_dispose: %s", base_object_to_string(self)); + g_debug("daemon_configuration_finalize: %s", base_object_to_string(object)); + DaemonConfiguration *self = DAEMON_CONFIGURATION(object); if(self->login_session_command) { g_free(self->login_session_command); @@ -51,11 +49,25 @@ static void daemon_configuration_dispose(GObject* object) { g_free(self->session_command); } + if(self->pam_service) + { + g_free(self->pam_service); + } if(self->string) { g_free(self->string); } self->string = NULL; + G_OBJECT_CLASS(daemon_configuration_parent_class)->finalize(object); +} + +/** + * @brief Dispose the object + */ +static void daemon_configuration_dispose(GObject* object) +{ + DaemonConfiguration * self = DAEMON_CONFIGURATION(object); + g_debug("daemon_configuration_dispose: %s", base_object_to_string(self)); G_OBJECT_CLASS(daemon_configuration_parent_class)->dispose(object); } /** @@ -117,6 +129,7 @@ static void daemon_configuration_class_init(DaemonConfigurationClass *class) BaseObjectClass *base_class = BASE_OBJECT_CLASS(class); gobject_class->dispose = daemon_configuration_dispose; + gobject_class->finalize = daemon_configuration_finalize; gobject_class->set_property = daemon_configuration_set_property; gobject_class->get_property = daemon_configuration_get_property; @@ -137,6 +150,7 @@ static void daemon_configuration_init(DaemonConfiguration *self) self->login_session_user = NULL; self->session_command = NULL; self->string = NULL; + self->pam_service = NULL; } /** * @brief Create a new DaemonConfiguration object @@ -235,6 +249,10 @@ DaemonConfiguration* daemon_configuration_new(const gchar* config_file) { conf->session_command = g_strdup(value); } + else if (strncmp(key, "pam_service", 11) == 0) + { + conf->pam_service = g_strdup(value); + } else { g_warning("Ignore unknown configuration: [%s]:[%s]", key, value); diff --git a/src/manager.c b/src/manager.c index 37f541d..ece1e18 100644 --- a/src/manager.c +++ b/src/manager.c @@ -1,5 +1,6 @@ #include "manager.h" #include "configuration.h" +#include "authenticator.h" #include #include #include @@ -9,6 +10,7 @@ #include #define PROCESS_MOM_TO 50 // in ms +#define DEFAULT_PAM_SERVICE "diya" #define DBUS_SERVER_NAME "dev.iohub.diya.SessionManager" #define DBUS_SERVER_PATH "/dev/iohub/diya/SessionManager" @@ -47,10 +49,22 @@ struct _SessionManager guint bus_id; session_manage_state_t state; gchar* current_session_user; + SessionAuthenticator* authenticator; }; G_DEFINE_FINAL_TYPE(SessionManager, session_manager, BASE_TYPE_OBJECT); +static void session_manager_finalize(GObject *object) +{ + g_debug("session_manager_finalize: %s", base_object_to_string(object)); + SessionManager *self = SESSION_MANAGER(object); + if (self->current_session_user) + { + g_free(self->current_session_user); + } + G_OBJECT_CLASS(session_manager_parent_class)->finalize(object); +} + /** * @brief Dispose the object */ @@ -74,9 +88,9 @@ static void session_manager_dispose(GObject *object) { g_bus_unown_name(self->bus_id); } - if (self->current_session_user) + if(self->authenticator) { - g_free(self->current_session_user); + g_object_unref(self->authenticator); } G_OBJECT_CLASS(session_manager_parent_class)->dispose(object); } @@ -116,6 +130,7 @@ static void session_manager_class_init(SessionManagerClass *class) GObjectClass *gobject_class = G_OBJECT_CLASS(class); BaseObjectClass *base_class = BASE_OBJECT_CLASS(class); gobject_class->dispose = session_manager_dispose; + gobject_class->finalize = session_manager_finalize; gobject_class->get_property = session_manager_get_property; base_class->to_string = session_manager_to_string; @@ -137,48 +152,12 @@ static void session_manager_init(SessionManager *self) self->bus_id = 0; self->state = SESSION_MANAGER_STATE_IDLE; self->current_session_user = NULL; + self->authenticator = NULL; } -static gboolean session_user_auth(const char *username, const char *password) +static gboolean session_user_auth(SessionManager *manager, const char *username, const char *password) { - char *encrypted; - struct passwd *pwd; - struct spwd *spwd; - /* Look up password and shadow password records for username */ - pwd = getpwnam(username); - if (pwd == NULL) - { - g_critical("getpwnam: %s", strerror(errno)); - return FALSE; - } - spwd = getspnam(username); - /* - if (spwd == NULL) - { - g_critical("getspnam: %s", strerror(errno)); - return FALSE; - } - */ - - if (spwd != NULL) /* If there is a shadow password record */ - { - pwd->pw_passwd = spwd->sp_pwdp; /* Use the shadow password */ - } - /* Encrypt password and erase cleartext version immediately */ - encrypted = crypt(password, pwd->pw_passwd); - if (encrypted == NULL) - { - g_critical("crypt: %s", strerror(errno)); - return FALSE; - } - if (strcmp(encrypted, pwd->pw_passwd) == 0) - { - return TRUE; - } - else - { - return FALSE; - } + return session_authenticator_authenticate(manager->authenticator, username, password); } static pid_t run_command_as(const gchar* command, const gchar* username) @@ -200,7 +179,7 @@ static pid_t run_command_as(const gchar* command, const gchar* username) } if (getgrouplist(username, pwd->pw_gid, g_list, &numgroups) == -1) { - g_critical("run_command_as: Unable to call getgrouplist: %s", strerror(errno)); + g_critical("run_command_as: Unable to call getgrouplist for gid %d: %s", pwd->pw_gid, strerror(errno)); return -1; } pid_t pid = fork(); @@ -280,6 +259,12 @@ static void start_user_session(SessionManager *self) g_critical("start_user_session: session_command is not set in the configuration file, do nothing"); return; } + /* + if(!session_authenticator_open_session(self->authenticator)) + { + g_critical("start_user_session: Failed to open session"); + return; + }*/ self->session_pid = run_command_as(session_command, self->current_session_user); g_free(session_command); } @@ -315,7 +300,7 @@ static void session_manager_method_call(GDBusConnection *connection, const gchar g_dbus_method_invocation_return_dbus_error(invocation, DBUS_SERVER_ERROR_NAME, "A session is currently running"); return; } - if(session_user_auth(username, password)) + if(session_user_auth(self, username, password)) { g_dbus_method_invocation_return_value(invocation, g_variant_new("(b)", TRUE)); /** @@ -436,7 +421,15 @@ SessionManager *session_manager_new(const gchar *config_file) on_name_lost, manager, NULL); - + + gchar *pam_service = NULL; + g_object_get(manager->configuration, "pam_service", &pam_service, NULL); + if (pam_service == NULL) + { + pam_service = g_strdup(DEFAULT_PAM_SERVICE); + } + manager->authenticator = session_authenticator_new(pam_service); + g_free(pam_service); return manager; } @@ -457,6 +450,8 @@ static void start_login_session(SessionManager *self) g_free(self->current_session_user); self->current_session_user = NULL; } + // close old session if any + session_authenticator_close_session(self->authenticator); gchar *login_session_command = NULL; g_object_get(self->configuration, "login-session-command", &login_session_command, NULL); if (login_session_command == NULL) @@ -473,7 +468,7 @@ static void start_login_session(SessionManager *self) g_critical("login_session_user is not set in the configuration file, start the login session as root"); return; } - + self->login_session_pid = run_command_as(login_session_command, login_session_user); g_free(login_session_user); g_free(login_session_command);