Commit 9b80ba9e authored by Alberts Muktupāvels's avatar Alberts Muktupāvels
Browse files

screensaver: add GfAuth

parent c487fa2d
Pipeline #137330 passed with stage
in 11 minutes and 30 seconds
......@@ -25,6 +25,7 @@ build-ubuntu:
libgnome-desktop-3-dev
libgtk-3-dev
libibus-1.0-dev
libpam0g-dev
libpango1.0-dev
libpolkit-agent-1-dev
libpolkit-gobject-1-dev
......
......@@ -333,6 +333,13 @@ AS_IF([test "x$with_compiz_session" != "xno"], [
AM_CONDITIONAL(WITH_COMPIZ_SESSION, [test "x$found_compiz" = "xyes"])
dnl **************************************************************************
dnl PAM
dnl **************************************************************************
AC_CHECK_HEADERS([security/pam_appl.h])
AC_SEARCH_LIBS([pam_start], [pam])
dnl **************************************************************************
dnl Process .in files
dnl **************************************************************************
......
......@@ -18,6 +18,8 @@ libscreensaver_la_CFLAGS = \
$(NULL)
libscreensaver_la_SOURCES = \
gf-auth.c \
gf-auth.h \
gf-info-bar.c \
gf-info-bar.h \
gf-listener.c \
......@@ -45,6 +47,7 @@ libscreensaver_la_LIBADD = \
$(NULL)
ENUM_TYPES = \
gf-auth.h \
$(NULL)
gf-screensaver-enum-types.c: gf-screensaver-enum-types.c.in gf-screensaver-enum-types.h $(ENUM_TYPES)
......
/*
* Copyright (C) 2019 Alberts Muktupāvels
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "gf-auth.h"
#include <glib/gi18n.h>
#include <security/pam_appl.h>
#include "gf-screensaver-enum-types.h"
typedef struct
{
GfAuth *self;
GfAuthMessageType type;
char *message;
} GfMessageData;
struct _GfAuth
{
GObject parent;
GCancellable *cancellable;
GMutex mutex;
GCond cond;
char *username;
char *display;
GTask *task;
guint message_id;
gboolean awaits_response;
char *response;
};
enum
{
PROP_0,
PROP_USERNAME,
PROP_DISPLAY,
LAST_PROP
};
static GParamSpec *auth_properties[LAST_PROP] = { NULL };
enum
{
MESSAGE,
COMPLETE,
LAST_SIGNAL
};
static guint auth_signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE (GfAuth, gf_auth, G_TYPE_OBJECT)
static GQuark
gf_auth_error_quark (void)
{
static GQuark quark;
if (G_UNLIKELY (quark == 0))
quark = g_quark_from_static_string ("gf-auth-error-quark");
return quark;
}
static GfMessageData *
gf_message_data_new (GfAuth *self,
GfAuthMessageType type,
const char *message)
{
GfMessageData *msg;
msg = g_new0 (GfMessageData, 1);
msg->self = g_object_ref (self);
msg->type = type;
msg->message = g_strdup (message);
return msg;
}
static void
gf_message_data_free (gpointer data)
{
GfMessageData *msg;
msg = data;
g_object_unref (msg->self);
g_free (msg->message);
g_free (msg);
}
static GfAuthMessageType
message_type_from_msg_style (int msg_style)
{
GfAuthMessageType type;
switch (msg_style)
{
case PAM_PROMPT_ECHO_OFF:
type = GF_AUTH_MESSAGE_PROMPT_ECHO_OFF;
break;
case PAM_PROMPT_ECHO_ON:
type = GF_AUTH_MESSAGE_PROMPT_ECHO_ON;
break;
case PAM_ERROR_MSG:
type = GF_AUTH_MESSAGE_ERROR_MSG;
break;
case PAM_TEXT_INFO:
type = GF_AUTH_MESSAGE_TEXT_INFO;
break;
default:
g_assert_not_reached ();
break;
}
return type;
}
static gboolean
message_cb (gpointer user_data)
{
GfMessageData *data;
GfAuth *self;
data = user_data;
self = data->self;
g_signal_emit (self, auth_signals[MESSAGE], 0, data->type, data->message);
self->message_id = 0;
return G_SOURCE_REMOVE;
}
static void
emit_message_idle (GfAuth *self,
int msg_style,
const char *msg)
{
GfAuthMessageType message_type;
GfMessageData *data;
message_type = message_type_from_msg_style (msg_style);
data = gf_message_data_new (self, message_type, msg);
g_assert (self->message_id == 0);
self->message_id = g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
message_cb,
data,
gf_message_data_free);
g_source_set_name_by_id (self->message_id, "[gnome-flashback] message_cb");
}
static int
conversation_cb (int num_msg,
const struct pam_message **msg,
struct pam_response **resp,
void *appdata_ptr)
{
GTask *task;
GCancellable *cancellable;
GfAuth *self;
struct pam_response *response;
int i;
task = G_TASK (appdata_ptr);
cancellable = g_task_get_cancellable (task);
self = g_task_get_task_data (task);
if (g_task_return_error_if_cancelled (task))
return PAM_CONV_ERR;
if (num_msg <= 0)
return PAM_CONV_ERR;
response = calloc (num_msg, sizeof (struct pam_response));
if (response == NULL)
return PAM_CONV_ERR;
for (i = 0; i < num_msg; i++)
{
gboolean failed;
failed = FALSE;
g_mutex_lock (&self->mutex);
emit_message_idle (self, msg[i]->msg_style, msg[i]->msg);
switch (msg[i]->msg_style)
{
case PAM_PROMPT_ECHO_ON:
case PAM_PROMPT_ECHO_OFF:
self->awaits_response = TRUE;
g_cond_wait (&self->cond, &self->mutex);
self->awaits_response = FALSE;
if (self->response != NULL)
{
response[i].resp = strdup (self->response);
g_clear_pointer (&self->response, g_free);
}
else
{
failed = TRUE;
}
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
break;
default:
failed = TRUE;
break;
}
g_mutex_unlock (&self->mutex);
if (failed || g_cancellable_is_cancelled (cancellable))
{
int j;
for (j = 0; j <= i; j++)
{
free (response[j].resp);
response[j].resp = NULL;
}
free (response);
response = NULL;
return PAM_CONV_ERR;
}
}
*resp = response;
return PAM_SUCCESS;
}
static void
terminate_pam_transaction (pam_handle_t *handle,
int status)
{
status = pam_end (handle, status);
if (status == PAM_SUCCESS)
return;
g_debug ("Failed to terminate PAM transaction (%d): %s",
status, pam_strerror (NULL, status));
}
static void
verify_in_thread_cb (GTask *task,
gpointer source_object,
gpointer task_data,
GCancellable *cancellable)
{
GfAuth *self;
struct pam_conv conv;
pam_handle_t *handle;
int status;
if (g_task_return_error_if_cancelled (task))
return;
self = g_task_get_task_data (task);
conv.conv = conversation_cb;
conv.appdata_ptr = task;
handle = NULL;
status = pam_start ("gnome-flashback", self->username, &conv, &handle);
if (status != PAM_SUCCESS)
{
g_task_return_new_error (task,
gf_auth_error_quark (),
status,
"%s",
pam_strerror (handle, status));
terminate_pam_transaction (handle, status);
return;
}
status = pam_set_item (handle, PAM_TTY, self->display);
if (status != PAM_SUCCESS)
{
g_task_return_new_error (task,
gf_auth_error_quark (),
status,
"%s",
pam_strerror (handle, status));
terminate_pam_transaction (handle, status);
return;
}
status = pam_authenticate (handle, 0);
if (g_task_return_error_if_cancelled (task))
{
terminate_pam_transaction (handle, status);
return;
}
if (status != PAM_SUCCESS)
{
g_task_return_new_error (task,
gf_auth_error_quark (),
status,
"%s",
pam_strerror (handle, status));
terminate_pam_transaction (handle, status);
return;
}
status = pam_acct_mgmt (handle, 0);
if (status != PAM_SUCCESS)
{
g_task_return_new_error (task,
gf_auth_error_quark (),
status,
"%s",
pam_strerror (handle, status));
terminate_pam_transaction (handle, status);
return;
}
status = pam_setcred (handle, PAM_REINITIALIZE_CRED);
if (status != PAM_SUCCESS)
{
g_task_return_new_error (task,
gf_auth_error_quark (),
status,
"%s",
pam_strerror (handle, status));
terminate_pam_transaction (handle, status);
return;
}
terminate_pam_transaction (handle, status);
g_task_return_boolean (task, TRUE);
}
static void
verify_done_cb (GObject *object,
GAsyncResult *res,
gpointer user_data)
{
GError *error;
gboolean verified;
GfAuth *self;
const char *message;
error = NULL;
verified = g_task_propagate_boolean (G_TASK (res), &error);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
{
g_error_free (error);
return;
}
self = GF_AUTH (user_data);
if (!verified)
g_clear_object (&self->task);
message = "";
if (error != NULL)
{
g_debug ("verify_done_cb (%d): %s", error->code, error->message);
if (error->domain == gf_auth_error_quark ())
{
switch (error->code)
{
case PAM_SUCCESS:
case PAM_IGNORE:
break;
case PAM_ACCT_EXPIRED:
case PAM_AUTHTOK_EXPIRED:
message = _("Your account was given a time limit that has now passed.");
break;
default:
message = _("Sorry, that didn’t work. Please try again.");
break;
}
}
}
g_signal_emit (self,
auth_signals[COMPLETE],
0,
verified,
message);
g_clear_error (&error);
}
static void
gf_auth_dispose (GObject *object)
{
GfAuth *self;
self = GF_AUTH (object);
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
g_mutex_lock (&self->mutex);
if (self->message_id != 0)
{
g_source_remove (self->message_id);
self->message_id = 0;
}
if (self->awaits_response)
g_cond_signal (&self->cond);
g_mutex_unlock (&self->mutex);
g_clear_object (&self->task);
G_OBJECT_CLASS (gf_auth_parent_class)->dispose (object);
}
static void
gf_auth_finalize (GObject *object)
{
GfAuth *self;
self = GF_AUTH (object);
g_clear_pointer (&self->username, g_free);
g_clear_pointer (&self->display, g_free);
g_clear_pointer (&self->response, g_free);
g_mutex_clear (&self->mutex);
g_cond_clear (&self->cond);
G_OBJECT_CLASS (gf_auth_parent_class)->finalize (object);
}
static void
gf_auth_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GfAuth *self;
self = GF_AUTH (object);
switch (property_id)
{
case PROP_USERNAME:
g_assert (self->username == NULL);
self->username = g_value_dup_string (value);
break;
case PROP_DISPLAY:
g_assert (self->display == NULL);
self->display = g_value_dup_string (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
install_properties (GObjectClass *object_class)
{
auth_properties[PROP_USERNAME] =
g_param_spec_string ("username",
"username",
"username",
NULL,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS);
auth_properties[PROP_DISPLAY] =
g_param_spec_string ("display",
"display",
"display",
NULL,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, LAST_PROP, auth_properties);
}
static void
install_signals (void)
{
auth_signals[MESSAGE] =
g_signal_new ("message",
GF_TYPE_AUTH,
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
2,
GF_TYPE_AUTH_MESSAGE_TYPE,
G_TYPE_STRING);
auth_signals[COMPLETE] =
g_signal_new ("complete",
GF_TYPE_AUTH,
G_SIGNAL_RUN_LAST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
2,
G_TYPE_BOOLEAN,
G_TYPE_STRING);
}
static void
gf_auth_class_init (GfAuthClass *self_class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (self_class);
object_class->dispose = gf_auth_dispose;
object_class->finalize = gf_auth_finalize;
object_class->set_property = gf_auth_set_property;
install_properties (object_class);
install_signals ();