~lightdm-team/lightdm/1.2

« back to all changes in this revision

Viewing changes to src/display.c

  • Committer: robert.ancell at gmail
  • Date: 2010-04-29 11:08:26 UTC
  • Revision ID: robert.ancell@gmail.com-20100429110826-eef3w3c9hl80pxxy
Compiles and does something

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2010 Canonical Ltd.
 
3
 * Author: Robert Ancell <robert.ancell@canonical.com>
 
4
 * 
 
5
 * This program is free software: you can redistribute it and/or modify it under
 
6
 * the terms of the GNU General Public License as published by the Free Software
 
7
 * Foundation, either version 3 of the License, or (at your option) any later
 
8
 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
 
9
 * license.
 
10
 */
 
11
 
 
12
#include <stdlib.h>
 
13
#include <errno.h>
 
14
#include <string.h>
 
15
#include <sys/wait.h>
 
16
#include <pwd.h>
 
17
#include <unistd.h>
 
18
#include <security/pam_appl.h>
 
19
 
 
20
#include "display.h"
 
21
 
 
22
enum {
 
23
    EXITED,
 
24
    LAST_SIGNAL
 
25
};
 
26
static guint signals[LAST_SIGNAL] = { 0 };
 
27
 
 
28
struct DisplayPrivate
 
29
{
 
30
    /* X process */
 
31
    GPid xserver_pid;
 
32
 
 
33
    /* Session process (either greeter or user session) */
 
34
    GPid session_pid;
 
35
 
 
36
    /* Authentication thread */
 
37
    GThread *authentication_thread;
 
38
 
 
39
    /* Queue to feed responses to the authentication thread */
 
40
    GAsyncQueue *authentication_response_queue;
 
41
  
 
42
    /* Current D-Bus call context */
 
43
    DBusGMethodInvocation *dbus_context;
 
44
 
 
45
    /* Authentication handle */
 
46
    pam_handle_t *pam_handle;
 
47
 
 
48
    /* User logged in as */
 
49
    char *username;
 
50
 
 
51
    /* TRUE if user has been authenticated */
 
52
    gboolean authenticated;
 
53
  
 
54
    /* TRUE if in user session */
 
55
    gboolean in_user_session;
 
56
 
 
57
    // FIXME: Token for secure access to this server
 
58
};
 
59
 
 
60
G_DEFINE_TYPE (Display, display, G_TYPE_OBJECT);
 
61
 
 
62
static void start_greeter (Display *display);
 
63
static void start_user_session (Display *display);
 
64
 
 
65
Display *
 
66
display_new (void)
 
67
{
 
68
    return g_object_new (DISPLAY_TYPE, NULL);
 
69
}
 
70
 
 
71
static void
 
72
session_watch_cb (GPid pid, gint status, gpointer data)
 
73
{
 
74
    Display *display = data;
 
75
 
 
76
    if (WIFEXITED (status))
 
77
        g_debug ("Display exited with return value %d", WEXITSTATUS (status));
 
78
    else if (WIFSIGNALED (status))
 
79
        g_debug ("Display terminated with signal %d", WTERMSIG (status));
 
80
 
 
81
    display->priv->session_pid = 0;
 
82
 
 
83
    // FIXME: Check for respawn loops
 
84
    if (display->priv->authenticated && !display->priv->in_user_session)
 
85
        start_user_session (display);
 
86
    else
 
87
        start_greeter (display);
 
88
}
 
89
 
 
90
static void
 
91
start_session (Display *display, const char *username, char * const argv[])
 
