]> rtime.felk.cvut.cz Git - sojka/lightdm.git/blobdiff - src/session-child.c
Re-enable SIGPIPE for children so they have default behaviour
[sojka/lightdm.git] / src / session-child.c
index 0f22d240e215cec6579190bb8209a96d6fc00883..fb12d9b812d02f0ee9ebf3a35a53a74249cd5f0b 100644 (file)
 #include <grp.h>
 #include <glib.h>
 #include <security/pam_appl.h>
+#include <utmp.h>
 #include <utmpx.h>
 #include <sys/mman.h>
 
+#if HAVE_LIBAUDIT
+#include <libaudit.h>
+#endif
+
 #include "configuration.h"
 #include "session-child.h"
 #include "session.h"
 #include "console-kit.h"
 #include "login1.h"
+#include "log-file.h"
 #include "privileges.h"
 #include "x-authority.h"
 #include "configuration.h"
@@ -66,7 +72,7 @@ read_data (void *buf, size_t count)
     n_read = read (from_daemon_output, buf, count);
     if (n_read < 0)
         g_printerr ("Error reading from daemon: %s\n", strerror (errno));
-  
+
     return n_read;
 }
 
@@ -85,10 +91,10 @@ read_string_full (void* (*alloc_fn)(size_t n))
         g_printerr ("Invalid string length %d from daemon\n", length);
         return NULL;
     }
-  
+
     value = (*alloc_fn) (sizeof (char) * (length + 1));
     read_data (value, length);
-    value[length] = '\0';      
+    value[length] = '\0';
 
     return value;
 }
@@ -196,6 +202,55 @@ read_xauth (void)
     return x_authority_new (x_authority_family, x_authority_address, x_authority_address_length, x_authority_number, x_authority_name, x_authority_data, x_authority_data_length);
 }
 
+/* GNU provides this but we can't rely on that so let's make our own version */
+static void
+updwtmpx (const gchar *wtmp_file, struct utmpx *ut)
+{
+    struct utmp u;
+
+    memset (&u, 0, sizeof (u));
+    u.ut_type = ut->ut_type;
+    u.ut_pid = ut->ut_pid;
+    if (ut->ut_line)
+        strncpy (u.ut_line, ut->ut_line, sizeof (u.ut_line));
+    if (ut->ut_id)
+        strncpy (u.ut_id, ut->ut_id, sizeof (u.ut_id));
+    if (ut->ut_user)
+        strncpy (u.ut_user, ut->ut_user, sizeof (u.ut_user));
+    if (ut->ut_host)
+        strncpy (u.ut_host, ut->ut_host, sizeof (u.ut_host));
+    u.ut_tv.tv_sec = ut->ut_tv.tv_sec;
+    u.ut_tv.tv_usec = ut->ut_tv.tv_usec;
+
+    updwtmp (wtmp_file, &u);
+}
+
+#if HAVE_LIBAUDIT
+static void
+audit_event (int type, const gchar *username, uid_t uid, const gchar *remote_host_name, const gchar *tty, gboolean success)
+{
+    int auditfd, result;
+    const char *op = NULL;
+
+    auditfd = audit_open ();
+    if (auditfd < 0) {
+        g_printerr ("Error opening audit socket: %s\n", strerror (errno));
+        return;
+    }
+
+    if (type == AUDIT_USER_LOGIN)
+        op = "login";
+    else if (type == AUDIT_USER_LOGOUT)
+        op = "logout";
+    result = success == TRUE ? 1 : 0;
+
+    if (audit_log_acct_message (auditfd, type, NULL, op, username, uid, remote_host_name, NULL, tty, result) <= 0)
+        g_printerr ("Error writing audit message: %s\n", strerror (errno));
+
+    close (auditfd);
+}
+#endif
+
 int
 session_child_run (int argc, char **argv)
 {
@@ -203,7 +258,8 @@ session_child_run (int argc, char **argv)
     int i, version, fd, result;
     gboolean auth_complete = TRUE;
     User *user = NULL;
-    gchar *log_filename, *log_backup_filename = NULL;
+    gchar *log_filename;
+    LogMode log_mode = LOG_MODE_BACKUP_AND_TRUNCATE;
     gsize env_length;
     gsize command_argc;
     gchar **command_argv;
@@ -213,21 +269,23 @@ session_child_run (int argc, char **argv)
     gchar *authentication_result_string;
     gchar *service;
     gchar *username;
-    gchar *class;
     gchar *tty;
     gchar *remote_host_name;
     gchar *xdisplay;
     XAuthority *x_authority = NULL;
     gchar *x_authority_filename;
     GDBusConnection *bus;
+    const gchar *login1_session_id = NULL;
     gchar *console_kit_cookie = NULL;
-    gchar *login1_session = NULL;
-
-    const gchar *path;
-    GError *error = NULL;
     const gchar *locale_value;
     gchar *locale_var;
     static const gchar * const locale_var_names[] = {
+        "LC_PAPER",
+        "LC_NAME",
+        "LC_ADDRESS",
+        "LC_TELEPHONE",
+        "LC_MEASUREMENT",
+        "LC_IDENTIFICATION",
         "LC_COLLATE",
         "LC_CTYPE",
         "LC_MONETARY",
@@ -238,6 +296,10 @@ session_child_run (int argc, char **argv)
         "LANG",
         NULL
     };
+    gid_t gid;
+    uid_t uid;
+    const gchar *home_directory;
+    GError *error = NULL;
 
 #if !defined(GLIB_VERSION_2_36)
     g_type_init ();
@@ -284,7 +346,7 @@ session_child_run (int argc, char **argv)
     username = read_string ();
     read_data (&do_authenticate, sizeof (do_authenticate));
     read_data (&is_interactive, sizeof (is_interactive));
-    class = read_string ();
+    read_string (); /* Used to be class, now we just use the environment variable */
     tty = read_string ();
     remote_host_name = read_string ();
     xdisplay = read_string ();
@@ -305,7 +367,7 @@ session_child_run (int argc, char **argv)
         pam_set_item (pam_handle, PAM_TTY, xdisplay);
     }
     else if (tty)
