mirror of
https://github.com/fairyglade/ly.git
synced 2025-12-20 19:24:53 +00:00
Implements utmp audit required by policykit. This commit also flattens the pidtree for the DM by starting the display environment directly in the first fork which already should have the environment ready for this purpose. This is with the exception of xorg environments where this can't be done that easily.
674 lines
11 KiB
C
674 lines
11 KiB
C
#include "dragonfail.h"
|
|
#include "termbox.h"
|
|
|
|
#include "inputs.h"
|
|
#include "draw.h"
|
|
#include "utils.h"
|
|
#include "config.h"
|
|
#include "login.h"
|
|
|
|
#include <errno.h>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <security/pam_appl.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <utmp.h>
|
|
#include <xcb/xcb.h>
|
|
|
|
int get_free_display()
|
|
{
|
|
char xlock[1024];
|
|
u8 i;
|
|
|
|
for (i = 0; i < 200; ++i)
|
|
{
|
|
snprintf(xlock, 1024, "/tmp/.X%d-lock", i);
|
|
|
|
if (access(xlock, F_OK) == -1)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return i;
|
|
}
|
|
|
|
void reset_terminal(struct passwd* pwd)
|
|
{
|
|
pid_t pid = fork();
|
|
|
|
if (pid == 0)
|
|
{
|
|
execl(pwd->pw_shell, pwd->pw_shell, "-c", config.term_reset_cmd, NULL);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
int status;
|
|
waitpid(pid, &status, 0);
|
|
}
|
|
|
|
int login_conv(
|
|
int num_msg,
|
|
const struct pam_message** msg,
|
|
struct pam_response** resp,
|
|
void* appdata_ptr)
|
|
{
|
|
*resp = calloc(num_msg, sizeof (struct pam_response));
|
|
|
|
if (*resp == NULL)
|
|
{
|
|
return PAM_BUF_ERR;
|
|
}
|
|
|
|
char* username;
|
|
char* password;
|
|
int ok = PAM_SUCCESS;
|
|
int i;
|
|
|
|
for (i = 0; i < num_msg; ++i)
|
|
{
|
|
switch (msg[i]->msg_style)
|
|
{
|
|
case PAM_PROMPT_ECHO_ON:
|
|
{
|
|
username = ((char**) appdata_ptr)[0];
|
|
(*resp)[i].resp = strdup(username);
|
|
break;
|
|
}
|
|
case PAM_PROMPT_ECHO_OFF:
|
|
{
|
|
password = ((char**) appdata_ptr)[1];
|
|
(*resp)[i].resp = strdup(password);
|
|
break;
|
|
}
|
|
case PAM_ERROR_MSG:
|
|
{
|
|
ok = PAM_CONV_ERR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ok != PAM_SUCCESS)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ok != PAM_SUCCESS)
|
|
{
|
|
for (i = 0; i < num_msg; ++i)
|
|
{
|
|
if ((*resp)[i].resp == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
free((*resp)[i].resp);
|
|
(*resp)[i].resp = NULL;
|
|
}
|
|
|
|
free(*resp);
|
|
*resp = NULL;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
void pam_diagnose(int error, struct term_buf* buf)
|
|
{
|
|
switch (error)
|
|
{
|
|
case PAM_ACCT_EXPIRED:
|
|
{
|
|
buf->info_line = lang.err_pam_acct_expired;
|
|
break;
|
|
}
|
|
case PAM_AUTH_ERR:
|
|
{
|
|
buf->info_line = lang.err_pam_auth;
|
|
break;
|
|
}
|
|
case PAM_AUTHINFO_UNAVAIL:
|
|
{
|
|
buf->info_line = lang.err_pam_authinfo_unavail;
|
|
break;
|
|
}
|
|
case PAM_BUF_ERR:
|
|
{
|
|
buf->info_line = lang.err_pam_buf;
|
|
break;
|
|
}
|
|
case PAM_CRED_ERR:
|
|
{
|
|
buf->info_line = lang.err_pam_cred_err;
|
|
break;
|
|
}
|
|
case PAM_CRED_EXPIRED:
|
|
{
|
|
buf->info_line = lang.err_pam_cred_expired;
|
|
break;
|
|
}
|
|
case PAM_CRED_INSUFFICIENT:
|
|
{
|
|
buf->info_line = lang.err_pam_cred_insufficient;
|
|
break;
|
|
}
|
|
case PAM_CRED_UNAVAIL:
|
|
{
|
|
buf->info_line = lang.err_pam_cred_unavail;
|
|
break;
|
|
}
|
|
case PAM_MAXTRIES:
|
|
{
|
|
buf->info_line = lang.err_pam_maxtries;
|
|
break;
|
|
}
|
|
case PAM_NEW_AUTHTOK_REQD:
|
|
{
|
|
buf->info_line = lang.err_pam_authok_reqd;
|
|
break;
|
|
}
|
|
case PAM_PERM_DENIED:
|
|
{
|
|
buf->info_line = lang.err_pam_perm_denied;
|
|
break;
|
|
}
|
|
case PAM_SESSION_ERR:
|
|
{
|
|
buf->info_line = lang.err_pam_session;
|
|
break;
|
|
}
|
|
case PAM_SYSTEM_ERR:
|
|
{
|
|
buf->info_line = lang.err_pam_sys;
|
|
break;
|
|
}
|
|
case PAM_USER_UNKNOWN:
|
|
{
|
|
buf->info_line = lang.err_pam_user_unknown;
|
|
break;
|
|
}
|
|
case PAM_ABORT:
|
|
default:
|
|
{
|
|
buf->info_line = lang.err_pam_abort;
|
|
break;
|
|
}
|
|
}
|
|
|
|
dgn_throw(DGN_PAM);
|
|
}
|
|
|
|
void env_init(struct passwd* pwd, const char* display_name)
|
|
{
|
|
extern char** environ;
|
|
char* term = getenv("TERM");
|
|
char* lang = getenv("LANG");
|
|
// clean env
|
|
environ[0] = NULL;
|
|
|
|
if (term != NULL)
|
|
{
|
|
setenv("TERM", term, 1);
|
|
}
|
|
else
|
|
{
|
|
setenv("TERM", "linux", 1);
|
|
}
|
|
|
|
setenv("HOME", pwd->pw_dir, 1);
|
|
setenv("PWD", pwd->pw_dir, 1);
|
|
setenv("SHELL", pwd->pw_shell, 1);
|
|
setenv("USER", pwd->pw_name, 1);
|
|
setenv("LOGNAME", pwd->pw_name, 1);
|
|
setenv("DISPLAY", display_name, 1);
|
|
setenv("LANG", lang, 1);
|
|
|
|
// Set PATH if specified in the configuration
|
|
if (strlen(config.path))
|
|
{
|
|
int ok = setenv("PATH", config.path, 1);
|
|
|
|
if (ok != 0)
|
|
{
|
|
dgn_throw(DGN_PATH);
|
|
}
|
|
}
|
|
}
|
|
|
|
void env_xdg(const char* tty_id, const enum display_server display_server)
|
|
{
|
|
char user[15];
|
|
snprintf(user, 15, "/run/user/%d", getuid());
|
|
setenv("XDG_RUNTIME_DIR", user, 0);
|
|
setenv("XDG_SESSION_CLASS", "user", 0);
|
|
setenv("XDG_SEAT", "seat0", 0);
|
|
setenv("XDG_VTNR", tty_id, 0);
|
|
|
|
switch (display_server)
|
|
{
|
|
case DS_WAYLAND:
|
|
{
|
|
setenv("XDG_SESSION_TYPE", "wayland", 0);
|
|
break;
|
|
}
|
|
case DS_SHELL:
|
|
{
|
|
setenv("XDG_SESSION_TYPE", "tty", 0);
|
|
break;
|
|
}
|
|
case DS_XINITRC:
|
|
case DS_XORG:
|
|
{
|
|
setenv("XDG_SESSION_TYPE", "x11", 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void add_utmp_entry(
|
|
struct utmp *entry,
|
|
char *username,
|
|
pid_t display_pid
|
|
) {
|
|
entry->ut_type = USER_PROCESS;
|
|
entry->ut_pid = display_pid;
|
|
strcpy(entry->ut_line, ttyname(STDIN_FILENO) + strlen("/dev/"));
|
|
|
|
/* only correct for ptys named /dev/tty[pqr][0-9a-z] */
|
|
strcpy(entry->ut_id, ttyname(STDIN_FILENO) + strlen("/dev/tty"));
|
|
|
|
time((long int *) &entry->ut_time);
|
|
|
|
strncpy(entry->ut_user, username, UT_NAMESIZE);
|
|
memset(entry->ut_host, 0, UT_HOSTSIZE);
|
|
entry->ut_addr = 0;
|
|
setutent();
|
|
|
|
pututline(entry);
|
|
}
|
|
|
|
void remove_utmp_entry(struct utmp *entry) {
|
|
entry->ut_type = DEAD_PROCESS;
|
|
memset(entry->ut_line, 0, UT_LINESIZE);
|
|
entry->ut_time = 0;
|
|
memset(entry->ut_user, 0, UT_NAMESIZE);
|
|
setutent();
|
|
pututline(entry);
|
|
endutent();
|
|
}
|
|
|
|
void xauth(const char* display_name, const char* shell, const char* home)
|
|
{
|
|
char xauthority[256];
|
|
snprintf(xauthority, 256, "%s/%s", home, ".lyxauth");
|
|
setenv("XAUTHORITY", xauthority, 1);
|
|
|
|
FILE* fp = fopen(xauthority, "ab+");
|
|
|
|
if (fp != NULL)
|
|
{
|
|
fclose(fp);
|
|
}
|
|
|
|
pid_t pid = fork();
|
|
|
|
if (pid == 0)
|
|
{
|
|
char cmd[1024];
|
|
snprintf(
|
|
cmd,
|
|
1024,
|
|
"%s add %s . `%s`",
|
|
config.xauth_cmd,
|
|
display_name,
|
|
config.mcookie_cmd);
|
|
execl(shell, shell, "-c", cmd, NULL);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
int status;
|
|
waitpid(pid, &status, 0);
|
|
}
|
|
|
|
void xorg(
|
|
struct passwd* pwd,
|
|
const char* display_name,
|
|
const char* vt,
|
|
const char* desktop_cmd)
|
|
{
|
|
// generate xauthority file
|
|
xauth(display_name, pwd->pw_shell, pwd->pw_dir);
|
|
|
|
// start xorg
|
|
pid_t pid = fork();
|
|
|
|
if (pid == 0)
|
|
{
|
|
char x_cmd[1024];
|
|
snprintf(
|
|
x_cmd,
|
|
1024,
|
|
"%s %s %s",
|
|
config.x_cmd,
|
|
display_name,
|
|
vt);
|
|
execl(pwd->pw_shell, pwd->pw_shell, "-c", x_cmd, NULL);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
int ok;
|
|
xcb_connection_t* xcb;
|
|
|
|
do
|
|
{
|
|
xcb = xcb_connect(NULL, NULL);
|
|
ok = xcb_connection_has_error(xcb);
|
|
kill(pid, 0);
|
|
}
|
|
while((ok != 0) && (errno != ESRCH));
|
|
|
|
if (ok != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pid_t xorg_pid = fork();
|
|
|
|
if (xorg_pid == 0)
|
|
{
|
|
char de_cmd[1024];
|
|
snprintf(
|
|
de_cmd,
|
|
1024,
|
|
"%s %s",
|
|
config.x_cmd_setup,
|
|
desktop_cmd);
|
|
execl(pwd->pw_shell, pwd->pw_shell, "-c", de_cmd, NULL);
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
int status;
|
|
waitpid(xorg_pid, &status, 0);
|
|
xcb_disconnect(xcb);
|
|
kill(pid, 0);
|
|
|
|
if (errno != ESRCH)
|
|
{
|
|
kill(pid, SIGTERM);
|
|
waitpid(pid, &status, 0);
|
|
}
|
|
}
|
|
|
|
void wayland(
|
|
struct passwd* pwd,
|
|
const char* desktop_cmd)
|
|
{
|
|
|
|
char cmd[1024];
|
|
snprintf(cmd, 1024, "%s %s", config.wayland_cmd, desktop_cmd);
|
|
execl(pwd->pw_shell, pwd->pw_shell, "-c", cmd, NULL);
|
|
}
|
|
|
|
void shell(struct passwd* pwd)
|
|
{
|
|
const char* pos = strrchr(pwd->pw_shell, '/');
|
|
char args[1024];
|
|
args[0] = '-';
|
|
|
|
if (pos != NULL)
|
|
{
|
|
pos = pos + 1;
|
|
}
|
|
else
|
|
{
|
|
pos = pwd->pw_shell;
|
|
}
|
|
|
|
strncpy(args + 1, pos, 1024);
|
|
execl(pwd->pw_shell, args, NULL);
|
|
}
|
|
|
|
// pam_do performs the pam action specified in pam_action
|
|
// on pam_action fail, call diagnose and end pam session
|
|
int pam_do(
|
|
int (pam_action)(struct pam_handle *, int),
|
|
struct pam_handle *handle,
|
|
int flags,
|
|
struct term_buf *buf)
|
|
{
|
|
int status = pam_action(handle, flags);
|
|
|
|
if (status != PAM_SUCCESS) {
|
|
pam_diagnose(status, buf);
|
|
pam_end(handle, status);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
void auth(
|
|
struct desktop* desktop,
|
|
struct text* login,
|
|
struct text* password,
|
|
struct term_buf* buf)
|
|
{
|
|
int ok;
|
|
|
|
// open pam session
|
|
const char* creds[2] = {login->text, password->text};
|
|
struct pam_conv conv = {login_conv, creds};
|
|
struct pam_handle* handle;
|
|
|
|
ok = pam_start(config.service_name, NULL, &conv, &handle);
|
|
|
|
if (ok != PAM_SUCCESS)
|
|
{
|
|
pam_diagnose(ok, buf);
|
|
pam_end(handle, ok);
|
|
return;
|
|
}
|
|
|
|
ok = pam_do(pam_authenticate, handle, 0, buf);
|
|
|
|
if (ok != PAM_SUCCESS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ok = pam_do(pam_acct_mgmt, handle, 0, buf);
|
|
|
|
if (ok != PAM_SUCCESS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ok = pam_do(pam_setcred, handle, PAM_ESTABLISH_CRED, buf);
|
|
|
|
if (ok != PAM_SUCCESS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ok = pam_do(pam_open_session, handle, 0, buf);
|
|
|
|
if (ok != PAM_SUCCESS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// clear the credentials
|
|
input_text_free(password);
|
|
input_text(password, config.max_password_len);
|
|
|
|
// get passwd structure
|
|
struct passwd* pwd = getpwnam(login->text);
|
|
endpwent();
|
|
|
|
if (pwd == NULL)
|
|
{
|
|
dgn_throw(DGN_PWNAM);
|
|
pam_end(handle, ok);
|
|
return;
|
|
}
|
|
|
|
// set user shell
|
|
if (pwd->pw_shell[0] == '\0')
|
|
{
|
|
setusershell();
|
|
|
|
char* shell = getusershell();
|
|
|
|
if (shell != NULL)
|
|
{
|
|
strcpy(pwd->pw_shell, shell);
|
|
}
|
|
|
|
endusershell();
|
|
}
|
|
|
|
// restore regular terminal mode
|
|
tb_clear();
|
|
tb_present();
|
|
tb_shutdown();
|
|
|
|
// start desktop environment
|
|
pid_t pid = fork();
|
|
|
|
if (pid == 0)
|
|
{
|
|
// set user info
|
|
ok = initgroups(pwd->pw_name, pwd->pw_gid);
|
|
|
|
if (ok != 0)
|
|
{
|
|
dgn_throw(DGN_USER_INIT);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ok = setgid(pwd->pw_gid);
|
|
|
|
if (ok != 0)
|
|
{
|
|
dgn_throw(DGN_USER_GID);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ok = setuid(pwd->pw_uid);
|
|
|
|
if (ok != 0)
|
|
{
|
|
dgn_throw(DGN_USER_UID);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// get a display
|
|
int display_id = get_free_display();
|
|
char display_name[3];
|
|
char tty_id [3];
|
|
char vt[5];
|
|
|
|
snprintf(display_name, 3, ":%d", display_id);
|
|
snprintf(tty_id, 3, "%d", config.tty);
|
|
snprintf(vt, 5, "vt%d", config.tty);
|
|
|
|
// set env
|
|
env_init(pwd, display_name);
|
|
|
|
if (dgn_catch())
|
|
{
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// add pam variables
|
|
char** env = pam_getenvlist(handle);
|
|
|
|
for (u16 i = 0; env && env[i]; ++i)
|
|
{
|
|
putenv(env[i]);
|
|
}
|
|
|
|
// add xdg variables
|
|
env_xdg(tty_id, desktop->display_server[desktop->cur]);
|
|
|
|
// execute
|
|
int ok = chdir(pwd->pw_dir);
|
|
|
|
if (ok != 0)
|
|
{
|
|
dgn_throw(DGN_CHDIR);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
reset_terminal(pwd);
|
|
switch (desktop->display_server[desktop->cur])
|
|
{
|
|
case DS_WAYLAND:
|
|
{
|
|
wayland(pwd, desktop->cmd[desktop->cur]);
|
|
break;
|
|
}
|
|
case DS_SHELL:
|
|
{
|
|
shell(pwd);
|
|
break;
|
|
}
|
|
case DS_XINITRC:
|
|
case DS_XORG:
|
|
{
|
|
xorg(pwd, display_name, vt, desktop->cmd[desktop->cur]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
exit(EXIT_SUCCESS);
|
|
}
|
|
|
|
// add utmp audit
|
|
struct utmp entry;
|
|
add_utmp_entry(&entry, pwd->pw_name, pid);
|
|
|
|
// wait for the session to stop
|
|
int status;
|
|
waitpid(pid, &status, 0);
|
|
remove_utmp_entry(&entry);
|
|
|
|
reset_terminal(pwd);
|
|
|
|
// reinit termbox
|
|
tb_init();
|
|
tb_select_output_mode(TB_OUTPUT_NORMAL);
|
|
|
|
// reload the desktop environment list on logout
|
|
input_desktop_free(desktop);
|
|
input_desktop(desktop);
|
|
desktop_load(desktop);
|
|
|
|
// close pam session
|
|
ok = pam_do(pam_close_session, handle, 0, buf);
|
|
|
|
if (ok != PAM_SUCCESS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ok = pam_do(pam_setcred, handle, PAM_DELETE_CRED, buf);
|
|
|
|
if (ok != PAM_SUCCESS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ok = pam_end(handle, 0);
|
|
|
|
if (ok != PAM_SUCCESS)
|
|
{
|
|
pam_diagnose(ok, buf);
|
|
}
|
|
} |