92
{
 
93
    pid_t pid;
 
94
    struct passwd *user_info;
 
95
 
 
96
    g_return_if_fail (display->priv->session_pid == 0);
 
97
 
 
98
    errno = 0;
 
99
    user_info = getpwnam (username);
 
100
    if (!user_info)
 
101
    {
 
102
        g_warning ("Unable to get information on user %s: %s", username, strerror (errno));
 
103
        return;
 
104
    }
 
105
 
 
106
    pid = fork ();
 
107
    if (pid < 0)
 
108
    {
 
109
        g_warning ("Failed to fork session: %s", strerror (errno));
 
110
        return;
 
111
    }
 
112
    else if (pid > 0)
 
113
    {
 
114
        g_debug ("Child process started with PID %d", pid);
 
115
        display->priv->session_pid = pid;
 
116
        g_child_watch_add (display->priv->session_pid, session_watch_cb, display);
 
117
        return;
 
118
    }
 
119
 
 
120
    if (setgid (user_info->pw_gid) != 0)
 
121
    {
 
122
        g_warning ("Failed to set group ID: %s", strerror (errno));
 
123
        _exit(1);
 
124
    }
 
125
    // FIXME: Is there a risk of connecting to the process for a user in the given group and accessing memory?
 
126
    if (setuid (user_info->pw_uid) != 0)
 
127
    {
 
128
        g_warning ("Failed to set user ID: %s", strerror (errno));
 
129
        _exit(1);
 
130
    }
 
131
    if (chdir (user_info->pw_dir) != 0)
 
132
        g_warning ("Failed to change directory: %s", strerror (errno));
 
133
 
 
134
    /* Reset environment */
 
135
    // FIXME: Check these work
 
136
    clearenv ();
 
137
    setenv ("USER", user_info->pw_name, 1);
 
138
    setenv ("HOME", user_info->pw_dir, 1);
 
139
    setenv ("SHELL", user_info->pw_shell, 1);
 
140
    setenv ("HOME", user_info->pw_dir, 1);
 
141
    setenv ("DISPLAY", ":0", 1);
 
142
 
 
143
    execv (argv[0], argv);
 
144
}
 
145
 
 
146
static void
 
147
start_user_session (Display *display)
 
148
 
149
    char *argv[] = { "/usr/bin/gnome-terminal", NULL };
 
150
    display->priv->in_user_session = TRUE;
 
151
    start_session (display, display->priv->username, argv);
 
152
}
 
153
 
 
154
static void
 
155
start_greeter (Display *display)
 
156
{
 
157
    char *argv[] = { "/usr/bin/lightdm-greeter", NULL };
 
158
    display->priv->in_user_session = FALSE;
 
159
    g_free (display->priv->username);
 
160
    display->priv->username = NULL;
 
161
    start_session (display, "gdm", argv);
 
162
}
 
163
 
 
164
static int
 
165
pam_conv_cb (int num_msg, const struct pam_message **msg,
 
166
             struct pam_response **resp, void *app_data)
 
167
{
 
168
    Display *display = app_data;
 
169
    gpointer response;
 
170
 
 
171
    /* Respond to d-bus query with messages */
 
172
    dbus_g_method_return (display->priv->dbus_context, 666); // ACTIONS
 
173
    display->priv->dbus_context = NULL;
 
174
 
 
175
    /* Wait for responses */
 
176
    response = g_async_queue_pop (display->priv->authentication_response_queue);
 
177
 
 
178
    /* Fill responses */
 
179
    // ...
 
180
 
 
181
    return 0;
 
182
}
 
183
 
 
184
static gpointer
 
185
authenticate_cb (gpointer data)
 
186
{
 
187
    Display *display = data;
 
188
    struct pam_conv conversation = { pam_conv_cb, display };
 
189
    int result;
 
190
 
 
191
    pam_start ("check_pass", display->priv->username, &conversation, &display->priv->pam_handle);
 
192
    result = pam_authenticate (display->priv->pam_handle, 0);
 
193
 
 
194
    /* Thread complete */
 
195
    g_thread_join (display->priv->authentication_thread);
 
196
    display->priv->authentication_thread = NULL;
 
197
    g_async_queue_unref (display->priv->authentication_response_queue);
 
198
    display->priv->authentication_response_queue = NULL;
 
199
 
 
200
    /* Respond to D-Bus request */
 
201
    dbus_g_method_return (display->priv->dbus_context, 888); // FINISHED
 
202
    display->priv->dbus_context = NULL;
 
203
 
 
204
    return NULL;
 
205
}
 
206
 
 
207
gboolean
 
208
display_start_authentication (Display *display, const char *username, DBusGMethodInvocation *context)
 