-        pam_set_item (pam_handle, PAM_TTY, tty);    
+        pam_set_item (pam_handle, PAM_TTY, tty);
 
 #ifdef PAM_XAUTHDATA
     if (x_authority)
@@ -329,15 +391,47 @@ session_child_run (int argc, char **argv)
 
         /* See what user we ended up as */
         if (pam_get_item (pam_handle, PAM_USER, (const void **) &new_username) != PAM_SUCCESS)
+        {
+            pam_end (pam_handle, 0);
             return EXIT_FAILURE;
+        }
         g_free (username);
         username = g_strdup (new_username);
 
+        /* Write record to btmp database */
+        if (authentication_result == PAM_AUTH_ERR)
+        {
+            struct utmpx ut;
+            struct timeval tv;
+
+            memset (&ut, 0, sizeof (ut));
+            ut.ut_type = USER_PROCESS;
+            ut.ut_pid = getpid ();
+            if (xdisplay)
+                strncpy (ut.ut_id, xdisplay, sizeof (ut.ut_id));
+            if (tty && g_str_has_prefix (tty, "/dev/"))
+                strncpy (ut.ut_line, tty + strlen ("/dev/"), sizeof (ut.ut_line));
+            strncpy (ut.ut_user, username, sizeof (ut.ut_user));
+            if (xdisplay)
+                strncpy (ut.ut_host, xdisplay, sizeof (ut.ut_host));
+            else if (remote_host_name)
+                strncpy (ut.ut_host, remote_host_name, sizeof (ut.ut_host));
+            gettimeofday (&tv, NULL);
+            ut.ut_tv.tv_sec = tv.tv_sec;
+            ut.ut_tv.tv_usec = tv.tv_usec;
+
+            updwtmpx ("/var/log/btmp", &ut);
+
+#if HAVE_LIBAUDIT
+            audit_event (AUDIT_USER_LOGIN, username, -1, remote_host_name, tty, FALSE);
+#endif
+        }
+
         /* Check account is valid */
         if (authentication_result == PAM_SUCCESS)
             authentication_result = pam_acct_mgmt (pam_handle, 0);
         if (authentication_result == PAM_NEW_AUTHTOK_REQD)
-            authentication_result = pam_chauthtok (pam_handle, 0);
+            authentication_result = pam_chauthtok (pam_handle, PAM_CHANGE_EXPIRED_AUTHTOK);
     }
     else
         authentication_result = PAM_SUCCESS;
@@ -386,19 +480,25 @@ session_child_run (int argc, char **argv)
     if (!username)
     {
         g_printerr ("No user selected during authentication\n");
+        pam_end (pam_handle, 0);
         return EXIT_FAILURE;
     }
 
     /* Stop if we didn't authenticated */
     if (authentication_result != PAM_SUCCESS)
+    {
+        pam_end (pam_handle, 0);
         return EXIT_FAILURE;
+    }
 
     /* Get the command to run (blocks) */
     log_filename = read_string ();
