366 lines
11 KiB
C++
366 lines
11 KiB
C++
/* pam_dinit_userservd: the client part of dinit-userservd
|
|
*
|
|
* it connects to its socket and requests logins/logouts,
|
|
* communicating over a rudimentary protocol
|
|
*
|
|
* the PAM session opens a persistent connection, which also
|
|
* takes care of tracking when a session needs ending on the
|
|
* daemon side (once all connections are gone)
|
|
*
|
|
* Copyright 2021 Daniel "q66" Kolesa <q66@chimera-linux.org>
|
|
* License: BSD-2-Clause
|
|
*/
|
|
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
|
|
#include <pwd.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/un.h>
|
|
|
|
#include <security/pam_modules.h>
|
|
#include <security/pam_misc.h>
|
|
|
|
#include "protocol.hh"
|
|
|
|
#define PAMAPI __attribute__((visibility ("default")))
|
|
|
|
static void free_sock(pam_handle_t *, void *data, int) {
|
|
int sock = *static_cast<int *>(data);
|
|
if (sock != -1) {
|
|
close(sock);
|
|
}
|
|
free(data);
|
|
}
|
|
|
|
static bool open_session(
|
|
pam_handle_t *pamh, unsigned int &uid, int argc, char const **argv,
|
|
unsigned int &orlen, char *orbuf, bool &set_rundir
|
|
) {
|
|
int *sock = static_cast<int *>(std::malloc(sizeof(int)));
|
|
if (!sock) {
|
|
return false;
|
|
}
|
|
|
|
#if 0
|
|
/* FIXME: this is problematic with gdm somehow, figure out why */
|
|
bool do_rundir = true;
|
|
|
|
/* overrides */
|
|
for (int i = 0; i < argc; ++i) {
|
|
if (!std::strcmp(argv[i], "norundir")) {
|
|
do_rundir = false;
|
|
}
|
|
}
|
|
#else
|
|
bool do_rundir = false;
|
|
#endif
|
|
|
|
/* blocking socket and a simple protocol */
|
|
*sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
|
|
if (*sock == -1) {
|
|
return false;
|
|
}
|
|
|
|
/* associate the socket with the session */
|
|
if (pam_set_data(
|
|
pamh, "pam_dinit_session", sock, free_sock
|
|
) != PAM_SUCCESS) {
|
|
return false;
|
|
}
|
|
|
|
sockaddr_un saddr;
|
|
std::memset(&saddr, 0, sizeof(saddr));
|
|
|
|
saddr.sun_family = AF_UNIX;
|
|
std::memcpy(saddr.sun_path, DAEMON_SOCK, sizeof(DAEMON_SOCK));
|
|
|
|
char const *puser;
|
|
char const *hdir;
|
|
char const *rdir;
|
|
passwd *pwd;
|
|
int ret, hlen, rlen;
|
|
|
|
auto send_msg = [sock](unsigned int msg) {
|
|
if (write(*sock, &msg, sizeof(msg)) < 0) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
if (pam_get_user(pamh, &puser, nullptr) != PAM_SUCCESS) {
|
|
goto err;
|
|
}
|
|
|
|
pwd = getpwnam(puser);
|
|
if (!pwd) {
|
|
goto err;
|
|
}
|
|
uid = pwd->pw_uid;
|
|
|
|
hdir = pam_getenv(pamh, "HOME");
|
|
if (!hdir || !hdir[0]) {
|
|
hdir = pwd->pw_dir;
|
|
}
|
|
if (!hdir || !hdir[0]) {
|
|
goto err;
|
|
}
|
|
hlen = strlen(hdir);
|
|
if (hlen > DIRLEN_MAX) {
|
|
goto err;
|
|
}
|
|
/* this is verified serverside too but bail out early if needed */
|
|
if (struct stat s; stat(hdir, &s) || !S_ISDIR(s.st_mode)) {
|
|
goto err;
|
|
}
|
|
|
|
/* the other runtime dir manager is expected to ensure that the
|
|
* rundir actually exists by this point (logind does ensure it)
|
|
*/
|
|
rdir = pam_getenv(pamh, "XDG_RUNTIME_DIR");
|
|
if (!rdir) {
|
|
rdir = "";
|
|
}
|
|
rlen = strlen(rdir);
|
|
if (rlen > DIRLEN_MAX) {
|
|
goto err;
|
|
} else if (rlen == 0) {
|
|
set_rundir = do_rundir;
|
|
}
|
|
|
|
if (connect(
|
|
*sock, reinterpret_cast<sockaddr const *>(&saddr), sizeof(saddr)
|
|
) < 0) {
|
|
goto err;
|
|
}
|
|
|
|
if (!send_msg(MSG_START)) {
|
|
goto err;
|
|
}
|
|
/* main message loop */
|
|
{
|
|
unsigned int msg;
|
|
unsigned int state = 0;
|
|
bool sent_uid = false;
|
|
bool sent_gid = false;
|
|
bool sent_hlen = false;
|
|
bool sent_rlen = false;
|
|
bool got_rlen = false;
|
|
char *rbuf = orbuf;
|
|
|
|
auto send_strpkt = [&send_msg](char const *&sdir, int &slen) {
|
|
unsigned int pkt = 0;
|
|
auto psize = MSG_SBYTES(slen);
|
|
std::memcpy(&pkt, sdir, psize);
|
|
pkt <<= MSG_TYPE_BITS;
|
|
pkt |= MSG_DATA;
|
|
if (!send_msg(pkt)) {
|
|
return false;
|
|
}
|
|
sdir += psize;
|
|
slen -= psize;
|
|
return true;
|
|
};
|
|
|
|
for (;;) {
|
|
ret = read(*sock, &msg, sizeof(msg));
|
|
if (ret < 0) {
|
|
goto err;
|
|
}
|
|
switch (state) {
|
|
case 0:
|
|
/* session not established yet */
|
|
if (msg != MSG_OK) {
|
|
goto err;
|
|
}
|
|
/* send uid */
|
|
if (!sent_uid) {
|
|
if (!send_msg(MSG_ENCODE(pwd->pw_uid))) {
|
|
goto err;
|
|
}
|
|
sent_uid = true;
|
|
break;
|
|
}
|
|
/* send gid */
|
|
if (!sent_gid) {
|
|
if (!send_msg(MSG_ENCODE(pwd->pw_gid))) {
|
|
goto err;
|
|
}
|
|
sent_gid = true;
|
|
break;
|
|
}
|
|
/* send homedir len */
|
|
if (!sent_hlen) {
|
|
if (!send_msg(MSG_ENCODE(hlen))) {
|
|
goto err;
|
|
}
|
|
sent_hlen = true;
|
|
break;
|
|
}
|
|
/* send a piece of homedir */
|
|
if (hlen) {
|
|
if (!send_strpkt(hdir, hlen)) {
|
|
goto err;
|
|
}
|
|
break;
|
|
}
|
|
/* send rundir len */
|
|
if (!sent_rlen) {
|
|
auto srlen = rlen;
|
|
if (!srlen && !do_rundir) {
|
|
srlen = DIRLEN_MAX + 1;
|
|
}
|
|
if (!send_msg(MSG_ENCODE(srlen))) {
|
|
goto err;
|
|
}
|
|
sent_rlen = true;
|
|
break;
|
|
}
|
|
/* send a piece of rundir */
|
|
if (rlen) {
|
|
if (!send_strpkt(rdir, rlen)) {
|
|
goto err;
|
|
}
|
|
break;
|
|
}
|
|
/* send clientside OK */
|
|
state = msg;
|
|
if (!send_msg(MSG_OK)) {
|
|
goto err;
|
|
}
|
|
break;
|
|
case MSG_OK:
|
|
/* if started, get the rundir back; else block */
|
|
if ((msg == MSG_OK_DONE) || (msg == MSG_OK_WAIT)) {
|
|
state = msg;
|
|
if ((msg == MSG_OK_DONE) && !send_msg(MSG_REQ_RLEN)) {
|
|
goto err;
|
|
}
|
|
continue;
|
|
}
|
|
/* bad message */
|
|
goto err;
|
|
case MSG_OK_WAIT:
|
|
/* if we previously waited and now got another message,
|
|
* it means either an error or that the system is now
|
|
* fully ready
|
|
*/
|
|
if (msg == MSG_OK_DONE) {
|
|
state = msg;
|
|
if (!send_msg(MSG_REQ_RLEN)) {
|
|
goto err;
|
|
}
|
|
continue;
|
|
}
|
|
/* bad message */
|
|
goto err;
|
|
case MSG_OK_DONE: {
|
|
if ((msg & MSG_TYPE_MASK) != MSG_DATA) {
|
|
goto err;
|
|
}
|
|
/* after MSG_OK_DONE, we should receive the runtime dir
|
|
* length first; if zero, it means we are completely done
|
|
*/
|
|
msg >>= MSG_TYPE_BITS;
|
|
if (!got_rlen) {
|
|
if (msg == 0) {
|
|
orlen = 0;
|
|
return true;
|
|
} else if (msg > DIRLEN_MAX) {
|
|
goto err;
|
|
}
|
|
got_rlen = true;
|
|
rlen = int(msg);
|
|
orlen = msg;
|
|
if (!send_msg(MSG_ENCODE_AUX(rlen, MSG_REQ_RDATA))) {
|
|
goto err;
|
|
}
|
|
continue;
|
|
}
|
|
/* we are receiving the string... */
|
|
int pkts = MSG_SBYTES(rlen);
|
|
std::memcpy(rbuf, &msg, pkts);
|
|
rbuf += pkts;
|
|
rlen -= pkts;
|
|
if (rlen == 0) {
|
|
/* we have received the whole thing, terminate */
|
|
*rbuf = '\0';
|
|
return true;
|
|
}
|
|
if (!send_msg(MSG_ENCODE_AUX(rlen, MSG_REQ_RDATA))) {
|
|
goto err;
|
|
}
|
|
/* keep receiving pieces */
|
|
continue;
|
|
}
|
|
default:
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
err:
|
|
close(*sock);
|
|
*sock = -1;
|
|
return false;
|
|
}
|
|
|
|
extern "C" PAMAPI int pam_sm_open_session(
|
|
pam_handle_t *pamh, int, int argc, char const **argv
|
|
) {
|
|
unsigned int uid, rlen = 0;
|
|
bool set_rundir = false;
|
|
/* potential rundir we are managing */
|
|
char rdir[DIRLEN_MAX + 1];
|
|
if (!open_session(pamh, uid, argc, argv, rlen, rdir, set_rundir)) {
|
|
return PAM_SESSION_ERR;
|
|
}
|
|
if (rlen) {
|
|
char const dpfx[] = "DBUS_SESSION_BUS_ADDRESS=unix:path=";
|
|
char buf[sizeof(rdir) + sizeof(dpfx) + 4];
|
|
|
|
/* try exporting a dbus session bus variable */
|
|
std::snprintf(buf, sizeof(buf), "%s%s/bus", dpfx, rdir);
|
|
|
|
struct stat sbuf;
|
|
if (!lstat(strchr(buf, '/'), &sbuf) && S_ISSOCK(sbuf.st_mode)) {
|
|
if (pam_putenv(pamh, buf) != PAM_SUCCESS) {
|
|
return PAM_SESSION_ERR;
|
|
}
|
|
}
|
|
|
|
if (!set_rundir) {
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
/* set rundir too if needed */
|
|
if (pam_misc_setenv(pamh, "XDG_RUNTIME_DIR", rdir, 1) != PAM_SUCCESS) {
|
|
return PAM_SESSION_ERR;
|
|
}
|
|
}
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
extern "C" PAMAPI int pam_sm_close_session(
|
|
pam_handle_t *pamh, int, int, char const **
|
|
) {
|
|
void const *data;
|
|
/* there is nothing we can do here */
|
|
if (pam_get_data(pamh, "pam_dinit_session", &data) != PAM_SUCCESS) {
|
|
return PAM_SUCCESS;
|
|
}
|
|
int sock = *static_cast<int const *>(data);
|
|
if (sock < 0) {
|
|
return PAM_SUCCESS;
|
|
}
|
|
/* close the session */
|
|
close(sock);
|
|
return PAM_SUCCESS;
|
|
}
|