209
{
 
210
    GError *error = NULL;
 
211
 
 
212
    // FIXME: Only allow calls from the greeter
 
213
 
 
214
    g_return_val_if_fail (display->priv->authentication_thread != NULL, FALSE);
 
215
 
 
216
    /* Store authentication request and D-Bus request to respond to */
 
217
    g_free (display->priv->username);
 
218
    display->priv->username = g_strdup (username);
 
219
    display->priv->dbus_context = context;
 
220
 
 
221
    /* Start thread */
 
222
    display->priv->authentication_response_queue = g_async_queue_new ();
 
223
    display->priv->authentication_thread = g_thread_create (authenticate_cb, display, TRUE, &error);
 
224
    if (!display->priv->authentication_thread)
 
225
    {
 
226
        g_warning ("Failed to start authentication thread: %s", error->message);
 
227
        display->priv->dbus_context = NULL;
 
228
        return FALSE;
 
229
    }
 
230
    g_clear_error (&error);
 
231
 
 
232
    return TRUE;
 
233
}
 
234
 
 
235
gboolean
 
236
display_continue_authentication (Display *display, int data, DBusGMethodInvocation *context)
 
237
{
 
238
    g_return_val_if_fail (display->priv->authentication_thread == NULL, FALSE);
 
239
    g_return_val_if_fail (display->priv->dbus_context != NULL, FALSE);
 
240
 
 
241
    // FIXME: Only allow calls from the greeter
 
242
 
 
243
    /* Push onto queue and store request to respond to */
 
244
    display->priv->dbus_context = context;
 
245
    g_async_queue_push (display->priv->authentication_response_queue, GINT_TO_POINTER (data));
 
246
 
 
247
    return TRUE;
 
248
}
 
249
 
 
250
static void
 
251
xserver_watch_cb (GPid pid, gint status, gpointer data)
 
252
{
 
253
    Display *display = data;
 
254
 
 
255
    if (WIFEXITED (status))
 
256
        g_debug ("Display exited with return value %d", WEXITSTATUS (status));
 
257
    else if (WIFSIGNALED (status))
 
258
        g_debug ("Display terminated with signal %d", WTERMSIG (status));
 
259
 
 
260
    display->priv->xserver_pid = 0;
 
261
 
 
262
    g_signal_emit (display, signals[EXITED], 0);
 
263
}
 
264
 
 
265
static void
 
266
display_init (Display *display)
 
267
{
 
268
    GError *error = NULL;
 
269
    char *argv[] = { "/usr/bin/X", ":0", NULL };
 
270
    char *env[] = { NULL };
 
271
    gboolean result;
 
272
 
 
273
    display->priv = G_TYPE_INSTANCE_GET_PRIVATE (display, DISPLAY_TYPE, DisplayPrivate);
 
274
 
 
275
    result = g_spawn_async (NULL, /* Working directory */
 
276
                            argv,
 
277
                            env,
 
278
                            G_SPAWN_DO_NOT_REAP_CHILD,
 
279
                            NULL, NULL,
 
280
                            &display->priv->xserver_pid,
 
281
                            &error);
 
282
    if (!result)
 
283
        g_warning ("Unable to create display: %s", error->message);
 
284
    else
 
285
        g_child_watch_add (display->priv->xserver_pid, xserver_watch_cb, display);
 
286
    g_clear_error (&error);
 
287
 
 
288
    /* TODO: Do autologin if this is requested */
 
289
    start_greeter (display);
 
290
}
 
291
 
 
292
static void
 
293
display_class_init (DisplayClass *klass)
 
294
{
 
295
    g_type_class_add_private (klass, sizeof (DisplayPrivate));
 
296
 
 
297
    signals[EXITED] =
 
298
        g_signal_new ("exited",
 
299
                      G_TYPE_FROM_CLASS (klass),
 
300
                      G_SIGNAL_RUN_LAST,
 
301
                      G_STRUCT_OFFSET (DisplayClass, exited),
 
302
                      NULL, NULL,
 
303
                      g_cclosure_marshal_VOID__VOID,
 
304
                      G_TYPE_NONE, 0);
 
305
}