+    if (version >= 3)
+        read_data (&log_mode, sizeof (log_mode));
     if (version >= 1)
     {
         g_free (tty);
-        tty = read_string ();      
+        tty = read_string ();
     }
     x_authority_filename = read_string ();
     if (version >= 1)
@@ -418,19 +518,29 @@ session_child_run (int argc, char **argv)
         command_argv[i] = read_string ();
     command_argv[i] = NULL;
 
+    /* If nothing to run just refresh credentials because we successfully authenticated */
+    if (command_argc == 0)
+    {
+        pam_setcred (pam_handle, PAM_REINITIALIZE_CRED);
+        pam_end (pam_handle, 0);
+        return EXIT_SUCCESS;
+    }
+
     /* Redirect stderr to a log file */
     if (log_filename)
-        log_backup_filename = g_strdup_printf ("%s.old", log_filename);
-    if (!log_filename)
     {
-        fd = open ("/dev/null", O_WRONLY);   
-        dup2 (fd, STDERR_FILENO);
-        close (fd);
+        if (g_path_is_absolute (log_filename))
+        {
+            fd = log_file_open (log_filename, log_mode);
+            dup2 (fd, STDERR_FILENO);
+            close (fd);
+            g_free (log_filename);
+            log_filename = NULL;
+        }
     }
-    else if (g_path_is_absolute (log_filename))
+    else
     {
-        rename (log_filename, log_backup_filename);
-        fd = open (log_filename, O_WRONLY | O_APPEND | O_CREAT, 0600);
+        fd = open ("/dev/null", O_WRONLY);
         dup2 (fd, STDERR_FILENO);
         close (fd);
     }
@@ -450,14 +560,16 @@ session_child_run (int argc, char **argv)
     if (result != PAM_SUCCESS)
     {
         g_printerr ("Failed to establish PAM credentials: %s\n", pam_strerror (pam_handle, result));
+        pam_end (pam_handle, 0);
         return EXIT_FAILURE;
     }
-     
+
     /* Open the session */
     result = pam_open_session (pam_handle, 0);
     if (result != PAM_SUCCESS)
     {
         g_printerr ("Failed to open PAM session: %s\n", pam_strerror (pam_handle, result));
+        pam_end (pam_handle, 0);
         return EXIT_FAILURE;
     }
 
@@ -466,21 +578,25 @@ session_child_run (int argc, char **argv)
     if (error)
         g_printerr ("Unable to contact system bus: %s", error->message);
     if (!bus)
+    {
+        pam_end (pam_handle, 0);
         return EXIT_FAILURE;
+    }
 
-    if (login1_is_running ())
+    /* Check what logind session we are, or fallback to ConsoleKit */
+    login1_session_id = pam_getenv (pam_handle, "XDG_SESSION_ID");
+    if (login1_session_id)
     {
-        login1_session = login1_get_session_id ();
-        write_string (login1_session);
+        write_string (login1_session_id);
+        if (version >= 2)
+            write_string (NULL);
     }
-
-    if (!login1_session)
+    else
     {
-        /* Open a Console Kit session */
         g_variant_builder_init (&ck_parameters, G_VARIANT_TYPE ("(a(sv))"));
         g_variant_builder_open (&ck_parameters, G_VARIANT_TYPE ("a(sv)"));
         g_variant_builder_add (&ck_parameters, "(sv)", "unix-user", g_variant_new_int32 (user_get_uid (user)));
-        if (g_strcmp0 (class, XDG_SESSION_CLASS_GREETER) == 0)
+        if (g_strcmp0 (pam_getenv (pam_handle, "XDG_SESSION_CLASS"), "greeter") == 0)
             g_variant_builder_add (&ck_parameters, "(sv)", "session-type", g_variant_new_string ("LoginWindow"));
         if (xdisplay)
         {
@@ -496,13 +612,26 @@ session_child_run (int argc, char **argv)
         else
             g_variant_builder_add (&ck_parameters, "(sv)", "is-local", g_variant_new_boolean (TRUE));
         console_kit_cookie = ck_open_session (&ck_parameters);
+        if (version >= 2)
+            write_string (NULL);
         write_string (console_kit_cookie);
         if (console_kit_cookie)
         {
             gchar *value;
+            gchar *runtime_dir;
             value = g_strdup_printf ("XDG_SESSION_COOKIE=%s", console_kit_cookie);
             pam_putenv (pam_handle, value);
             g_free (value);
+
+            runtime_dir = ck_get_xdg_runtime_dir (console_kit_cookie);
+            if (runtime_dir)
+            {
+                gchar *value;
+                value = g_strdup_printf ("XDG_RUNTIME_DIR=%s", runtime_dir);
+                pam_putenv (pam_handle, value);
+                g_free (value);
+                g_free (runtime_dir);
+            }
         }
     }
 
@@ -515,7 +644,7 @@ session_child_run (int argc, char **argv)
 
         drop_privileges = geteuid () == 0;
         if (drop_privileges)
-            privileges_drop (user);
+            privileges_drop (user_get_uid (user), user_get_gid (user));
         result = x_authority_write (x_authority, XAUTH_WRITE_MODE_REPLACE, x_authority_filename, &error);
         if (drop_privileges)
             privileges_reclaim ();
@@ -524,69 +653,62 @@ session_child_run (int argc, char **argv)
             g_printerr ("Error writing X authority: %s\n", error->message);
         g_clear_error (&error);
         if (!result)
+        {
+            pam_end (pam_handle, 0);
             return EXIT_FAILURE;
+        }
 
         value = g_strdup_printf ("XAUTHORITY=%s", x_authority_filename);
         pam_putenv (pam_handle, value);
         g_free (value);
     }
 
