The attached patch adds a pam profile for su -l (and su -), patches su to use that profile if su - is used, and adds a pseudo-xdg-session pam module.
It isn't possible to guarantee there's a "real" XDG session for that user for the su - session to join, and you making logind handle su - and similar sessions properly requires a fair bit of surgery, so we set up a summy XDG_RUNTIME_DIR instead.
diff -Nru shadow-4.2/debian/changelog shadow-4.2/debian/changelog --- shadow-4.2/debian/changelog 2015-11-12 14:33:56.000000000 +0000 +++ shadow-4.2/debian/changelog 2016-04-27 19:28:51.000000000 +0100 @@ -1,3 +1,11 @@ +shadow (1:4.2-3.2) stretch; urgency=medium + + * Add su-l pam.d stack and patch su to use it + * pam_pseudo_session: module to make minimal dummy XDG environment + for su - style sessions + + -- Vivek Das Mohapatra <vi...@collabora.com> Wed, 27 Apr 2016 12:00:18 +0100 + shadow (1:4.2-3.1) unstable; urgency=medium * Non-maintainer upload. diff -Nru shadow-4.2/debian/login.install shadow-4.2/debian/login.install --- shadow-4.2/debian/login.install 2014-11-19 20:41:19.000000000 +0000 +++ shadow-4.2/debian/login.install 2016-04-27 19:29:03.000000000 +0100 @@ -17,9 +17,11 @@ usr/share/man/man8/faillog.8 usr/share/man/man8/lastlog.8 usr/share/man/man8/nologin.8 +usr/share/man/man8/pam_pseudo_session.8 usr/sbin/nologin usr/bin/faillog usr/bin/lastlog usr/bin/newgrp bin/login bin/su +lib/*/security/*.so diff -Nru shadow-4.2/debian/login.su-l.pam shadow-4.2/debian/login.su-l.pam --- shadow-4.2/debian/login.su-l.pam 1970-01-01 01:00:00.000000000 +0100 +++ shadow-4.2/debian/login.su-l.pam 2016-04-27 19:36:54.000000000 +0100 @@ -0,0 +1,9 @@ +#%PAM-1.0 +auth include su +account include su +password include su +session optional pam_keyinit.so force revoke +session include su +session optional pam_pseudo_session.so + + diff -Nru shadow-4.2/debian/patches/470_su_pam_xdg_session shadow-4.2/debian/patches/470_su_pam_xdg_session --- shadow-4.2/debian/patches/470_su_pam_xdg_session 1970-01-01 01:00:00.000000000 +0100 +++ shadow-4.2/debian/patches/470_su_pam_xdg_session 2016-04-27 19:23:20.000000000 +0100 @@ -0,0 +1,335 @@ +--- a/src/su.c ++++ b/src/su.c +@@ -960,6 +960,7 @@ + + #ifdef USE_PAM + int ret; ++ const char *pam_service = NULL; + #endif /* USE_PAM */ + + old_debian_behavior = (getenv("SU_NO_SHELL_ARGS") != NULL); +@@ -977,7 +978,8 @@ + initenv (); + + #ifdef USE_PAM +- ret = pam_start ("su", name, &conv, &pamh); ++ pam_service = fakelogin ? "su-l" : "su"; ++ ret = pam_start (pam_service, name, &conv, &pamh); + if (PAM_SUCCESS != ret) { + SYSLOG ((LOG_ERR, "pam_start: error %d", ret); + fprintf (stderr, +--- a/Makefile.am ++++ b/Makefile.am +@@ -5,4 +5,5 @@ + AUTOMAKE_OPTIONS = 1.5 dist-bzip2 foreign + + SUBDIRS = po man libmisc lib src \ +- contrib doc etc ++ contrib doc etc pam ++ +--- /dev/null ++++ b/pam/Makefile.am +@@ -0,0 +1,12 @@ ++pamdir = $(libdir)/security ++man_MANS = pam_pseudo_session.8 ++pam_pseudo_session_la_SOURCES = pam_pseudo_session.c pam_pseudo_session.sym ++pam_pseudo_session_la_CFLAGS = $(AM_CFLAGS) -shared -fPIC -DPIC ++pam_pseudo_session_la_LDFLAGS = $(AM_LDFLAGS) \ ++ -module \ ++ -export-dynamic \ ++ -avoid-version \ ++ -Wl,--version-script=$(top_srcdir)/pam/pam_pseudo_session.sym ++pam_pseudo_session_la_LIBADD = $(LIBPAM) ++ ++pam_LTLIBRARIES = pam_pseudo_session.la +--- /dev/null ++++ b/pam/pam_pseudo_session.sym +@@ -0,0 +1,6 @@ ++{ ++global: ++ pam_sm_close_session; ++ pam_sm_open_session; ++local: *; ++}; +--- a/configure.in ++++ b/configure.in +@@ -9,7 +9,7 @@ + + AC_GNU_SOURCE + +-AM_DISABLE_SHARED ++AM_ENABLE_SHARED + AM_ENABLE_STATIC + + AM_MAINTAINER_MODE +@@ -656,6 +656,7 @@ + man/zh_TW/Makefile + libmisc/Makefile + lib/Makefile ++ pam/Makefile + src/Makefile + contrib/Makefile + etc/Makefile +--- /dev/null ++++ b/pam/pam_pseudo_session.c +@@ -0,0 +1,177 @@ ++/* pam_pseudo_session module */ ++ ++/* ++ * Written by Vivek Das Mohapatra <vi...@collabora.com> 2016-04-26 ++ * Copyright © Collabora Ltd 2016 ++ * provides a pseudo-xdg set of environment variables and directories ++ * to allow basic functionality like systemd --test to work in a ++ * su - style user session. Explicitly does _not_ join the su - ++ * to an existing user XDG environment. ++ */ ++ ++#include <stdlib.h> ++#include <stdio.h> ++#include <stdarg.h> ++#include <sys/stat.h> ++#include <sys/types.h> ++#include <pwd.h> ++#include <syslog.h> ++#include <signal.h> ++#include <unistd.h> ++#include <errno.h> ++#include <sys/wait.h> ++#include <assert.h> ++ ++#define PAM_SM_SESSION ++#define XDG_PSEUDOTMP "/run/user/pseudo-xdg" ++ ++ ++#include <security/pam_modules.h> ++#include <security/pam_modutil.h> ++#include <security/_pam_macros.h> ++#include <security/pam_ext.h> ++ ++static void safeprintf (char *buf, size_t limit, const char *format, ...) ++{ ++ int written; ++ va_list ap; ++ ++ va_start(ap, format); ++ written = vsnprintf(buf, limit - 1, format, ap); ++ va_end(ap); ++ ++ if (written >= limit - 1) ++ buf[limit - 1] = '\0'; ++} ++ ++static int ++get_user_data (pam_handle_t *handle, struct passwd** user) ++{ ++ int result; ++ const char *name = NULL; ++ struct passwd *entry = NULL; ++ ++ *user = NULL; ++ result = pam_get_user(handle, &name, NULL); ++ ++ if (result != PAM_SUCCESS) ++ { ++ pam_syslog(handle, LOG_ERR, "Failed to get user name."); ++ return result; ++ } ++ ++ if (name == NULL || *name == '\0') ++ { ++ pam_syslog(handle, LOG_ERR, "User name is empty."); ++ return PAM_USER_UNKNOWN; ++ } ++ ++ entry = pam_modutil_getpwnam(handle, name); ++ ++ if(!entry) ++ { ++ pam_syslog(handle, LOG_ERR, "Failed to get user data."); ++ return PAM_USER_UNKNOWN; ++ } ++ ++ *user = entry; ++ ++ return PAM_SUCCESS; ++} ++ ++static int ++env_setup (pam_handle_t *handle, const char *path) ++{ ++ char env_key[255]; ++ ++ safeprintf(env_key, sizeof(env_key), "XDG_RUNTIME_DIR=%s", path); ++ ++ return pam_putenv(handle, env_key); ++} ++ ++static int ++pseudo_session_setup (pam_handle_t *handle, ++ struct passwd *user, ++ char *path, ++ int pathmax) ++{ ++ int result; ++ ++ safeprintf(path, pathmax, XDG_PSEUDOTMP "/%d", user->pw_uid); ++ ++ result = mkdir(XDG_PSEUDOTMP, 0755); ++ if (result != 0 && errno != EEXIST) ++ return PAM_SYSTEM_ERR; ++ ++ result = mkdir(path, 0700); ++ if (result == 0) ++ { ++ result = chown(path, user->pw_uid, user->pw_gid); ++ if (result == 0) ++ return PAM_SUCCESS; ++ else ++ return PAM_SYSTEM_ERR; ++ } ++ ++ // Ok if it already exists, if it has the right perms: ++ // or if it's owned by root:root which is a potential race ++ // if > 1 su - style sessions happen close together: ++ if(errno == EEXIST) ++ { ++ struct stat dir = { 0 }; ++ int r; ++ r = stat(path, &dir); ++ if (r == 0) ++ { ++ if((dir.st_uid == user->pw_uid) && ++ (dir.st_gid == user->pw_gid)) ++ { ++ return PAM_SUCCESS; ++ } ++ else if ((dir.st_uid == 0) && (dir.st_gid == 0)) ++ { ++ result = chown(path, user->pw_uid, user->pw_gid); ++ if (result == 0) ++ return PAM_SUCCESS; ++ } ++ } ++ } ++ ++ if (errno == EPERM) ++ return PAM_PERM_DENIED; ++ ++ return PAM_SYSTEM_ERR; ++} ++ ++PAM_EXTERN int ++pam_sm_open_session (pam_handle_t *handle, int flags, ++ int argc, const char **argv) ++{ ++ int result; ++ char path[255]; ++ struct passwd *user = NULL; ++ ++ assert(handle); ++ ++ result = get_user_data(handle, &user); ++ ++ if (result != PAM_SUCCESS) ++ return result; ++ ++ result = pseudo_session_setup(handle, user, &path[0], sizeof(path)); ++ ++ if (result != PAM_SUCCESS) ++ return result; ++ ++ return env_setup(handle, &path[0]); ++} ++ ++PAM_EXTERN int ++pam_sm_close_session (pam_handle_t *pamh, int flags, ++ int argc, const char **argv) ++{ ++ // In theory we could to some sort of lock/count style checking ++ // here and delete the pseudo XDG directory when it's gone but ++ // there's not much point: ++ return PAM_SUCCESS; ++} +--- /dev/null ++++ b/pam/pam_pseudo_session.8 +@@ -0,0 +1,80 @@ ++'\" t ++.\" Title: pam_pseudo_session ++.\" Author: [see the "AUTHOR" section] ++.\" Date: 27/04/2016 ++.\" Language: English ++.\" ++.TH "PAM_PSEUDO_SESSION" "8" "06/04/2016" "pam_pseudo_session" "pam_pseudo_session" ++.\" ----------------------------------------------------------------- ++.\" * Define some portability stuff ++.\" ----------------------------------------------------------------- ++.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++.\" http://bugs.debian.org/507673 ++.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html ++.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++.ie \n(.g .ds Aq \(aq ++.el .ds Aq ' ++.\" ----------------------------------------------------------------- ++.\" * set default formatting ++.\" ----------------------------------------------------------------- ++.\" disable hyphenation ++.nh ++.\" disable justification (adjust text to left margin only) ++.ad l ++.\" ----------------------------------------------------------------- ++.\" * MAIN CONTENT STARTS HERE * ++.\" ----------------------------------------------------------------- ++.SH "NAME" ++pam_pseudo_session \- create dummy XDG environments for su - style sessions ++.SH "SYNOPSIS" ++.HP \w'\fBpam_pseudo_session\&.so\fR\ 'u ++\fBsession optional pam_pseudo_session\&.so\fR ++.SH "DESCRIPTION" ++.PP ++The pam_pseudo_session module sets up a minimal dummy XDG environment and ++runtime directory to allow commands such as systemd --test to run\&. All ++pseudo-login sessions which use this module share the same dummy XDG ++environment but this is not shared with (or the same as) real logins' XDG ++environments\&. ++.SH "MODULE TYPES PROVIDED" ++.PP ++Only the ++\fBsession\fR ++type is provided\&. ++.SH "RETURN VALUES" ++.PP ++PAM_SUCCESS ++.RS 4 ++The XDG runtime directory was created and the environment populated\&. ++.RE ++.PP ++PAM_PERM_DENIED ++.RS 4 ++The module was unable to create the dummy XDG directory (should never happen ++in normal PAM operation)\&. ++.RE ++.PP ++PAM_SYSTEM_ERR ++.RS 4 ++A non-permission related error occurred - you should check the contents ++of /run/user\&. ++.RE ++.PP ++PAM_USER_UNKNOWN ++.RS 4 ++The user is not known to the system\&. ++.RE ++.SH "FILES" ++.PP ++/run/user/pseudo-xdg/* ++.RS 4 ++The directory where pam_pseudo_session will try to create the dummy XDG tree\&. ++.RE ++.SH "SEE ALSO" ++.PP ++ ++\fBpam.d\fR(5), ++\fBpam\fR(7)\&. ++.SH "AUTHOR" ++.PP ++pam_pseudo_session was written by Vivek Das Mohapatra <vivek@collabora\&.com>\&. diff -Nru shadow-4.2/debian/patches/series shadow-4.2/debian/patches/series --- shadow-4.2/debian/patches/series 2015-11-12 14:24:49.000000000 +0000 +++ shadow-4.2/debian/patches/series 2016-04-22 19:42:26.000000000 +0100 @@ -35,3 +35,4 @@ 1000_configure_userns 1010_vietnamese_translation 1020_fix_user_busy_errors +470_su_pam_xdg_session diff -Nru shadow-4.2/debian/rules shadow-4.2/debian/rules --- shadow-4.2/debian/rules 2014-11-19 20:49:09.000000000 +0000 +++ shadow-4.2/debian/rules 2016-04-27 19:29:26.000000000 +0100 @@ -21,7 +21,7 @@ include /usr/share/cdbs/1/class/autotools.mk # Adds extra options when calling the configure script: -DEB_CONFIGURE_EXTRA_FLAGS := --disable-shared --without-libcrack --mandir=/usr/share/man --with-libpam --enable-shadowgrp --enable-man --disable-account-tools-setuid --with-group-name-max-length=32 --without-acl --without-attr --without-tcb +DEB_CONFIGURE_EXTRA_FLAGS := --without-libcrack --libdir=/lib/$(DEB_HOST_MULTIARCH) --mandir=/usr/share/man --with-libpam --enable-shadowgrp --enable-man --disable-account-tools-setuid --with-group-name-max-length=32 --without-acl --without-attr --without-tcb ifneq ($(DEB_BUILD_GNU_TYPE),$(DEB_HOST_GNU_TYPE)) DEB_CONFIGURE_EXTRA_FLAGS += --host=$(DEB_HOST_GNU_TYPE) endif @@ -37,6 +37,7 @@ endif dh_installpam -p login dh_installpam -p login --name=su + dh_installpam -p login --name=su-l install -c -m 444 debian/login.defs debian/login/etc/login.defs install -c -m 444 debian/securetty.$(DEB_HOST_ARCH_OS) debian/login/etc/securetty dh_lintian -p login