#include "dm.h"
#include "dm_auth.h"
#include "dm_error.h"
#include "greet.h"
#include <X11/Xlib.h>
#include <signal.h>
#include <X11/Xatom.h>
#include <X11/Xmu/Error.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
#include <grp.h>
#ifdef AIXV3
# include <usersec.h>
#endif
#ifdef SECURE_RPC
# include <rpc/rpc.h>
# include <rpc/key_prot.h>
extern int key_setnet(struct key_netstarg *arg);
#endif
#ifdef K5AUTH
# include <krb5/krb5.h>
#endif
#ifndef GREET_USER_STATIC
# include <dlfcn.h>
# ifndef RTLD_NOW
# define RTLD_NOW 1
# endif
#endif
static int runAndWait (char **args, char **environ);
#if defined(CSRG_BASED) || defined(__osf__) || defined(__DARWIN__) || defined(__QNXNTO__) || defined(sun) || defined(__GLIBC__)
# include <sys/types.h>
# include <grp.h>
#else
extern void setgrent(void);
extern struct group *getgrent(void);
extern void endgrent(void);
#endif
#ifdef USESHADOW
# if defined(SVR4)
# include <shadow.h>
# else
extern struct spwd *getspnam(GETSPNAM_ARGS);
extern void endspent(void);
# endif
#endif
#if defined(CSRG_BASED) || defined(__GLIBC__) || defined(USL)
# include <pwd.h>
# include <unistd.h>
#else
extern struct passwd *getpwnam(GETPWNAM_ARGS);
# ifdef linux
extern void endpwent(void);
# endif
extern char *crypt(CRYPT_ARGS);
#endif
#ifdef USE_PAM
pam_handle_t **
thepamhp(void)
{
static pam_handle_t *pamh = NULL;
return &pamh;
}
pam_handle_t *
thepamh(void)
{
pam_handle_t **pamhp;
pamhp = thepamhp();
if (pamhp)
return *pamhp;
else
return NULL;
}
#endif
static struct dlfuncs dlfuncs = {
PingServer,
SessionPingFailed,
Debug,
RegisterCloseOnFork,
SecureDisplay,
UnsecureDisplay,
ClearCloseOnFork,
SetupDisplay,
LogError,
SessionExit,
DeleteXloginResources,
source,
defaultEnv,
setEnv,
putEnv,
parseArgs,
printEnv,
systemEnv,
LogOutOfMem,
setgrent,
getgrent,
endgrent,
#ifdef USESHADOW
getspnam,
# ifndef QNX4
endspent,
# endif
#endif
getpwnam,
#if defined(linux) || defined(__GLIBC__)
endpwent,
#endif
crypt,
#ifdef USE_PAM
thepamhp,
#endif
};
static Bool StartClient(
struct verify_info *verify,
struct display *d,
int *pidp,
char *name,
char *passwd);
static int clientPid;
static struct greet_info greet;
static struct verify_info verify;
static Jmp_buf abortSession;
static SIGVAL
catchTerm (int n)
{
Longjmp (abortSession, 1);
}
static Jmp_buf pingTime;
static SIGVAL
catchAlrm (int n)
{
Longjmp (pingTime, 1);
}
static Jmp_buf tenaciousClient;
static SIGVAL
waitAbort (int n)
{
Longjmp (tenaciousClient, 1);
}
#if defined(_POSIX_SOURCE) || defined(SYSV) || defined(SVR4)
# define killpg(pgrp, sig) kill(-(pgrp), sig)
#endif
static void
AbortClient (int pid)
{
int sig = SIGTERM;
volatile int i;
int retId;
for (i = 0; i < 4; i++) {
if (killpg (pid, sig) == -1) {
switch (errno) {
case EPERM:
LogError ("xdm can't kill client\n");
case EINVAL:
case ESRCH:
return;
}
}
if (!Setjmp (tenaciousClient)) {
(void) Signal (SIGALRM, waitAbort);
(void) alarm ((unsigned) 10);
retId = wait ((waitType *) 0);
(void) alarm ((unsigned) 0);
(void) Signal (SIGALRM, SIG_DFL);
if (retId == pid)
break;
} else
(void) Signal (SIGALRM, SIG_DFL);
sig = SIGKILL;
}
}
void
SessionPingFailed (struct display *d)
{
if (clientPid > 1) {
AbortClient (clientPid);
source (verify.systemEnviron, d->reset);
}
SessionExit (d, RESERVER_DISPLAY, TRUE);
}
static int
IOErrorHandler (Display *dpy)
{
LogError ("fatal IO error %d (%s)\n", errno, _SysErrorMsg(errno));
exit(RESERVER_DISPLAY);
return 0;
}
static int
ErrorHandler(Display *dpy, XErrorEvent *event)
{
LogError ("X error\n");
if (XmuPrintDefaultErrorMessage (dpy, event, stderr) == 0) return 0;
exit(UNMANAGE_DISPLAY);
}
void
ManageSession (struct display *d)
{
static int pid = 0;
Display *dpy;
greet_user_rtn greet_stat;
static GreetUserProc greet_user_proc = NULL;
#ifndef GREET_USER_STATIC
void *greet_lib_handle;
#endif
Debug ("ManageSession %s\n", d->name);
(void)XSetIOErrorHandler(IOErrorHandler);
(void)XSetErrorHandler(ErrorHandler);
#ifndef HAS_SETPROCTITLE
SetTitle(d->name, (char *) 0);
#else
setproctitle("%s", d->name);
#endif
LoadXloginResources (d);
#ifdef GREET_USER_STATIC
greet_user_proc = GreetUser;
#else
Debug ("ManageSession: loading greeter library %s\n", greeterLib);
greet_lib_handle = dlopen(greeterLib, RTLD_NOW);
if (greet_lib_handle != NULL)
greet_user_proc = (GreetUserProc)dlsym(greet_lib_handle, "GreetUser");
if (greet_user_proc == NULL) {
LogError ("%s while loading %s\n", dlerror(), greeterLib);
exit(UNMANAGE_DISPLAY);
}
#endif
verify.version = 1;
greet.version = 1;
greet_stat = (*greet_user_proc)(d, &dpy, &verify, &greet, &dlfuncs);
if (greet_stat == Greet_Success) {
clientPid = 0;
if (!Setjmp (abortSession)) {
(void) Signal (SIGTERM, catchTerm);
if (StartClient (&verify, d, &clientPid, greet.name, greet.password)) {
Debug ("Client Started\n");
#ifndef GREET_USER_STATIC
dlclose(greet_lib_handle);
#endif
for (;;) {
if (d->pingInterval) {
if (!Setjmp (pingTime)) {
(void) Signal (SIGALRM, catchAlrm);
(void) alarm (d->pingInterval * 60);
pid = wait ((waitType *) 0);
(void) alarm (0);
} else {
(void) alarm (0);
if (!PingServer (d, (Display *) NULL))
SessionPingFailed (d);
}
} else {
pid = wait ((waitType *) 0);
}
if (pid == clientPid)
break;
}
} else {
LogError ("session start failed\n");
}
} else {
AbortClient (clientPid);
}
}
Debug ("Source reset program %s\n", d->reset);
source (verify.systemEnviron, d->reset);
SessionExit (d, OBEYSESS_DISPLAY, TRUE);
}
void
LoadXloginResources (struct display *d)
{
char **args;
char **env = 0;
if (d->resources[0] && access (d->resources, 4) == 0) {
env = systemEnv (d, (char *) 0, (char *) 0);
args = parseArgs ((char **) 0, d->xrdb);
args = parseArgs (args, d->resources);
Debug ("Loading resource file: %s\n", d->resources);
(void) runAndWait (args, env);
freeArgs (args);
freeEnv (env);
}
}
void
SetupDisplay (struct display *d)
{
char **env = 0;
if (d->setup && d->setup[0]) {
env = systemEnv (d, (char *) 0, (char *) 0);
(void) source (env, d->setup);
freeEnv (env);
}
}
void
DeleteXloginResources (struct display *d, Display *dpy)
{
int i;
Atom prop = XInternAtom(dpy, "SCREEN_RESOURCES", True);
XDeleteProperty(dpy, RootWindow (dpy, 0), XA_RESOURCE_MANAGER);
if (prop) {
for (i = ScreenCount(dpy); --i >= 0; )
XDeleteProperty(dpy, RootWindow (dpy, i), prop);
}
}
static Jmp_buf syncJump;
static SIGVAL
syncTimeout (int n)
{
Longjmp (syncJump, 1);
}
void
SecureDisplay (struct display *d, Display *dpy)
{
Debug ("SecureDisplay %s\n", d->name);
(void) Signal (SIGALRM, syncTimeout);
if (Setjmp (syncJump)) {
LogError ("WARNING: display %s could not be secured\n",
d->name);
SessionExit (d, RESERVER_DISPLAY, FALSE);
}
(void) alarm ((unsigned) d->grabTimeout);
Debug ("Before XGrabServer %s\n", d->name);
XGrabServer (dpy);
if (XGrabKeyboard (dpy, DefaultRootWindow (dpy), True, GrabModeAsync,
GrabModeAsync, CurrentTime) != GrabSuccess) {
(void) alarm (0);
(void) Signal (SIGALRM, SIG_DFL);
LogError ("WARNING: keyboard on display %s could not be secured\n",
d->name);
SessionExit (d, RESERVER_DISPLAY, FALSE);
}
Debug ("XGrabKeyboard succeeded %s\n", d->name);
(void) alarm (0);
(void) Signal (SIGALRM, SIG_DFL);
pseudoReset (dpy);
if (!d->grabServer) {
XUngrabServer (dpy);
XSync (dpy, 0);
}
Debug ("done secure %s\n", d->name);
}
void
UnsecureDisplay (struct display *d, Display *dpy)
{
Debug ("Unsecure display %s\n", d->name);
if (d->grabServer) {
XUngrabServer (dpy);
XSync (dpy, 0);
}
}
void
SessionExit (struct display *d, int status, int removeAuth)
{
#ifdef USE_PAM
pam_handle_t *pamh = thepamh();
#endif
#ifdef USE_PAM
if (pamh) {
pam_close_session(pamh, 0);
pam_end(pamh, PAM_SUCCESS);
pamh = NULL;
}
#endif
if (d->serverPid >= 2 && d->resetSignal)
kill (d->serverPid, d->resetSignal);
else
ResetServer (d);
if (removeAuth) {
setgid (verify.gid);
setuid (verify.uid);
RemoveUserAuthorization (d, &verify);
#ifdef K5AUTH
{
krb5_error_code code;
krb5_ccache ccache;
code = Krb5DisplayCCache(d->name, &ccache);
if (code)
LogError ("%s while getting Krb5 ccache to destroy\n",
error_message(code));
else {
code = krb5_cc_destroy(ccache);
if (code) {
if (code == KRB5_FCC_NOFILE) {
Debug ("No Kerberos ccache file found to destroy\n");
} else
LogError ("%s while destroying Krb5 credentials cache\n",
error_message(code));
} else
Debug ("Kerberos ccache destroyed\n");
krb5_cc_close(ccache);
}
}
#endif
}
Debug ("Display %s exiting with status %d\n", d->name, status);
exit (status);
}
static Bool
StartClient (
struct verify_info *verify,
struct display *d,
int *pidp,
char *name,
char *passwd)
{
char **f, *home;
char *failsafeArgv[2];
int pid;
#ifdef HAS_SETUSERCONTEXT
struct passwd* pwd;
#endif
#ifdef USE_PAM
pam_handle_t *pamh = thepamh ();
int pam_error;
#endif
if (verify->argv) {
Debug ("StartSession %s: ", verify->argv[0]);
for (f = verify->argv; *f; f++)
Debug ("%s ", *f);
Debug ("; ");
}
if (verify->userEnviron) {
for (f = verify->userEnviron; *f; f++)
Debug ("%s ", *f);
Debug ("\n");
}
#ifdef USE_PAM
if (pamh) pam_open_session(pamh, 0);
#endif
switch (pid = fork ()) {
case 0:
CleanUpChild ();
#ifdef XDMCP
DestroyWellKnownSockets();
#endif
#ifdef USE_PAM
if (pamh) {
long i;
char **pam_env = pam_getenvlist(pamh);
for(i = 0; pam_env && pam_env[i]; i++) {
verify->userEnviron = putEnv(pam_env[i], verify->userEnviron);
}
}
#endif
#ifndef AIXV3
#ifndef HAS_SETUSERCONTEXT
if (setgid(verify->gid) < 0) {
LogError ("setgid %d (user \"%s\") failed, errno=%d\n",
verify->gid, name, errno);
return (0);
}
#if defined(BSD) && (BSD >= 199103)
if (setlogin(name) < 0) {
LogError ("setlogin for \"%s\" failed, errno=%d", name, errno);
return(0);
}
#endif
#ifndef QNX4
if (initgroups(name, verify->gid) < 0) {
LogError ("initgroups for \"%s\" failed, errno=%d\n", name, errno);
return (0);
}
#endif
#ifdef USE_PAM
if (pamh) {
pam_error = pam_setcred (pamh, PAM_ESTABLISH_CRED);
if (pam_error != PAM_SUCCESS) {
LogError ("pam_setcred for \"%s\" failed: %s\n",
name, pam_strerror(pamh, pam_error));
return(0);
}
}
#endif
if (setuid(verify->uid) < 0) {
LogError ("setuid %d (user \"%s\") failed, errno=%d\n",
verify->uid, name, errno);
return (0);
}
#else
pwd = getpwnam(name);
if (pwd) {
if (setusercontext(NULL, pwd, pwd->pw_uid, LOGIN_SETALL) < 0) {
LogError ("setusercontext for \"%s\" failed, errno=%d\n", name,
errno);
return (0);
}
endpwent();
} else {
LogError ("getpwnam for \"%s\" failed, errno=%d\n", name, errno);
return (0);
}
#endif
#else
if (setpcred(name, NULL) == -1) {
LogError ("setpcred for \"%s\" failed, errno=%d\n", name, errno);
return (0);
}
#endif
#ifdef SECURE_RPC
{
char netname[MAXNETNAMELEN+1], secretkey[HEXKEYBYTES+1];
int nameret, keyret;
int len;
struct key_netstarg netst;
int key_set_ok = 0;
nameret = getnetname (netname);
Debug ("User netname: %s\n", netname);
len = strlen (passwd);
if (len > 8)
bzero (passwd + 8, len - 8);
keyret = getsecretkey(netname,secretkey,passwd);
Debug ("getsecretkey returns %d, key length %d\n",
keyret, strlen (secretkey));
memcpy(&(netst.st_priv_key), secretkey, HEXKEYBYTES);
netst.st_netname = strdup(netname);
memset(netst.st_pub_key, 0, HEXKEYBYTES);
if (key_setnet(&netst) < 0) {
Debug ("Could not set secret key.\n");
}
free(netst.st_netname);
if (keyret == 1) {
if (*secretkey) {
keyret = key_setsecret(secretkey);
Debug ("key_setsecret returns %d\n", keyret);
if (keyret == -1)
LogError ("failed to set NIS secret key\n");
else
key_set_ok = 1;
} else {
LogError ("password incorrect for NIS principal \"%s\"\n",
nameret ? netname : name);
}
}
if (!key_set_ok) {
int i, j;
for (i = 0; i < d->authNum; i++) {
if (d->authorizations[i]->name_length == 9 &&
memcmp(d->authorizations[i]->name, "SUN-DES-1", 9) == 0) {
for (j = i+1; j < d->authNum; j++)
d->authorizations[j-1] = d->authorizations[j];
d->authNum--;
break;
}
}
}
bzero(secretkey, strlen(secretkey));
}
#endif
#ifdef K5AUTH
{
int i, j;
int result;
extern char *Krb5CCacheName();
result = Krb5Init(name, passwd, d);
if (result == 0) {
verify->userEnviron =
setEnv(verify->userEnviron,
"KRB5CCNAME", Krb5CCacheName(d->name));
} else {
for (i = 0; i < d->authNum; i++) {
if (d->authorizations[i]->name_length == 14 &&
memcmp(d->authorizations[i]->name, "MIT-KERBEROS-5", 14) == 0) {
for (j = i+1; j < d->authNum; j++)
d->authorizations[j-1] = d->authorizations[j];
d->authNum--;
break;
}
}
}
}
#endif
bzero(passwd, strlen(passwd));
SetUserAuthorization (d, verify);
home = getEnv (verify->userEnviron, "HOME");
if (home)
if (chdir (home) == -1) {
LogError ("user \"%s\": cannot chdir to home \"%s\" (err %d), using \"/\"\n",
getEnv (verify->userEnviron, "USER"), home, errno);
chdir ("/");
verify->userEnviron = setEnv(verify->userEnviron, "HOME", "/");
}
if (verify->argv) {
Debug ("executing session %s\n", verify->argv[0]);
execute (verify->argv, verify->userEnviron);
LogError ("Session \"%s\" execution failed (err %d)\n", verify->argv[0], errno);
} else {
LogError ("Session has no command/arguments\n");
}
failsafeArgv[0] = d->failsafeClient;
failsafeArgv[1] = 0;
execute (failsafeArgv, verify->userEnviron);
exit (1);
case -1:
bzero(passwd, strlen(passwd));
Debug ("StartSession, fork failed\n");
LogError ("can't start session on \"%s\", fork failed, errno=%d\n",
d->name, errno);
return 0;
default:
bzero(passwd, strlen(passwd));
Debug ("StartSession, fork succeeded %d\n", pid);
*pidp = pid;
return 1;
}
}
int
source (char **environ, char *file)
{
char **args, *args_safe[2];
int ret;
if (file && file[0]) {
Debug ("source %s\n", file);
args = parseArgs ((char **) 0, file);
if (!args) {
args = args_safe;
args[0] = file;
args[1] = NULL;
}
ret = runAndWait (args, environ);
freeArgs (args);
return ret;
}
return 0;
}
static int
runAndWait (char **args, char **environ)
{
int pid;
waitType result;
switch (pid = fork ()) {
case 0:
CleanUpChild ();
execute (args, environ);
LogError ("can't execute \"%s\" (err %d)\n", args[0], errno);
exit (1);
case -1:
Debug ("fork failed\n");
LogError ("can't fork to execute \"%s\" (err %d)\n", args[0], errno);
return 1;
default:
while (wait (&result) != pid)
;
break;
}
return waitVal (result);
}
void
execute (char **argv, char **environ)
{
(void) close (0);
open ("/dev/null", O_RDONLY);
dup2 (2,1);
execve (argv[0], argv, environ);
if (errno != ENOENT) {
char program[1024], *e, *p, *optarg;
FILE *f;
char **newargv, **av;
int argc;
f = fopen (argv[0], "r");
if (!f)
return;
if (fgets (program, sizeof (program) - 1, f) == NULL) {
fclose (f);
return;
}
fclose (f);
e = program + strlen (program) - 1;
if (*e == '\n')
*e = '\0';
if (!strncmp (program, "#!", 2)) {
p = program + 2;
while (*p && isspace (*p))
++p;
optarg = p;
while (*optarg && !isspace (*optarg))
++optarg;
if (*optarg) {
*optarg = '\0';
do
++optarg;
while (*optarg && isspace (*optarg));
} else
optarg = 0;
} else {
p = "/bin/sh";
optarg = 0;
}
Debug ("Shell script execution: %s (optarg %s)\n",
p, optarg ? optarg : "(null)");
for (av = argv, argc = 0; *av; av++, argc++)
;
newargv = (char **) malloc ((argc + (optarg ? 3 : 2)) * sizeof (char *));
if (!newargv)
return;
av = newargv;
*av++ = p;
if (optarg)
*av++ = optarg;
while ((*av++ = *argv++))
;
execve (newargv[0], newargv, environ);
}
}
char **
defaultEnv (void)
{
char **env, **exp, *value;
env = 0;
for (exp = exportList; exp && *exp; ++exp) {
value = getenv (*exp);
if (value)
env = setEnv (env, *exp, value);
}
return env;
}
char **
systemEnv (struct display *d, char *user, char *home)
{
char **env;
env = defaultEnv ();
env = setEnv (env, "DISPLAY", d->name);
if (home)
env = setEnv (env, "HOME", home);
if (user) {
env = setEnv (env, "USER", user);
env = setEnv (env, "LOGNAME", user);
}
env = setEnv (env, "PATH", d->systemPath);
env = setEnv (env, "SHELL", d->systemShell);
if (d->authFile)
env = setEnv (env, "XAUTHORITY", d->authFile);
return env;
}
#if (defined(Lynx) && !defined(HAS_CRYPT)) || defined(SCO) && !defined(SCO_USA) && !defined(_SCO_DS)
char *crypt(char *s1, char *s2)
{
return(s2);
}
#endif