-    /* Put our tools directory in the path as a hack so we can use the legacy gdmflexiserver interface */
-    path = pam_getenv (pam_handle, "PATH");
-    if (path)
-        pam_putenv (pam_handle, g_strdup_printf ("PATH=%s:%s", PKGLIBEXEC_DIR, path));
-
     /* Catch terminate signal and pass it to the child */
     signal (SIGTERM, signal_cb);
 
     /* Run the command as the authenticated user */
-    child_pid = fork (); 
+    uid = user_get_uid (user);
+    gid = user_get_gid (user);
+    home_directory = user_get_home_directory (user);
+    child_pid = fork ();
     if (child_pid == 0)
     {
-        // FIXME: This is not thread safe (particularly the printfs)
-
         /* Make this process its own session */
         if (setsid () < 0)
-            g_printerr ("Failed to make process a new session: %s\n", strerror (errno));
+            _exit (errno);
 
         /* Change to this user */
         if (getuid () == 0)
         {
-            if (setgid (user_get_gid (user)) != 0)
-            {
-                g_printerr ("Failed to set group ID to %d: %s\n", user_get_gid (user), strerror (errno));
-                _exit (EXIT_FAILURE);
-            }
+            if (setgid (gid) != 0)
+                _exit (errno);
 
-            if (setuid (user_get_uid (user)) != 0)
-            {
-                g_printerr ("Failed to set user ID to %d: %s\n", user_get_uid (user), strerror (errno));
-                _exit (EXIT_FAILURE);
-            }
+            if (setuid (uid) != 0)
+                _exit (errno);
         }
 
         /* Change working directory */
         /* NOTE: This must be done after the permissions are changed because NFS filesystems can
          * be setup so the local root user accesses the NFS files as 'nobody'.  If the home directories
          * are not system readable then the chdir can fail */
-        if (chdir (user_get_home_directory (user)) != 0)
-        {
-            g_printerr ("Failed to change to home directory %s: %s\n", user_get_home_directory (user), strerror (errno));
-            _exit (EXIT_FAILURE);
-        }
+        if (chdir (home_directory) != 0)
+            _exit (errno);
 
-        /* Redirect stderr to a log file */
-        if (log_filename && !g_path_is_absolute (log_filename))
+        if (log_filename)
         {
-            rename (log_filename, log_backup_filename);
-            fd = open (log_filename, O_WRONLY | O_APPEND | O_CREAT, 0600);
-            dup2 (fd, STDERR_FILENO);
-            close (fd);
+            fd = log_file_open (log_filename, log_mode);
+            if (fd >= 0)
+            {
+                dup2 (fd, STDERR_FILENO);
+                close (fd);
+            }
         }
 
+        /* Reset SIGPIPE handler so the child has default behaviour (we disabled it at LightDM start) */
+        signal (SIGPIPE, SIG_DFL);
+
         /* Run the command */
         execve (command_argv[0], command_argv, pam_getenvlist (pam_handle));
-        g_printerr ("Failed to run command: %s\n", strerror (errno));
         _exit (EXIT_FAILURE);
     }
 
@@ -601,7 +723,7 @@ session_child_run (int argc, char **argv)
     if (child_pid > 0)
     {
         /* Log to utmp */
-        if (g_strcmp0 (class, XDG_SESSION_CLASS_GREETER) != 0)
+        if (g_strcmp0 (pam_getenv (pam_handle, "XDG_SESSION_CLASS"), "greeter") != 0)
         {
             struct utmpx ut;
             struct timeval tv;
@@ -609,8 +731,10 @@ session_child_run (int argc, char **argv)
             memset (&ut, 0, sizeof (ut));
             ut.ut_type = USER_PROCESS;
             ut.ut_pid = child_pid;
-            strncpy (ut.ut_line, tty + strlen ("/dev/"), sizeof (ut.ut_line));
-            strncpy (ut.ut_id, xdisplay, sizeof (ut.ut_id));
+            if (xdisplay)
+                strncpy (ut.ut_id, xdisplay, sizeof (ut.ut_id));
+            if (tty && g_str_has_prefix (tty, "/dev/"))
+                strncpy (ut.ut_line, tty + strlen ("/dev/"), sizeof (ut.ut_line));
             strncpy (ut.ut_user, username, sizeof (ut.ut_user));
             if (xdisplay)
                 strncpy (ut.ut_host, xdisplay, sizeof (ut.ut_host));
@@ -620,17 +744,23 @@ session_child_run (int argc, char **argv)
             ut.ut_tv.tv_sec = tv.tv_sec;
             ut.ut_tv.tv_usec = tv.tv_usec;
 
+            /* Write records to utmp/wtmp databases */
             setutxent ();
             if (!pututxline (&ut))
                 g_printerr ("Failed to write utmpx: %s\n", strerror (errno));
             endutxent ();
+            updwtmpx ("/var/log/wtmp", &ut);
+
+#if HAVE_LIBAUDIT          
+            audit_event (AUDIT_USER_LOGIN, username, uid, remote_host_name, tty, TRUE);
+#endif
         }
 
         waitpid (child_pid, &return_code, 0);
         child_pid = 0;
 
         /* Log to utmp */
-        if (g_strcmp0 (class, XDG_SESSION_CLASS_GREETER) != 0)
+        if (g_strcmp0 (pam_getenv (pam_handle, "XDG_SESSION_CLASS"), "greeter") != 0)
         {
             struct utmpx ut;
             struct timeval tv;
@@ -638,8 +768,10 @@ session_child_run (int argc, char **argv)
             memset (&ut, 0, sizeof (ut));
             ut.ut_type = DEAD_PROCESS;
             ut.ut_pid = child_pid;
-            strncpy (ut.ut_line, tty + strlen ("/dev/"), sizeof (ut.ut_line));
-            strncpy (ut.ut_id, xdisplay, sizeof (ut.ut_id));
+            if (xdisplay)
+                strncpy (ut.ut_id, xdisplay, sizeof (ut.ut_id));
+            if (tty && g_str_has_prefix (tty, "/dev/"))
+                strncpy (ut.ut_line, tty + strlen ("/dev/"), sizeof (ut.ut_line));
             strncpy (ut.ut_user, username, sizeof (ut.ut_user));
             if (xdisplay)
                 strncpy (ut.ut_host, xdisplay, sizeof (ut.ut_host));
@@ -649,10 +781,16 @@ session_child_run (int argc, char **argv)
             ut.ut_tv.tv_sec = tv.tv_sec;
             ut.ut_tv.tv_usec = tv.tv_usec;
 
+            /* Write records to utmp/wtmp databases */
             setutxent ();
             if (!pututxline (&ut))
                 g_printerr ("Failed to write utmpx: %s\n", strerror (errno));
             endutxent ();
+            updwtmpx ("/var/log/wtmp", &ut);
+
+#if HAVE_LIBAUDIT
+            audit_event (AUDIT_USER_LOGOUT, username, uid, remote_host_name, tty, TRUE);
+#endif
         }
     }
 
@@ -664,7 +802,7 @@ session_child_run (int argc, char **argv)
 
         drop_privileges = geteuid () == 0;
         if (drop_privileges)
-            privileges_drop (user);
+            privileges_drop (user_get_uid (user), user_get_gid (user));
         result = x_authority_write (x_authority, XAUTH_WRITE_MODE_REMOVE, x_authority_filename, &error);
         if (drop_privileges)
             privileges_reclaim ();
@@ -684,7 +822,7 @@ session_child_run (int argc, char **argv)
     pam_close_session (pam_handle, 0);
 
     /* Remove credentials */
-    result = pam_setcred (pam_handle, PAM_DELETE_CRED);
+    pam_setcred (pam_handle, PAM_DELETE_CRED);
 
     pam_end (pam_handle, 0);
     pam_handle = NULL;